@ontrails/trails 1.0.0-beta.12 → 1.0.0-beta.13
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/CHANGELOG.md +24 -12
- package/package.json +1 -1
- package/src/__tests__/create.test.ts +35 -33
- package/src/__tests__/guide.test.ts +4 -4
- package/src/__tests__/survey.test.ts +55 -55
- package/src/__tests__/warden.test.ts +2 -2
- package/src/app.ts +2 -2
- package/src/clack.ts +1 -1
- package/src/cli.ts +2 -2
- package/src/trails/add-trail.ts +13 -13
- package/src/trails/{add-surface.ts → add-trailhead.ts} +39 -37
- package/src/trails/add-verify.ts +10 -10
- package/src/trails/create-scaffold.ts +28 -28
- package/src/trails/create.ts +42 -42
- package/src/trails/guide.ts +14 -14
- package/src/trails/survey.ts +83 -82
- package/src/trails/warden.ts +32 -32
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `add.
|
|
2
|
+
* `add.trailhead` trail -- Add a trailhead to an existing project.
|
|
3
3
|
*
|
|
4
4
|
* Generates the CLI or MCP entry point and updates package.json dependencies.
|
|
5
5
|
*/
|
|
@@ -13,24 +13,24 @@ import { z } from 'zod';
|
|
|
13
13
|
import { findTopoPath } from './project.js';
|
|
14
14
|
|
|
15
15
|
const generateCliEntry = (appImportPath: string): string =>
|
|
16
|
-
`import {
|
|
16
|
+
`import { trailhead } from '@ontrails/cli/commander';
|
|
17
17
|
|
|
18
18
|
import { app } from '${appImportPath}';
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
trailhead(app);
|
|
21
21
|
`;
|
|
22
22
|
|
|
23
23
|
const generateMcpEntry = (appImportPath: string): string =>
|
|
24
|
-
`import {
|
|
24
|
+
`import { trailhead } from '@ontrails/mcp';
|
|
25
25
|
|
|
26
26
|
import { app } from '${appImportPath}';
|
|
27
27
|
|
|
28
|
-
await
|
|
28
|
+
await trailhead(app);
|
|
29
29
|
`;
|
|
30
30
|
|
|
31
|
-
/** Resolve the entry file for a
|
|
32
|
-
const getEntryFile = (
|
|
33
|
-
|
|
31
|
+
/** Resolve the entry file for a trailhead. */
|
|
32
|
+
const getEntryFile = (trailhead: 'cli' | 'mcp'): string =>
|
|
33
|
+
trailhead === 'cli' ? 'src/cli.ts' : 'src/mcp.ts';
|
|
34
34
|
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
36
|
// Trail definition
|
|
@@ -39,13 +39,13 @@ const getEntryFile = (surface: 'cli' | 'mcp'): string =>
|
|
|
39
39
|
/** Patch deps and optionally bin in a parsed package.json. */
|
|
40
40
|
const patchPkgDeps = (
|
|
41
41
|
pkg: Record<string, unknown>,
|
|
42
|
-
|
|
42
|
+
trailhead: 'cli' | 'mcp',
|
|
43
43
|
cwd: string
|
|
44
44
|
): string => {
|
|
45
|
-
const depName =
|
|
45
|
+
const depName = trailhead === 'cli' ? '@ontrails/cli' : '@ontrails/mcp';
|
|
46
46
|
const deps = (pkg['dependencies'] ?? {}) as Record<string, string>;
|
|
47
47
|
deps[depName] = 'workspace:*';
|
|
48
|
-
if (
|
|
48
|
+
if (trailhead === 'cli') {
|
|
49
49
|
deps['commander'] = '^14.0.0';
|
|
50
50
|
pkg['bin'] = {
|
|
51
51
|
[(pkg['name'] as string | undefined) ?? basename(cwd)]: './src/cli.ts',
|
|
@@ -57,31 +57,31 @@ const patchPkgDeps = (
|
|
|
57
57
|
return depName;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
/** Update package.json with
|
|
61
|
-
const
|
|
60
|
+
/** Update package.json with trailhead dependency and CLI bin if needed. */
|
|
61
|
+
const updatePkgJsonForTrailhead = async (
|
|
62
62
|
cwd: string,
|
|
63
|
-
|
|
63
|
+
trailhead: 'cli' | 'mcp'
|
|
64
64
|
): Promise<string> => {
|
|
65
65
|
const pkgPath = join(cwd, 'package.json');
|
|
66
66
|
if (!existsSync(pkgPath)) {
|
|
67
|
-
return
|
|
67
|
+
return trailhead === 'cli' ? '@ontrails/cli' : '@ontrails/mcp';
|
|
68
68
|
}
|
|
69
69
|
const pkg = (await Bun.file(pkgPath).json()) as Record<string, unknown>;
|
|
70
|
-
const depName = patchPkgDeps(pkg,
|
|
70
|
+
const depName = patchPkgDeps(pkg, trailhead, cwd);
|
|
71
71
|
await Bun.write(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
72
72
|
return depName;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
/** Create the entry file for a
|
|
76
|
-
const
|
|
75
|
+
/** Create the entry file for a trailhead and return the relative path. */
|
|
76
|
+
const writeTrailheadEntry = async (
|
|
77
77
|
cwd: string,
|
|
78
|
-
|
|
78
|
+
trailhead: 'cli' | 'mcp'
|
|
79
79
|
): Promise<string> => {
|
|
80
|
-
const entryFile = getEntryFile(
|
|
80
|
+
const entryFile = getEntryFile(trailhead);
|
|
81
81
|
const fullEntryPath = join(cwd, entryFile);
|
|
82
82
|
const appImport = (await findTopoPath(cwd)) ?? './app.js';
|
|
83
83
|
const content =
|
|
84
|
-
|
|
84
|
+
trailhead === 'cli'
|
|
85
85
|
? generateCliEntry(appImport)
|
|
86
86
|
: generateMcpEntry(appImport);
|
|
87
87
|
|
|
@@ -90,30 +90,32 @@ const writeSurfaceEntry = async (
|
|
|
90
90
|
return entryFile;
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
export const
|
|
94
|
-
|
|
95
|
-
input: z.object({
|
|
96
|
-
dir: z.string().optional().describe('Project directory'),
|
|
97
|
-
surface: z.enum(['cli', 'mcp']).describe('Surface to add'),
|
|
98
|
-
}),
|
|
99
|
-
output: z.object({
|
|
100
|
-
created: z.string(),
|
|
101
|
-
dependency: z.string(),
|
|
102
|
-
}),
|
|
103
|
-
run: async (input) => {
|
|
93
|
+
export const addTrailhead = trail('add.trailhead', {
|
|
94
|
+
blaze: async (input) => {
|
|
104
95
|
const cwd = resolve(input.dir ?? '.');
|
|
105
|
-
const {
|
|
106
|
-
const entryFile = getEntryFile(
|
|
96
|
+
const { trailhead } = input;
|
|
97
|
+
const entryFile = getEntryFile(trailhead);
|
|
107
98
|
|
|
108
99
|
if (existsSync(join(cwd, entryFile))) {
|
|
109
100
|
return Result.err(
|
|
110
|
-
new Error(
|
|
101
|
+
new Error(
|
|
102
|
+
`${trailhead.toUpperCase()} trailhead already exists. Nothing to do.`
|
|
103
|
+
)
|
|
111
104
|
);
|
|
112
105
|
}
|
|
113
106
|
|
|
114
107
|
return Result.ok({
|
|
115
|
-
created: await
|
|
116
|
-
dependency: await
|
|
108
|
+
created: await writeTrailheadEntry(cwd, trailhead),
|
|
109
|
+
dependency: await updatePkgJsonForTrailhead(cwd, trailhead),
|
|
117
110
|
});
|
|
118
111
|
},
|
|
112
|
+
description: 'Add a trailhead to an existing project',
|
|
113
|
+
input: z.object({
|
|
114
|
+
dir: z.string().optional().describe('Project directory'),
|
|
115
|
+
trailhead: z.enum(['cli', 'mcp']).describe('Trailhead to add'),
|
|
116
|
+
}),
|
|
117
|
+
output: z.object({
|
|
118
|
+
created: z.string(),
|
|
119
|
+
dependency: z.string(),
|
|
120
|
+
}),
|
|
119
121
|
});
|
package/src/trails/add-verify.ts
CHANGED
|
@@ -55,16 +55,7 @@ const updatePackageJsonForVerify = async (
|
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
|
|
57
57
|
export const addVerify = trail('add.verify', {
|
|
58
|
-
|
|
59
|
-
input: z.object({
|
|
60
|
-
dir: z.string().optional().describe('Parent directory'),
|
|
61
|
-
name: z.string().describe('Project name'),
|
|
62
|
-
}),
|
|
63
|
-
metadata: { internal: true },
|
|
64
|
-
output: z.object({
|
|
65
|
-
created: z.array(z.string()),
|
|
66
|
-
}),
|
|
67
|
-
run: async (input) => {
|
|
58
|
+
blaze: async (input) => {
|
|
68
59
|
const projectDir = resolve(input.dir ?? '.', input.name);
|
|
69
60
|
const files: string[] = [];
|
|
70
61
|
|
|
@@ -84,4 +75,13 @@ export const addVerify = trail('add.verify', {
|
|
|
84
75
|
|
|
85
76
|
return Result.ok({ created: files });
|
|
86
77
|
},
|
|
78
|
+
description: 'Add testing and warden verification',
|
|
79
|
+
input: z.object({
|
|
80
|
+
dir: z.string().optional().describe('Parent directory'),
|
|
81
|
+
name: z.string().describe('Project name'),
|
|
82
|
+
}),
|
|
83
|
+
meta: { internal: true },
|
|
84
|
+
output: z.object({
|
|
85
|
+
created: z.array(z.string()),
|
|
86
|
+
}),
|
|
87
87
|
});
|
|
@@ -73,7 +73,7 @@ const TSCONFIG_CONTENT = JSON.stringify(
|
|
|
73
73
|
const GITIGNORE_CONTENT = `node_modules/
|
|
74
74
|
dist/
|
|
75
75
|
*.tsbuildinfo
|
|
76
|
-
.trails/
|
|
76
|
+
.trails/_trailhead.json
|
|
77
77
|
`;
|
|
78
78
|
|
|
79
79
|
const OXLINTRC_CONTENT = JSON.stringify(
|
|
@@ -107,7 +107,7 @@ export const hello = trail('hello', {
|
|
|
107
107
|
name: 'Named greeting',
|
|
108
108
|
},
|
|
109
109
|
],
|
|
110
|
-
|
|
110
|
+
blaze: (input) => {
|
|
111
111
|
const name = input.name ?? 'world';
|
|
112
112
|
return Result.ok({ message: \`Hello, \${name}!\` });
|
|
113
113
|
},
|
|
@@ -139,7 +139,7 @@ export const show = trail('entity.show', {
|
|
|
139
139
|
name: 'Show entity',
|
|
140
140
|
},
|
|
141
141
|
],
|
|
142
|
-
|
|
142
|
+
blaze: (input) => {
|
|
143
143
|
return Result.ok({ id: input.id, name: 'Example' });
|
|
144
144
|
},
|
|
145
145
|
input: z.object({ id: z.string() }),
|
|
@@ -156,7 +156,7 @@ export const add = trail('entity.add', {
|
|
|
156
156
|
name: 'Add entity',
|
|
157
157
|
},
|
|
158
158
|
],
|
|
159
|
-
|
|
159
|
+
blaze: (input) => {
|
|
160
160
|
return Result.ok({ id: '1', name: input.name });
|
|
161
161
|
},
|
|
162
162
|
input: z.object({ name: z.string() }),
|
|
@@ -177,7 +177,7 @@ export const search = trail('search', {
|
|
|
177
177
|
name: 'Search entities',
|
|
178
178
|
},
|
|
179
179
|
],
|
|
180
|
-
|
|
180
|
+
blaze: () => {
|
|
181
181
|
return Result.ok({ results: [] });
|
|
182
182
|
},
|
|
183
183
|
input: z.object({ query: z.string() }),
|
|
@@ -194,9 +194,9 @@ import { z } from 'zod';
|
|
|
194
194
|
|
|
195
195
|
export const onboard = trail('entity.onboard', {
|
|
196
196
|
description: 'Onboard a new entity end-to-end',
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const result = await ctx.
|
|
197
|
+
crosses: ['entity.add'],
|
|
198
|
+
blaze: async (input, ctx) => {
|
|
199
|
+
const result = await ctx.cross('entity.add', { name: input.name });
|
|
200
200
|
if (result.isErr()) {
|
|
201
201
|
return result;
|
|
202
202
|
}
|
|
@@ -207,11 +207,11 @@ export const onboard = trail('entity.onboard', {
|
|
|
207
207
|
});
|
|
208
208
|
`;
|
|
209
209
|
|
|
210
|
-
const
|
|
211
|
-
`import {
|
|
210
|
+
const generateEntitySignals = (): string =>
|
|
211
|
+
`import { signal } from '@ontrails/core';
|
|
212
212
|
import { z } from 'zod';
|
|
213
213
|
|
|
214
|
-
export const entityUpdated =
|
|
214
|
+
export const entityUpdated = signal('entity.updated', {
|
|
215
215
|
description: 'Fired when an entity is updated',
|
|
216
216
|
payload: z.object({
|
|
217
217
|
entityId: z.string(),
|
|
@@ -248,9 +248,9 @@ const starterImports: Record<
|
|
|
248
248
|
"import * as entity from './trails/entity.js';",
|
|
249
249
|
"import * as search from './trails/search.js';",
|
|
250
250
|
"import * as onboard from './trails/onboard.js';",
|
|
251
|
-
"import * as
|
|
251
|
+
"import * as entitySignals from './signals/entity-signals.js';",
|
|
252
252
|
],
|
|
253
|
-
modules: ['entity', 'search', 'onboard', '
|
|
253
|
+
modules: ['entity', 'search', 'onboard', 'entitySignals'],
|
|
254
254
|
},
|
|
255
255
|
hello: {
|
|
256
256
|
imports: ["import * as hello from './trails/hello.js';"],
|
|
@@ -282,7 +282,7 @@ const starterFileGenerators: Record<Starter, () => [string, string][]> = {
|
|
|
282
282
|
['src/trails/entity.ts', generateEntityTrails()],
|
|
283
283
|
['src/trails/search.ts', generateSearchTrail()],
|
|
284
284
|
['src/trails/onboard.ts', generateOnboardTrail()],
|
|
285
|
-
['src/
|
|
285
|
+
['src/signals/entity-signals.ts', generateEntitySignals()],
|
|
286
286
|
['src/store.ts', generateStore()],
|
|
287
287
|
],
|
|
288
288
|
hello: () => [['src/trails/hello.ts', generateHelloTrail()]],
|
|
@@ -321,6 +321,19 @@ const writeScaffoldFiles = async (
|
|
|
321
321
|
// ---------------------------------------------------------------------------
|
|
322
322
|
|
|
323
323
|
export const createScaffold = trail('create.scaffold', {
|
|
324
|
+
blaze: async (input) => {
|
|
325
|
+
const projectDir = resolve(input.dir ?? '.', input.name);
|
|
326
|
+
const starter = (input.starter ?? 'hello') as Starter;
|
|
327
|
+
const fileMap = collectScaffoldFiles(input.name, starter);
|
|
328
|
+
const files = await writeScaffoldFiles(projectDir, fileMap);
|
|
329
|
+
mkdirSync(join(projectDir, '.trails'), { recursive: true });
|
|
330
|
+
|
|
331
|
+
return Result.ok({
|
|
332
|
+
created: files,
|
|
333
|
+
dir: projectDir,
|
|
334
|
+
name: input.name,
|
|
335
|
+
} satisfies ScaffoldResult);
|
|
336
|
+
},
|
|
324
337
|
description: 'Scaffold a new Trails project',
|
|
325
338
|
input: z.object({
|
|
326
339
|
dir: z.string().optional().describe('Parent directory'),
|
|
@@ -330,23 +343,10 @@ export const createScaffold = trail('create.scaffold', {
|
|
|
330
343
|
.default('hello')
|
|
331
344
|
.describe('Starter trail'),
|
|
332
345
|
}),
|
|
333
|
-
|
|
346
|
+
meta: { internal: true },
|
|
334
347
|
output: z.object({
|
|
335
348
|
created: z.array(z.string()),
|
|
336
349
|
dir: z.string(),
|
|
337
350
|
name: z.string(),
|
|
338
351
|
}),
|
|
339
|
-
run: async (input) => {
|
|
340
|
-
const projectDir = resolve(input.dir ?? '.', input.name);
|
|
341
|
-
const starter = (input.starter ?? 'hello') as Starter;
|
|
342
|
-
const fileMap = collectScaffoldFiles(input.name, starter);
|
|
343
|
-
const files = await writeScaffoldFiles(projectDir, fileMap);
|
|
344
|
-
mkdirSync(join(projectDir, '.trails'), { recursive: true });
|
|
345
|
-
|
|
346
|
-
return Result.ok({
|
|
347
|
-
created: files,
|
|
348
|
-
dir: projectDir,
|
|
349
|
-
name: input.name,
|
|
350
|
-
} satisfies ScaffoldResult);
|
|
351
|
-
},
|
|
352
352
|
});
|
package/src/trails/create.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `create` route -- Create a new Trails project.
|
|
3
3
|
*
|
|
4
|
-
* Composes create.scaffold, add.
|
|
5
|
-
* via ctx.
|
|
4
|
+
* Composes create.scaffold, add.trailhead, and add.verify sub-trails
|
|
5
|
+
* via ctx.cross().
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type {
|
|
8
|
+
import type { CrossFn } from '@ontrails/core';
|
|
9
9
|
import { Result, trail } from '@ontrails/core';
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
|
|
@@ -14,13 +14,13 @@ import { z } from 'zod';
|
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
|
|
16
16
|
type Starter = 'empty' | 'entity' | 'hello';
|
|
17
|
-
type
|
|
17
|
+
type Trailhead = 'cli' | 'mcp';
|
|
18
18
|
|
|
19
|
-
interface
|
|
19
|
+
interface CreateInput {
|
|
20
20
|
readonly dir?: string | undefined;
|
|
21
21
|
readonly name: string;
|
|
22
22
|
readonly starter: Starter;
|
|
23
|
-
readonly
|
|
23
|
+
readonly trailheads: readonly Trailhead[];
|
|
24
24
|
readonly verify: boolean;
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -48,9 +48,9 @@ const buildScaffoldInput = (input: ScaffoldRequest) => ({
|
|
|
48
48
|
starter: input.starter,
|
|
49
49
|
});
|
|
50
50
|
|
|
51
|
-
const
|
|
51
|
+
const buildTrailheadInput = (dir: string, trailhead: string) => ({
|
|
52
52
|
dir,
|
|
53
|
-
|
|
53
|
+
trailhead,
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
const buildVerifyInput = (input: VerifyRequest) => ({
|
|
@@ -59,22 +59,22 @@ const buildVerifyInput = (input: VerifyRequest) => ({
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
const scaffoldProject = (
|
|
62
|
-
|
|
62
|
+
cross: CrossFn,
|
|
63
63
|
input: ScaffoldRequest
|
|
64
64
|
): Promise<Result<ScaffoldedProject, Error>> =>
|
|
65
|
-
|
|
65
|
+
cross('create.scaffold', buildScaffoldInput(input));
|
|
66
66
|
|
|
67
|
-
const
|
|
68
|
-
|
|
67
|
+
const addTrailheadFiles = async (
|
|
68
|
+
cross: CrossFn,
|
|
69
69
|
dir: string,
|
|
70
|
-
|
|
70
|
+
trailheads: readonly string[]
|
|
71
71
|
): Promise<Result<string[], Error>> => {
|
|
72
72
|
const created: string[] = [];
|
|
73
73
|
|
|
74
|
-
for (const
|
|
75
|
-
const result = await
|
|
76
|
-
'add.
|
|
77
|
-
|
|
74
|
+
for (const trailhead of trailheads) {
|
|
75
|
+
const result = await cross<{ created: string; dependency: string }>(
|
|
76
|
+
'add.trailhead',
|
|
77
|
+
buildTrailheadInput(dir, trailhead)
|
|
78
78
|
);
|
|
79
79
|
if (result.isErr()) {
|
|
80
80
|
return Result.err(result.error);
|
|
@@ -86,14 +86,14 @@ const addSurfaceFiles = async (
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
const collectVerifyFiles = async (
|
|
89
|
-
|
|
89
|
+
cross: CrossFn,
|
|
90
90
|
input: VerifyRequest
|
|
91
91
|
): Promise<Result<string[], Error>> => {
|
|
92
92
|
if (!input.verify) {
|
|
93
93
|
return Result.ok([]);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
const result = await
|
|
96
|
+
const result = await cross<{ created: string[] }>(
|
|
97
97
|
'add.verify',
|
|
98
98
|
buildVerifyInput(input)
|
|
99
99
|
);
|
|
@@ -104,29 +104,29 @@ const collectVerifyFiles = async (
|
|
|
104
104
|
|
|
105
105
|
const collectCreatedFiles = (
|
|
106
106
|
scaffolded: readonly string[],
|
|
107
|
-
|
|
107
|
+
trailheads: readonly string[],
|
|
108
108
|
verify: readonly string[]
|
|
109
|
-
): string[] => [...scaffolded, ...
|
|
109
|
+
): string[] => [...scaffolded, ...trailheads, ...verify];
|
|
110
110
|
|
|
111
111
|
const runCreate = async (
|
|
112
|
-
|
|
113
|
-
input:
|
|
112
|
+
cross: CrossFn,
|
|
113
|
+
input: CreateInput
|
|
114
114
|
): Promise<Result<{ created: string[]; dir: string; name: string }, Error>> => {
|
|
115
|
-
const scaffolded = await scaffoldProject(
|
|
115
|
+
const scaffolded = await scaffoldProject(cross, input);
|
|
116
116
|
if (scaffolded.isErr()) {
|
|
117
117
|
return Result.err(scaffolded.error);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const
|
|
121
|
-
|
|
120
|
+
const trailheadResults = await addTrailheadFiles(
|
|
121
|
+
cross,
|
|
122
122
|
scaffolded.value.dir,
|
|
123
|
-
input.
|
|
123
|
+
input.trailheads
|
|
124
124
|
);
|
|
125
|
-
if (
|
|
126
|
-
return Result.err(
|
|
125
|
+
if (trailheadResults.isErr()) {
|
|
126
|
+
return Result.err(trailheadResults.error);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
const verifyFiles = await collectVerifyFiles(
|
|
129
|
+
const verifyFiles = await collectVerifyFiles(cross, input);
|
|
130
130
|
if (verifyFiles.isErr()) {
|
|
131
131
|
return Result.err(verifyFiles.error);
|
|
132
132
|
}
|
|
@@ -134,7 +134,7 @@ const runCreate = async (
|
|
|
134
134
|
return Result.ok({
|
|
135
135
|
created: collectCreatedFiles(
|
|
136
136
|
scaffolded.value.created,
|
|
137
|
-
|
|
137
|
+
trailheadResults.value,
|
|
138
138
|
verifyFiles.value
|
|
139
139
|
),
|
|
140
140
|
dir: scaffolded.value.dir,
|
|
@@ -147,6 +147,13 @@ const runCreate = async (
|
|
|
147
147
|
// ---------------------------------------------------------------------------
|
|
148
148
|
|
|
149
149
|
export const createRoute = trail('create', {
|
|
150
|
+
blaze: async (input: CreateInput, ctx) => {
|
|
151
|
+
if (!ctx.cross) {
|
|
152
|
+
return Result.err(new Error('create route requires ctx.cross'));
|
|
153
|
+
}
|
|
154
|
+
return await runCreate(ctx.cross, input);
|
|
155
|
+
},
|
|
156
|
+
crosses: ['create.scaffold', 'add.trailhead', 'add.verify'],
|
|
150
157
|
description: 'Create a new Trails project',
|
|
151
158
|
fields: {
|
|
152
159
|
starter: {
|
|
@@ -157,14 +164,14 @@ export const createRoute = trail('create', {
|
|
|
157
164
|
value: 'hello',
|
|
158
165
|
},
|
|
159
166
|
{
|
|
160
|
-
hint: '4 trails,
|
|
167
|
+
hint: '4 trails, signal, store',
|
|
161
168
|
label: 'Entity CRUD',
|
|
162
169
|
value: 'entity',
|
|
163
170
|
},
|
|
164
171
|
{ hint: 'Just the structure', label: 'Empty', value: 'empty' },
|
|
165
172
|
],
|
|
166
173
|
},
|
|
167
|
-
|
|
174
|
+
trailheads: {
|
|
168
175
|
options: [
|
|
169
176
|
{ hint: 'Commander-based command line', label: 'CLI', value: 'cli' },
|
|
170
177
|
{
|
|
@@ -175,7 +182,6 @@ export const createRoute = trail('create', {
|
|
|
175
182
|
],
|
|
176
183
|
},
|
|
177
184
|
},
|
|
178
|
-
follow: ['create.scaffold', 'add.surface', 'add.verify'],
|
|
179
185
|
input: z.object({
|
|
180
186
|
dir: z.string().optional().describe('Parent directory'),
|
|
181
187
|
name: z.string().describe('Project name'),
|
|
@@ -183,10 +189,10 @@ export const createRoute = trail('create', {
|
|
|
183
189
|
.enum(['hello', 'entity', 'empty'])
|
|
184
190
|
.default('hello')
|
|
185
191
|
.describe('Starter trail'),
|
|
186
|
-
|
|
192
|
+
trailheads: z
|
|
187
193
|
.array(z.enum(['cli', 'mcp']))
|
|
188
194
|
.default(['cli'])
|
|
189
|
-
.describe('
|
|
195
|
+
.describe('Trailheads'),
|
|
190
196
|
verify: z.boolean().default(true).describe('Include testing + warden'),
|
|
191
197
|
}),
|
|
192
198
|
output: z.object({
|
|
@@ -194,10 +200,4 @@ export const createRoute = trail('create', {
|
|
|
194
200
|
dir: z.string(),
|
|
195
201
|
name: z.string(),
|
|
196
202
|
}),
|
|
197
|
-
run: async (input: BlazeInput, ctx) => {
|
|
198
|
-
if (!ctx.follow) {
|
|
199
|
-
return Result.err(new Error('create route requires ctx.follow'));
|
|
200
|
-
}
|
|
201
|
-
return await runCreate(ctx.follow, input);
|
|
202
|
-
},
|
|
203
203
|
});
|
package/src/trails/guide.ts
CHANGED
|
@@ -55,11 +55,24 @@ const toGuideDetail = (item: Trail<unknown, unknown>): object => ({
|
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
export const guideTrail = trail('guide', {
|
|
58
|
+
blaze: async (input, ctx) => {
|
|
59
|
+
const app = await loadApp(input.module, ctx.cwd ?? '.');
|
|
60
|
+
|
|
61
|
+
if (input.trailId) {
|
|
62
|
+
const item = app.get(input.trailId);
|
|
63
|
+
if (!item) {
|
|
64
|
+
return Result.err(new Error(`Trail not found: ${input.trailId}`));
|
|
65
|
+
}
|
|
66
|
+
return Result.ok(toGuideDetail(item as Trail<unknown, unknown>));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return Result.ok(toGuideEntries(app));
|
|
70
|
+
},
|
|
58
71
|
description: 'Runtime guidance for trails',
|
|
59
72
|
examples: [
|
|
60
73
|
{
|
|
61
74
|
description: 'Lists all trails with descriptions and example counts',
|
|
62
|
-
input: {},
|
|
75
|
+
input: { module: './src/app.ts' },
|
|
63
76
|
name: 'List trail guidance',
|
|
64
77
|
},
|
|
65
78
|
],
|
|
@@ -88,17 +101,4 @@ export const guideTrail = trail('guide', {
|
|
|
88
101
|
kind: z.string(),
|
|
89
102
|
}),
|
|
90
103
|
]),
|
|
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
104
|
});
|