@ontrails/trails 1.0.0-beta.14 → 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 +13 -0
- package/__tests__/examples.test.ts +29 -4
- package/dist/src/app.js +2 -2
- package/dist/src/app.js.map +1 -1
- package/dist/src/clack.d.ts +1 -1
- package/dist/src/clack.js +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 +38 -11
- package/dist/src/trails/add-trail.js.map +1 -1
- package/dist/src/trails/add-trailhead.d.ts +1 -1
- package/dist/src/trails/add-trailhead.d.ts.map +1 -1
- package/dist/src/trails/add-trailhead.js +4 -4
- 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 +7 -6
- 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 +10 -3
- package/dist/src/trails/create-scaffold.js.map +1 -1
- package/dist/src/trails/create.d.ts +10 -14
- package/dist/src/trails/create.d.ts.map +1 -1
- package/dist/src/trails/create.js +42 -37
- package/dist/src/trails/create.js.map +1 -1
- package/dist/src/trails/dev-clean.d.ts +4 -4
- package/dist/src/trails/dev-clean.d.ts.map +1 -1
- package/dist/src/trails/dev-clean.js +22 -21
- package/dist/src/trails/dev-clean.js.map +1 -1
- package/dist/src/trails/dev-reset.d.ts +1 -1
- package/dist/src/trails/dev-reset.d.ts.map +1 -1
- package/dist/src/trails/dev-reset.js +3 -2
- package/dist/src/trails/dev-reset.js.map +1 -1
- package/dist/src/trails/dev-stats.d.ts +4 -4
- package/dist/src/trails/dev-stats.d.ts.map +1 -1
- package/dist/src/trails/dev-stats.js +19 -19
- package/dist/src/trails/dev-stats.js.map +1 -1
- package/dist/src/trails/dev-support.d.ts +17 -17
- package/dist/src/trails/dev-support.d.ts.map +1 -1
- package/dist/src/trails/dev-support.js +60 -57
- package/dist/src/trails/dev-support.js.map +1 -1
- package/dist/src/trails/draft-promote.d.ts +1 -1
- package/dist/src/trails/draft-promote.d.ts.map +1 -1
- package/dist/src/trails/draft-promote.js +30 -16
- package/dist/src/trails/draft-promote.js.map +1 -1
- package/dist/src/trails/guide.d.ts +2 -2
- package/dist/src/trails/guide.d.ts.map +1 -1
- package/dist/src/trails/guide.js +1 -4
- package/dist/src/trails/guide.js.map +1 -1
- package/dist/src/trails/load-app.d.ts +7 -1
- package/dist/src/trails/load-app.d.ts.map +1 -1
- package/dist/src/trails/load-app.js +374 -26
- package/dist/src/trails/load-app.js.map +1 -1
- package/dist/src/trails/survey.d.ts +5 -5
- package/dist/src/trails/survey.d.ts.map +1 -1
- package/dist/src/trails/survey.js +51 -29
- package/dist/src/trails/survey.js.map +1 -1
- package/dist/src/trails/topo-export.d.ts +5 -4
- package/dist/src/trails/topo-export.d.ts.map +1 -1
- package/dist/src/trails/topo-export.js +4 -7
- package/dist/src/trails/topo-export.js.map +1 -1
- package/dist/src/trails/topo-history.d.ts +6 -10
- package/dist/src/trails/topo-history.d.ts.map +1 -1
- package/dist/src/trails/topo-history.js +7 -8
- package/dist/src/trails/topo-history.js.map +1 -1
- package/dist/src/trails/topo-pin.d.ts +5 -9
- package/dist/src/trails/topo-pin.d.ts.map +1 -1
- package/dist/src/trails/topo-pin.js +5 -9
- package/dist/src/trails/topo-pin.js.map +1 -1
- package/dist/src/trails/topo-read-support.d.ts +12 -8
- package/dist/src/trails/topo-read-support.d.ts.map +1 -1
- package/dist/src/trails/topo-read-support.js +71 -82
- package/dist/src/trails/topo-read-support.js.map +1 -1
- package/dist/src/trails/topo-reports.d.ts +15 -11
- package/dist/src/trails/topo-reports.d.ts.map +1 -1
- package/dist/src/trails/topo-reports.js +40 -34
- package/dist/src/trails/topo-reports.js.map +1 -1
- package/dist/src/trails/topo-show.d.ts +4 -4
- package/dist/src/trails/topo-show.d.ts.map +1 -1
- package/dist/src/trails/topo-show.js +8 -12
- package/dist/src/trails/topo-show.js.map +1 -1
- package/dist/src/trails/topo-store-support.d.ts +2 -2
- package/dist/src/trails/topo-store-support.d.ts.map +1 -1
- package/dist/src/trails/topo-store-support.js +22 -22
- package/dist/src/trails/topo-store-support.js.map +1 -1
- package/dist/src/trails/topo-support.d.ts +19 -30
- package/dist/src/trails/topo-support.d.ts.map +1 -1
- package/dist/src/trails/topo-support.js +63 -96
- package/dist/src/trails/topo-support.js.map +1 -1
- package/dist/src/trails/topo-unpin.d.ts +9 -4
- package/dist/src/trails/topo-unpin.d.ts.map +1 -1
- package/dist/src/trails/topo-unpin.js +9 -4
- package/dist/src/trails/topo-unpin.js.map +1 -1
- package/dist/src/trails/topo-verify.d.ts +2 -2
- package/dist/src/trails/topo-verify.d.ts.map +1 -1
- package/dist/src/trails/topo-verify.js +1 -5
- package/dist/src/trails/topo-verify.js.map +1 -1
- package/dist/src/trails/topo.d.ts +2 -2
- package/dist/src/trails/topo.d.ts.map +1 -1
- package/dist/src/trails/topo.js +6 -10
- package/dist/src/trails/topo.js.map +1 -1
- 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 +11 -2
- 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 -8
- package/src/__tests__/add-trail.test.ts +97 -0
- package/src/__tests__/create.test.ts +91 -27
- package/src/__tests__/guide.test.ts +10 -5
- package/src/__tests__/load-app.test.ts +365 -4
- package/src/__tests__/survey.test.ts +143 -67
- package/src/__tests__/topo-dev.test.ts +48 -46
- package/src/app.ts +2 -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 +25 -21
- package/src/trails/dev-reset.ts +3 -2
- package/src/trails/dev-stats.ts +22 -19
- package/src/trails/dev-support.ts +88 -86
- package/src/trails/draft-promote.ts +70 -35
- package/src/trails/guide.ts +1 -4
- package/src/trails/load-app.ts +511 -32
- package/src/trails/survey.ts +60 -35
- package/src/trails/topo-export.ts +5 -9
- package/src/trails/topo-history.ts +8 -10
- package/src/trails/topo-pin.ts +9 -13
- package/src/trails/topo-read-support.ts +103 -106
- package/src/trails/topo-reports.ts +54 -47
- package/src/trails/topo-show.ts +8 -12
- package/src/trails/topo-store-support.ts +36 -28
- package/src/trails/topo-support.ts +90 -134
- package/src/trails/topo-unpin.ts +11 -6
- package/src/trails/topo-verify.ts +1 -5
- package/src/trails/topo.ts +6 -10
- package/src/trails/warden.ts +12 -3
- package/src/versions.ts +43 -0
- package/tsconfig.tests.json +10 -0
- package/src/trails/add-trailhead.ts +0 -121
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `add.surface` trail -- Add a surface to an existing project.
|
|
3
|
+
*
|
|
4
|
+
* Generates surface entry points and updates package.json dependencies.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
9
|
+
|
|
10
|
+
import { Result, trail } from '@ontrails/core';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
ontrailsPackageRange,
|
|
15
|
+
scaffoldDependencyVersions,
|
|
16
|
+
} from '../versions.js';
|
|
17
|
+
import { findTopoPath } from './project.js';
|
|
18
|
+
|
|
19
|
+
type Surface = 'cli' | 'http' | 'mcp';
|
|
20
|
+
|
|
21
|
+
const generateCliEntry = (appImportPath: string): string =>
|
|
22
|
+
`import { surface } from '@ontrails/cli/commander';
|
|
23
|
+
|
|
24
|
+
import { app } from '${appImportPath}';
|
|
25
|
+
|
|
26
|
+
await surface(app);
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
const generateMcpEntry = (appImportPath: string): string =>
|
|
30
|
+
`import { surface } from '@ontrails/mcp';
|
|
31
|
+
|
|
32
|
+
import { app } from '${appImportPath}';
|
|
33
|
+
|
|
34
|
+
await surface(app);
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const generateHttpEntry = (appImportPath: string): string =>
|
|
38
|
+
`import { surface } from '@ontrails/hono';
|
|
39
|
+
|
|
40
|
+
import { app } from '${appImportPath}';
|
|
41
|
+
|
|
42
|
+
await surface(app, { port: 3000 });
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const surfaceEntryFiles = {
|
|
46
|
+
cli: 'src/cli.ts',
|
|
47
|
+
http: 'src/http.ts',
|
|
48
|
+
mcp: 'src/mcp.ts',
|
|
49
|
+
} satisfies Record<Surface, string>;
|
|
50
|
+
|
|
51
|
+
const surfaceDependencies = {
|
|
52
|
+
cli: ['@ontrails/cli'],
|
|
53
|
+
http: ['@ontrails/hono', '@ontrails/http'],
|
|
54
|
+
mcp: ['@ontrails/mcp'],
|
|
55
|
+
} satisfies Record<Surface, readonly string[]>;
|
|
56
|
+
|
|
57
|
+
/** Resolve the entry file for a surface. */
|
|
58
|
+
const getEntryFile = (surface: Surface): string => surfaceEntryFiles[surface];
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Trail definition
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/** Patch deps and optionally bin in a parsed package.json. */
|
|
65
|
+
const patchPkgDeps = (
|
|
66
|
+
pkg: Record<string, unknown>,
|
|
67
|
+
surface: Surface,
|
|
68
|
+
cwd: string
|
|
69
|
+
): string => {
|
|
70
|
+
const [depName = ''] = surfaceDependencies[surface];
|
|
71
|
+
const deps = (pkg['dependencies'] ?? {}) as Record<string, string>;
|
|
72
|
+
for (const dependency of surfaceDependencies[surface]) {
|
|
73
|
+
deps[dependency] = ontrailsPackageRange;
|
|
74
|
+
}
|
|
75
|
+
if (surface === 'cli') {
|
|
76
|
+
deps['commander'] = scaffoldDependencyVersions.commander;
|
|
77
|
+
pkg['bin'] = {
|
|
78
|
+
[(pkg['name'] as string | undefined) ?? basename(cwd)]: './src/cli.ts',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
pkg['dependencies'] = Object.fromEntries(
|
|
82
|
+
Object.entries(deps).toSorted(([a], [b]) => a.localeCompare(b))
|
|
83
|
+
);
|
|
84
|
+
return depName;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Update package.json with surface dependency and CLI bin if needed. */
|
|
88
|
+
const updatePkgJsonForSurface = async (
|
|
89
|
+
cwd: string,
|
|
90
|
+
surface: Surface
|
|
91
|
+
): Promise<string> => {
|
|
92
|
+
const pkgPath = join(cwd, 'package.json');
|
|
93
|
+
if (!existsSync(pkgPath)) {
|
|
94
|
+
return surfaceDependencies[surface][0] ?? '';
|
|
95
|
+
}
|
|
96
|
+
const pkg = (await Bun.file(pkgPath).json()) as Record<string, unknown>;
|
|
97
|
+
const depName = patchPkgDeps(pkg, surface, cwd);
|
|
98
|
+
await Bun.write(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
99
|
+
return depName;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** Create the entry file for a surface and return the relative path. */
|
|
103
|
+
const writeSurfaceEntry = async (
|
|
104
|
+
cwd: string,
|
|
105
|
+
surface: Surface
|
|
106
|
+
): Promise<string> => {
|
|
107
|
+
const entryFile = getEntryFile(surface);
|
|
108
|
+
const fullEntryPath = join(cwd, entryFile);
|
|
109
|
+
const appImport = (await findTopoPath(cwd)) ?? './app.js';
|
|
110
|
+
const generators = {
|
|
111
|
+
cli: generateCliEntry,
|
|
112
|
+
http: generateHttpEntry,
|
|
113
|
+
mcp: generateMcpEntry,
|
|
114
|
+
} satisfies Record<Surface, (appImportPath: string) => string>;
|
|
115
|
+
const content = generators[surface](appImport);
|
|
116
|
+
|
|
117
|
+
mkdirSync(dirname(fullEntryPath), { recursive: true });
|
|
118
|
+
await Bun.write(fullEntryPath, content);
|
|
119
|
+
return entryFile;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const addSurface = trail('add.surface', {
|
|
123
|
+
blaze: async (input) => {
|
|
124
|
+
const cwd = resolve(input.dir ?? '.');
|
|
125
|
+
const { surface } = input;
|
|
126
|
+
const entryFile = getEntryFile(surface);
|
|
127
|
+
|
|
128
|
+
if (existsSync(join(cwd, entryFile))) {
|
|
129
|
+
return Result.err(
|
|
130
|
+
new Error(
|
|
131
|
+
`${surface.toUpperCase()} surface already exists. Nothing to do.`
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return Result.ok({
|
|
137
|
+
created: await writeSurfaceEntry(cwd, surface),
|
|
138
|
+
dependency: await updatePkgJsonForSurface(cwd, surface),
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
description: 'Add a surface to an existing project',
|
|
142
|
+
input: z.object({
|
|
143
|
+
dir: z.string().optional().describe('Project directory'),
|
|
144
|
+
surface: z.enum(['cli', 'http', 'mcp']).describe('Surface to add'),
|
|
145
|
+
}),
|
|
146
|
+
output: z.object({
|
|
147
|
+
created: z.string(),
|
|
148
|
+
dependency: z.string(),
|
|
149
|
+
}),
|
|
150
|
+
});
|
package/src/trails/add-trail.ts
CHANGED
|
@@ -12,40 +12,53 @@ import { z } from 'zod';
|
|
|
12
12
|
// Helpers
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
|
|
15
|
+
const literal = (value: string): string => JSON.stringify(value);
|
|
16
|
+
|
|
17
|
+
const deriveExampleMessage = (id: string): string => `${id} completed`;
|
|
18
|
+
|
|
15
19
|
const generateTrailFile = (
|
|
16
20
|
id: string,
|
|
21
|
+
description: string,
|
|
22
|
+
exampleName: string,
|
|
17
23
|
intent: 'read' | 'write' | 'destroy'
|
|
18
24
|
): string => {
|
|
19
25
|
const intentLine = intent === 'write' ? '' : `\n intent: '${intent}',`;
|
|
26
|
+
const exampleMessage = deriveExampleMessage(id);
|
|
20
27
|
|
|
21
28
|
return `import { Result, trail } from '@ontrails/core';
|
|
22
29
|
import { z } from 'zod';
|
|
23
30
|
|
|
24
31
|
export const ${id.replaceAll('.', '_')} = trail('${id}', {
|
|
25
|
-
|
|
32
|
+
blaze: async () => {
|
|
33
|
+
return Result.ok({ message: ${literal(exampleMessage)} });
|
|
34
|
+
},
|
|
35
|
+
description: ${literal(description)},
|
|
26
36
|
examples: [
|
|
27
37
|
{
|
|
38
|
+
expected: { message: ${literal(exampleMessage)} },
|
|
28
39
|
input: {},
|
|
29
|
-
name:
|
|
40
|
+
name: ${literal(exampleName)},
|
|
30
41
|
},
|
|
31
42
|
],
|
|
32
|
-
blaze: async (input) => {
|
|
33
|
-
return Result.ok({ message: 'TODO' });
|
|
34
|
-
},
|
|
35
43
|
input: z.object({}),${intentLine}
|
|
36
44
|
output: z.object({ message: z.string() }),
|
|
37
45
|
});
|
|
38
46
|
`;
|
|
39
47
|
};
|
|
40
48
|
|
|
41
|
-
const generateTestFile = (id: string): string => {
|
|
49
|
+
const generateTestFile = (id: string, exampleName: string): string => {
|
|
42
50
|
const moduleName = id.replaceAll('.', '-');
|
|
43
51
|
const trailName = id.replaceAll('.', '_');
|
|
52
|
+
const exampleMessage = deriveExampleMessage(id);
|
|
44
53
|
return `import { testTrail } from '@ontrails/testing';
|
|
45
54
|
import { ${trailName} } from '../src/trails/${moduleName}.js';
|
|
46
55
|
|
|
47
56
|
testTrail(${trailName}, [
|
|
48
|
-
{
|
|
57
|
+
{
|
|
58
|
+
description: ${literal(exampleName)},
|
|
59
|
+
expectValue: { message: ${literal(exampleMessage)} },
|
|
60
|
+
input: {},
|
|
61
|
+
},
|
|
49
62
|
]);
|
|
50
63
|
`;
|
|
51
64
|
};
|
|
@@ -64,14 +77,26 @@ const writeWithDirs = async (
|
|
|
64
77
|
};
|
|
65
78
|
|
|
66
79
|
export const addTrail = trail('add.trail', {
|
|
80
|
+
args: ['id'],
|
|
67
81
|
blaze: async (input, ctx) => {
|
|
68
82
|
const { id } = input;
|
|
69
83
|
const moduleName = id.replaceAll('.', '-');
|
|
70
84
|
const cwd = resolve(ctx.cwd ?? '.');
|
|
71
85
|
|
|
72
86
|
const files = new Map<string, string>([
|
|
73
|
-
[
|
|
74
|
-
|
|
87
|
+
[
|
|
88
|
+
`src/trails/${moduleName}.ts`,
|
|
89
|
+
generateTrailFile(
|
|
90
|
+
id,
|
|
91
|
+
input.description,
|
|
92
|
+
input.exampleName,
|
|
93
|
+
input.intent
|
|
94
|
+
),
|
|
95
|
+
],
|
|
96
|
+
[
|
|
97
|
+
`__tests__/${moduleName}.test.ts`,
|
|
98
|
+
generateTestFile(id, input.exampleName),
|
|
99
|
+
],
|
|
75
100
|
]);
|
|
76
101
|
|
|
77
102
|
for (const [relativePath, content] of files) {
|
|
@@ -82,7 +107,18 @@ export const addTrail = trail('add.trail', {
|
|
|
82
107
|
},
|
|
83
108
|
description: 'Scaffold a new trail with tests and examples',
|
|
84
109
|
input: z.object({
|
|
85
|
-
|
|
110
|
+
description: z
|
|
111
|
+
.string()
|
|
112
|
+
.min(1, 'Trail description is required')
|
|
113
|
+
.describe('Trail description'),
|
|
114
|
+
exampleName: z
|
|
115
|
+
.string()
|
|
116
|
+
.min(1, 'Starter example name is required')
|
|
117
|
+
.describe('Starter example name'),
|
|
118
|
+
id: z
|
|
119
|
+
.string()
|
|
120
|
+
.min(1, 'Trail ID is required')
|
|
121
|
+
.describe('Trail ID (e.g., entity.update)'),
|
|
86
122
|
intent: z
|
|
87
123
|
.enum(['read', 'write', 'destroy'])
|
|
88
124
|
.default('write')
|
package/src/trails/add-verify.ts
CHANGED
|
@@ -8,30 +8,35 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
8
8
|
import { Result, trail } from '@ontrails/core';
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
|
|
11
|
+
import {
|
|
12
|
+
ontrailsPackageRange,
|
|
13
|
+
scaffoldDependencyVersions,
|
|
14
|
+
} from '../versions.js';
|
|
15
|
+
|
|
11
16
|
// ---------------------------------------------------------------------------
|
|
12
17
|
// Content generators
|
|
13
18
|
// ---------------------------------------------------------------------------
|
|
14
19
|
|
|
15
20
|
const generateTestFile = (): string =>
|
|
16
|
-
`import {
|
|
21
|
+
`import { testAllEstablished } from '@ontrails/testing';
|
|
17
22
|
import { app } from '../src/app.js';
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
testAllEstablished(app);
|
|
20
25
|
`;
|
|
21
26
|
|
|
22
27
|
const generateLefthookYml = (): string =>
|
|
23
28
|
`pre-push:
|
|
24
29
|
commands:
|
|
25
30
|
warden:
|
|
26
|
-
run: bunx trails warden
|
|
31
|
+
run: bunx trails warden
|
|
27
32
|
`;
|
|
28
33
|
|
|
29
34
|
/** Add testing and warden devDependencies to package.json when present. */
|
|
30
35
|
const patchVerifyDeps = (pkg: Record<string, unknown>): void => {
|
|
31
36
|
const devDeps = (pkg['devDependencies'] ?? {}) as Record<string, string>;
|
|
32
|
-
devDeps['@ontrails/testing'] =
|
|
33
|
-
devDeps['@ontrails/warden'] =
|
|
34
|
-
devDeps['lefthook'] =
|
|
37
|
+
devDeps['@ontrails/testing'] = ontrailsPackageRange;
|
|
38
|
+
devDeps['@ontrails/warden'] = ontrailsPackageRange;
|
|
39
|
+
devDeps['lefthook'] = scaffoldDependencyVersions.lefthook;
|
|
35
40
|
pkg['devDependencies'] = Object.fromEntries(
|
|
36
41
|
Object.entries(devDeps).toSorted(([a], [b]) => a.localeCompare(b))
|
|
37
42
|
);
|
|
@@ -10,6 +10,11 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
10
10
|
import { Result, trail } from '@ontrails/core';
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
|
|
13
|
+
import {
|
|
14
|
+
ontrailsPackageRange,
|
|
15
|
+
scaffoldDependencyVersions,
|
|
16
|
+
} from '../versions.js';
|
|
17
|
+
|
|
13
18
|
// ---------------------------------------------------------------------------
|
|
14
19
|
// Types
|
|
15
20
|
// ---------------------------------------------------------------------------
|
|
@@ -28,14 +33,22 @@ interface ScaffoldResult {
|
|
|
28
33
|
|
|
29
34
|
const generatePackageJson = (name: string): string => {
|
|
30
35
|
const deps: Record<string, string> = {
|
|
31
|
-
'@ontrails/core':
|
|
32
|
-
zod:
|
|
36
|
+
'@ontrails/core': ontrailsPackageRange,
|
|
37
|
+
zod: scaffoldDependencyVersions.zod,
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
const pkg: Record<string, unknown> = {
|
|
36
41
|
dependencies: Object.fromEntries(
|
|
37
42
|
Object.entries(deps).toSorted(([a], [b]) => a.localeCompare(b))
|
|
38
43
|
),
|
|
44
|
+
devDependencies: Object.fromEntries(
|
|
45
|
+
Object.entries({
|
|
46
|
+
'@types/bun': scaffoldDependencyVersions.bunTypes,
|
|
47
|
+
oxlint: scaffoldDependencyVersions.oxlint,
|
|
48
|
+
typescript: scaffoldDependencyVersions.typescript,
|
|
49
|
+
ultracite: scaffoldDependencyVersions.ultracite,
|
|
50
|
+
}).toSorted(([a], [b]) => a.localeCompare(b))
|
|
51
|
+
),
|
|
39
52
|
name,
|
|
40
53
|
scripts: {
|
|
41
54
|
build: 'tsc -b',
|
|
@@ -73,7 +86,7 @@ const TSCONFIG_CONTENT = JSON.stringify(
|
|
|
73
86
|
const GITIGNORE_CONTENT = `node_modules/
|
|
74
87
|
dist/
|
|
75
88
|
*.tsbuildinfo
|
|
76
|
-
.trails/
|
|
89
|
+
.trails/_surface.json
|
|
77
90
|
`;
|
|
78
91
|
|
|
79
92
|
const OXLINTRC_CONTENT = JSON.stringify(
|
package/src/trails/create.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `create` route -- Create a new Trails project.
|
|
3
3
|
*
|
|
4
|
-
* Composes create.scaffold, add.
|
|
5
|
-
* via ctx.cross
|
|
4
|
+
* Composes create.scaffold, add.surface, and add.verify sub-trails
|
|
5
|
+
* via ctx.cross.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { CrossFn } from '@ontrails/core';
|
|
9
8
|
import { Result, trail } from '@ontrails/core';
|
|
10
9
|
import { z } from 'zod';
|
|
11
10
|
|
|
@@ -14,13 +13,13 @@ import { z } from 'zod';
|
|
|
14
13
|
// ---------------------------------------------------------------------------
|
|
15
14
|
|
|
16
15
|
type Starter = 'empty' | 'entity' | 'hello';
|
|
17
|
-
type
|
|
16
|
+
type Surface = 'cli' | 'http' | 'mcp';
|
|
18
17
|
|
|
19
18
|
interface CreateInput {
|
|
20
19
|
readonly dir?: string | undefined;
|
|
21
20
|
readonly name: string;
|
|
22
21
|
readonly starter: Starter;
|
|
23
|
-
readonly
|
|
22
|
+
readonly surfaces: readonly Surface[];
|
|
24
23
|
readonly verify: boolean;
|
|
25
24
|
}
|
|
26
25
|
|
|
@@ -42,15 +41,20 @@ interface ScaffoldedProject {
|
|
|
42
41
|
readonly name: string;
|
|
43
42
|
}
|
|
44
43
|
|
|
44
|
+
interface SurfaceResult {
|
|
45
|
+
readonly created: string;
|
|
46
|
+
readonly dependency: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
const buildScaffoldInput = (input: ScaffoldRequest) => ({
|
|
46
50
|
...(input.dir === undefined ? {} : { dir: input.dir }),
|
|
47
51
|
name: input.name,
|
|
48
52
|
starter: input.starter,
|
|
49
53
|
});
|
|
50
54
|
|
|
51
|
-
const
|
|
55
|
+
const buildSurfaceInput = (dir: string, surface: string) => ({
|
|
52
56
|
dir,
|
|
53
|
-
|
|
57
|
+
surface,
|
|
54
58
|
});
|
|
55
59
|
|
|
56
60
|
const buildVerifyInput = (input: VerifyRequest) => ({
|
|
@@ -58,24 +62,14 @@ const buildVerifyInput = (input: VerifyRequest) => ({
|
|
|
58
62
|
name: input.name,
|
|
59
63
|
});
|
|
60
64
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
): Promise<Result<ScaffoldedProject, Error>> =>
|
|
65
|
-
cross('create.scaffold', buildScaffoldInput(input));
|
|
66
|
-
|
|
67
|
-
const addTrailheadFiles = async (
|
|
68
|
-
cross: CrossFn,
|
|
69
|
-
dir: string,
|
|
70
|
-
trailheads: readonly string[]
|
|
65
|
+
const collectSurfaceFiles = async (
|
|
66
|
+
surfaces: readonly string[],
|
|
67
|
+
addSurface: (surface: string) => Promise<Result<SurfaceResult, Error>>
|
|
71
68
|
): Promise<Result<string[], Error>> => {
|
|
72
69
|
const created: string[] = [];
|
|
73
70
|
|
|
74
|
-
for (const
|
|
75
|
-
const result = await
|
|
76
|
-
'add.trailhead',
|
|
77
|
-
buildTrailheadInput(dir, trailhead)
|
|
78
|
-
);
|
|
71
|
+
for (const surface of surfaces) {
|
|
72
|
+
const result = await addSurface(surface);
|
|
79
73
|
if (result.isErr()) {
|
|
80
74
|
return Result.err(result.error);
|
|
81
75
|
}
|
|
@@ -86,17 +80,14 @@ const addTrailheadFiles = async (
|
|
|
86
80
|
};
|
|
87
81
|
|
|
88
82
|
const collectVerifyFiles = async (
|
|
89
|
-
|
|
90
|
-
|
|
83
|
+
shouldVerify: boolean,
|
|
84
|
+
addVerify: () => Promise<Result<{ created: string[] }, Error>>
|
|
91
85
|
): Promise<Result<string[], Error>> => {
|
|
92
|
-
if (!
|
|
86
|
+
if (!shouldVerify) {
|
|
93
87
|
return Result.ok([]);
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
const result = await
|
|
97
|
-
'add.verify',
|
|
98
|
-
buildVerifyInput(input)
|
|
99
|
-
);
|
|
90
|
+
const result = await addVerify();
|
|
100
91
|
return result.isErr()
|
|
101
92
|
? Result.err(result.error)
|
|
102
93
|
: Result.ok(result.value.created);
|
|
@@ -104,43 +95,9 @@ const collectVerifyFiles = async (
|
|
|
104
95
|
|
|
105
96
|
const collectCreatedFiles = (
|
|
106
97
|
scaffolded: readonly string[],
|
|
107
|
-
|
|
98
|
+
surfaces: readonly string[],
|
|
108
99
|
verify: readonly string[]
|
|
109
|
-
): string[] => [...scaffolded, ...
|
|
110
|
-
|
|
111
|
-
const runCreate = async (
|
|
112
|
-
cross: CrossFn,
|
|
113
|
-
input: CreateInput
|
|
114
|
-
): Promise<Result<{ created: string[]; dir: string; name: string }, Error>> => {
|
|
115
|
-
const scaffolded = await scaffoldProject(cross, input);
|
|
116
|
-
if (scaffolded.isErr()) {
|
|
117
|
-
return Result.err(scaffolded.error);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const trailheadResults = await addTrailheadFiles(
|
|
121
|
-
cross,
|
|
122
|
-
scaffolded.value.dir,
|
|
123
|
-
input.trailheads
|
|
124
|
-
);
|
|
125
|
-
if (trailheadResults.isErr()) {
|
|
126
|
-
return Result.err(trailheadResults.error);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const verifyFiles = await collectVerifyFiles(cross, input);
|
|
130
|
-
if (verifyFiles.isErr()) {
|
|
131
|
-
return Result.err(verifyFiles.error);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return Result.ok({
|
|
135
|
-
created: collectCreatedFiles(
|
|
136
|
-
scaffolded.value.created,
|
|
137
|
-
trailheadResults.value,
|
|
138
|
-
verifyFiles.value
|
|
139
|
-
),
|
|
140
|
-
dir: scaffolded.value.dir,
|
|
141
|
-
name: input.name,
|
|
142
|
-
});
|
|
143
|
-
};
|
|
100
|
+
): string[] => [...scaffolded, ...surfaces, ...verify];
|
|
144
101
|
|
|
145
102
|
// ---------------------------------------------------------------------------
|
|
146
103
|
// Route definition
|
|
@@ -151,9 +108,52 @@ export const createRoute = trail('create', {
|
|
|
151
108
|
if (!ctx.cross) {
|
|
152
109
|
return Result.err(new Error('create route requires ctx.cross'));
|
|
153
110
|
}
|
|
154
|
-
|
|
111
|
+
const { cross } = ctx;
|
|
112
|
+
|
|
113
|
+
const scaffolded = await cross<ScaffoldedProject>(
|
|
114
|
+
'create.scaffold',
|
|
115
|
+
buildScaffoldInput(input)
|
|
116
|
+
);
|
|
117
|
+
if (scaffolded.isErr()) {
|
|
118
|
+
return Result.err(scaffolded.error);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const finishCreate = async (): Promise<
|
|
122
|
+
Result<{ created: string[]; dir: string; name: string }, Error>
|
|
123
|
+
> => {
|
|
124
|
+
const surfaceFiles = await collectSurfaceFiles(
|
|
125
|
+
input.surfaces,
|
|
126
|
+
(surface) =>
|
|
127
|
+
cross<SurfaceResult>(
|
|
128
|
+
'add.surface',
|
|
129
|
+
buildSurfaceInput(scaffolded.value.dir, surface)
|
|
130
|
+
)
|
|
131
|
+
);
|
|
132
|
+
if (surfaceFiles.isErr()) {
|
|
133
|
+
return Result.err(surfaceFiles.error);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const verifyFiles = await collectVerifyFiles(input.verify, () =>
|
|
137
|
+
cross<{ created: string[] }>('add.verify', buildVerifyInput(input))
|
|
138
|
+
);
|
|
139
|
+
if (verifyFiles.isErr()) {
|
|
140
|
+
return Result.err(verifyFiles.error);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return Result.ok({
|
|
144
|
+
created: collectCreatedFiles(
|
|
145
|
+
scaffolded.value.created,
|
|
146
|
+
surfaceFiles.value,
|
|
147
|
+
verifyFiles.value
|
|
148
|
+
),
|
|
149
|
+
dir: scaffolded.value.dir,
|
|
150
|
+
name: input.name,
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return finishCreate();
|
|
155
155
|
},
|
|
156
|
-
crosses: ['create.scaffold', 'add.
|
|
156
|
+
crosses: ['create.scaffold', 'add.surface', 'add.verify'],
|
|
157
157
|
description: 'Create a new Trails project',
|
|
158
158
|
fields: {
|
|
159
159
|
starter: {
|
|
@@ -171,7 +171,7 @@ export const createRoute = trail('create', {
|
|
|
171
171
|
{ hint: 'Just the structure', label: 'Empty', value: 'empty' },
|
|
172
172
|
],
|
|
173
173
|
},
|
|
174
|
-
|
|
174
|
+
surfaces: {
|
|
175
175
|
options: [
|
|
176
176
|
{ hint: 'Commander-based command line', label: 'CLI', value: 'cli' },
|
|
177
177
|
{
|
|
@@ -179,6 +179,11 @@ export const createRoute = trail('create', {
|
|
|
179
179
|
label: 'MCP',
|
|
180
180
|
value: 'mcp',
|
|
181
181
|
},
|
|
182
|
+
{
|
|
183
|
+
hint: 'Hono-powered HTTP endpoints',
|
|
184
|
+
label: 'HTTP',
|
|
185
|
+
value: 'http',
|
|
186
|
+
},
|
|
182
187
|
],
|
|
183
188
|
},
|
|
184
189
|
},
|
|
@@ -189,10 +194,10 @@ export const createRoute = trail('create', {
|
|
|
189
194
|
.enum(['hello', 'entity', 'empty'])
|
|
190
195
|
.default('hello')
|
|
191
196
|
.describe('Starter trail'),
|
|
192
|
-
|
|
193
|
-
.array(z.enum(['cli', 'mcp']))
|
|
197
|
+
surfaces: z
|
|
198
|
+
.array(z.enum(['cli', 'http', 'mcp']))
|
|
194
199
|
.default(['cli'])
|
|
195
|
-
.describe('
|
|
200
|
+
.describe('Surfaces'),
|
|
196
201
|
verify: z.boolean().default(true).describe('Include testing + warden'),
|
|
197
202
|
}),
|
|
198
203
|
output: z.object({
|
package/src/trails/dev-clean.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { Result, ValidationError, trail } from '@ontrails/core';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
cleanDevState,
|
|
6
|
+
DEFAULT_TOPO_SNAPSHOT_RETENTION,
|
|
7
|
+
} from './dev-support.js';
|
|
8
|
+
import { createIsolatedExampleInput } from './topo-support.js';
|
|
6
9
|
|
|
7
10
|
export const devCleanTrail = trail('dev.clean', {
|
|
8
11
|
blaze: (input, ctx) => {
|
|
@@ -18,19 +21,19 @@ export const devCleanTrail = trail('dev.clean', {
|
|
|
18
21
|
return Result.ok(
|
|
19
22
|
cleanDevState({
|
|
20
23
|
dryRun: input.dryRun,
|
|
21
|
-
maxAge: input.
|
|
22
|
-
maxRecords: input.
|
|
24
|
+
maxAge: input.traceAgeMs,
|
|
25
|
+
maxRecords: input.traces,
|
|
23
26
|
rootDir,
|
|
24
|
-
|
|
27
|
+
snapshotRetention: input.snapshots,
|
|
25
28
|
})
|
|
26
29
|
);
|
|
27
30
|
},
|
|
28
|
-
description: 'Prune unpinned topo
|
|
31
|
+
description: 'Prune unpinned topo snapshots and old trace records',
|
|
29
32
|
examples: [
|
|
30
33
|
{
|
|
31
34
|
input: {
|
|
32
35
|
dryRun: true,
|
|
33
|
-
rootDir:
|
|
36
|
+
rootDir: createIsolatedExampleInput('dev-clean').rootDir,
|
|
34
37
|
},
|
|
35
38
|
name: 'Preview local cleanup',
|
|
36
39
|
},
|
|
@@ -41,33 +44,34 @@ export const devCleanTrail = trail('dev.clean', {
|
|
|
41
44
|
.default(true)
|
|
42
45
|
.describe('Preview cleanup without changing state'),
|
|
43
46
|
rootDir: z.string().optional().describe('Workspace root directory'),
|
|
44
|
-
|
|
47
|
+
snapshots: z
|
|
45
48
|
.number()
|
|
46
|
-
.default(
|
|
47
|
-
.describe('Unpinned topo
|
|
48
|
-
|
|
49
|
+
.default(DEFAULT_TOPO_SNAPSHOT_RETENTION)
|
|
50
|
+
.describe('Unpinned topo snapshots to retain'),
|
|
51
|
+
traceAgeMs: z
|
|
49
52
|
.number()
|
|
50
53
|
.default(7 * 24 * 60 * 60 * 1000)
|
|
51
|
-
.describe('Maximum retained
|
|
52
|
-
|
|
54
|
+
.describe('Maximum retained trace age in milliseconds'),
|
|
55
|
+
traces: z.number().default(10_000).describe('Maximum retained trace count'),
|
|
53
56
|
yes: z.boolean().default(false).describe('Confirm destructive changes'),
|
|
54
57
|
}),
|
|
55
58
|
intent: 'destroy',
|
|
56
59
|
output: z.object({
|
|
57
60
|
dryRun: z.boolean(),
|
|
58
61
|
remaining: z.object({
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
pinnedCount: z.number(),
|
|
63
|
+
snapshotCount: z.number(),
|
|
64
|
+
traceCount: z.number(),
|
|
62
65
|
}),
|
|
63
66
|
removed: z.object({
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
topoSnapshots: z.number(),
|
|
68
|
+
traceRecords: z.number(),
|
|
66
69
|
}),
|
|
67
70
|
retention: z.object({
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
snapshots: z.number(),
|
|
72
|
+
traceAgeMs: z.number(),
|
|
73
|
+
traces: z.number(),
|
|
71
74
|
}),
|
|
72
75
|
}),
|
|
76
|
+
permit: { scopes: ['dev:clean'] },
|
|
73
77
|
});
|