@ontrails/trails 1.0.0-beta.2 → 1.0.0-beta.22
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 +647 -0
- package/README.md +26 -0
- package/package.json +28 -7
- package/src/app.ts +86 -2
- package/src/clack.ts +22 -0
- package/src/cli.ts +330 -11
- package/src/completions.ts +240 -0
- package/src/lifecycle-source-io.ts +33 -0
- package/src/load-app-mirror.ts +202 -0
- package/src/local-state-io.ts +153 -0
- package/src/mcp-app.ts +30 -0
- package/src/mcp-options.ts +77 -0
- package/src/mcp.ts +8 -0
- package/src/project-writes.ts +377 -0
- package/src/release/bindings.ts +39 -0
- package/src/release/check.ts +818 -0
- package/src/release/config.ts +63 -0
- package/src/release/contract-facts.ts +425 -0
- package/src/release/index.ts +85 -0
- package/src/release/native-bun-publish.ts +651 -0
- package/src/release/native-bun-registry.ts +350 -0
- package/src/release/packed-artifacts-smoke.ts +236 -0
- package/src/release/smoke.ts +46 -0
- package/src/release/wayfinder-dogfood-smoke.ts +226 -0
- package/src/retired-topo-command.ts +36 -0
- package/src/run-adapter-check.ts +76 -0
- package/src/run-collision.ts +126 -0
- package/src/run-completions-install.ts +179 -0
- package/src/run-example.ts +149 -0
- package/src/run-examples.ts +148 -0
- package/src/run-quiet.ts +75 -0
- package/src/run-release-check.ts +74 -0
- package/src/run-trace.ts +273 -0
- package/src/run-warden.ts +39 -0
- package/src/run-watch.ts +432 -0
- package/src/scaffold-version-sync.ts +183 -0
- package/src/scaffold-versions.generated.ts +12 -0
- package/src/trails/adapter-check.ts +244 -0
- package/src/trails/add-surface.ts +94 -40
- package/src/trails/add-trail.ts +79 -41
- package/src/trails/add-verify.ts +95 -25
- package/src/trails/compile.ts +67 -0
- package/src/trails/completions-complete.ts +165 -0
- package/src/trails/completions.ts +47 -0
- package/src/trails/create-adapter.ts +1084 -0
- package/src/trails/create-scaffold.ts +399 -104
- package/src/trails/create-versions.ts +62 -0
- package/src/trails/create.ts +185 -71
- package/src/trails/deprecate.ts +59 -0
- package/src/trails/dev-clean.ts +82 -0
- package/src/trails/dev-reset.ts +50 -0
- package/src/trails/dev-stats.ts +72 -0
- package/src/trails/dev-support.ts +340 -0
- package/src/trails/doctor.ts +56 -0
- package/src/trails/draft-promote.ts +949 -0
- package/src/trails/guide.ts +74 -68
- package/src/trails/load-app.ts +1143 -15
- package/src/trails/project.ts +17 -3
- package/src/trails/release-check.ts +104 -0
- package/src/trails/release-smoke.ts +48 -0
- package/src/trails/revise.ts +53 -0
- package/src/trails/root-dir.ts +21 -0
- package/src/trails/run-example.ts +491 -0
- package/src/trails/run-examples.ts +145 -0
- package/src/trails/run.ts +410 -0
- package/src/trails/scaffold-json.ts +58 -0
- package/src/trails/survey.ts +881 -226
- package/src/trails/topo-activation.ts +385 -0
- package/src/trails/topo-constants.ts +2 -0
- package/src/trails/topo-history.ts +47 -0
- package/src/trails/topo-output-schemas.ts +248 -0
- package/src/trails/topo-pin.ts +52 -0
- package/src/trails/topo-read-support.ts +313 -0
- package/src/trails/topo-reports.ts +807 -0
- package/src/trails/topo-store-support.ts +174 -0
- package/src/trails/topo-support.ts +220 -0
- package/src/trails/topo-unpin.ts +61 -0
- package/src/trails/topo.ts +106 -0
- package/src/trails/validate.ts +38 -0
- package/src/trails/version-lifecycle-support.ts +945 -0
- package/src/trails/warden-guide.ts +129 -0
- package/src/trails/warden.ts +165 -58
- package/src/versions.ts +31 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/__tests__/examples.test.ts +0 -6
- package/dist/bin/trails.d.ts +0 -3
- package/dist/bin/trails.d.ts.map +0 -1
- package/dist/bin/trails.js +0 -4
- package/dist/bin/trails.js.map +0 -1
- package/dist/src/app.d.ts +0 -2
- package/dist/src/app.d.ts.map +0 -1
- package/dist/src/app.js +0 -11
- package/dist/src/app.js.map +0 -1
- package/dist/src/clack.d.ts +0 -9
- package/dist/src/clack.d.ts.map +0 -1
- package/dist/src/clack.js +0 -62
- package/dist/src/clack.js.map +0 -1
- package/dist/src/cli.d.ts +0 -2
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -13
- package/dist/src/cli.js.map +0 -1
- package/dist/src/trails/add-surface.d.ts +0 -13
- package/dist/src/trails/add-surface.d.ts.map +0 -1
- package/dist/src/trails/add-surface.js +0 -88
- package/dist/src/trails/add-surface.js.map +0 -1
- package/dist/src/trails/add-trail.d.ts +0 -11
- package/dist/src/trails/add-trail.d.ts.map +0 -1
- package/dist/src/trails/add-trail.js +0 -85
- package/dist/src/trails/add-trail.js.map +0 -1
- package/dist/src/trails/add-verify.d.ts +0 -10
- package/dist/src/trails/add-verify.d.ts.map +0 -1
- package/dist/src/trails/add-verify.js +0 -67
- package/dist/src/trails/add-verify.js.map +0 -1
- package/dist/src/trails/create-scaffold.d.ts +0 -15
- package/dist/src/trails/create-scaffold.d.ts.map +0 -1
- package/dist/src/trails/create-scaffold.js +0 -288
- package/dist/src/trails/create-scaffold.js.map +0 -1
- package/dist/src/trails/create.d.ts +0 -22
- package/dist/src/trails/create.d.ts.map +0 -1
- package/dist/src/trails/create.js +0 -121
- package/dist/src/trails/create.js.map +0 -1
- package/dist/src/trails/guide.d.ts +0 -11
- package/dist/src/trails/guide.d.ts.map +0 -1
- package/dist/src/trails/guide.js +0 -80
- package/dist/src/trails/guide.js.map +0 -1
- package/dist/src/trails/load-app.d.ts +0 -4
- package/dist/src/trails/load-app.d.ts.map +0 -1
- package/dist/src/trails/load-app.js +0 -24
- package/dist/src/trails/load-app.js.map +0 -1
- package/dist/src/trails/project.d.ts +0 -8
- package/dist/src/trails/project.d.ts.map +0 -1
- package/dist/src/trails/project.js +0 -43
- package/dist/src/trails/project.js.map +0 -1
- package/dist/src/trails/survey.d.ts +0 -33
- package/dist/src/trails/survey.d.ts.map +0 -1
- package/dist/src/trails/survey.js +0 -225
- package/dist/src/trails/survey.js.map +0 -1
- package/dist/src/trails/warden.d.ts +0 -19
- package/dist/src/trails/warden.d.ts.map +0 -1
- package/dist/src/trails/warden.js +0 -88
- package/dist/src/trails/warden.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/__tests__/create.test.ts +0 -349
- package/src/__tests__/guide.test.ts +0 -91
- package/src/__tests__/load-app.test.ts +0 -15
- package/src/__tests__/survey.test.ts +0 -161
- package/src/__tests__/warden.test.ts +0 -74
- package/tsconfig.json +0 -9
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { CreateServerOptions, McpSurfaceFacetMap } from '@ontrails/mcp';
|
|
2
|
+
|
|
3
|
+
export const trailsMcpIncludedTrails = [
|
|
4
|
+
'adapter.check',
|
|
5
|
+
'add.surface',
|
|
6
|
+
'add.trail',
|
|
7
|
+
'compile',
|
|
8
|
+
'create',
|
|
9
|
+
'create.adapter',
|
|
10
|
+
'deprecate',
|
|
11
|
+
'dev.clean',
|
|
12
|
+
'dev.reset',
|
|
13
|
+
'dev.stats',
|
|
14
|
+
'diff',
|
|
15
|
+
'doctor',
|
|
16
|
+
'draft.promote',
|
|
17
|
+
'guide',
|
|
18
|
+
'release.check',
|
|
19
|
+
'release.smoke',
|
|
20
|
+
'revise',
|
|
21
|
+
'run',
|
|
22
|
+
'run.example',
|
|
23
|
+
'run.examples',
|
|
24
|
+
'survey',
|
|
25
|
+
'survey.brief',
|
|
26
|
+
'survey.diff',
|
|
27
|
+
'survey.resource',
|
|
28
|
+
'survey.signal',
|
|
29
|
+
'survey.surfaces',
|
|
30
|
+
'survey.trail',
|
|
31
|
+
'topo',
|
|
32
|
+
'topo.history',
|
|
33
|
+
'topo.pin',
|
|
34
|
+
'topo.unpin',
|
|
35
|
+
'validate',
|
|
36
|
+
'warden',
|
|
37
|
+
'warden.guide',
|
|
38
|
+
'wayfind.adapters',
|
|
39
|
+
'wayfind.contract',
|
|
40
|
+
'wayfind.errors',
|
|
41
|
+
'wayfind.examples',
|
|
42
|
+
'wayfind.impact',
|
|
43
|
+
'wayfind.nearby',
|
|
44
|
+
'wayfind.overview',
|
|
45
|
+
'wayfind.search',
|
|
46
|
+
'wayfind.trails',
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
export const trailsMcpFacets = {
|
|
50
|
+
inspect: {
|
|
51
|
+
description:
|
|
52
|
+
'Inspect saved topo structure, resources, signals, surfaces, and diffs.',
|
|
53
|
+
mcp: { loading: 'deferred' },
|
|
54
|
+
trails: [
|
|
55
|
+
'survey',
|
|
56
|
+
'diff',
|
|
57
|
+
'topo',
|
|
58
|
+
'guide',
|
|
59
|
+
'survey.brief',
|
|
60
|
+
'survey.diff',
|
|
61
|
+
'survey.resource',
|
|
62
|
+
'survey.signal',
|
|
63
|
+
'survey.surfaces',
|
|
64
|
+
'survey.trail',
|
|
65
|
+
'topo.history',
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
} satisfies McpSurfaceFacetMap;
|
|
69
|
+
|
|
70
|
+
export const trailsMcpSurfaceOptions = {
|
|
71
|
+
description:
|
|
72
|
+
'Trails framework operator surface. Use MCP resources for cold context, direct tools for high-signal work, and the inspect facet for saved topo reads.',
|
|
73
|
+
facets: trailsMcpFacets,
|
|
74
|
+
include: trailsMcpIncludedTrails,
|
|
75
|
+
mcpResources: { examples: true, surfaceMap: true },
|
|
76
|
+
name: 'trails',
|
|
77
|
+
} satisfies CreateServerOptions;
|
package/src/mcp.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/* oxlint-disable eslint-plugin-jest/require-hook -- MCP stdio entrypoints execute at module scope */
|
|
3
|
+
import { surface } from '@ontrails/mcp';
|
|
4
|
+
|
|
5
|
+
import { trailsMcpApp } from './mcp-app.js';
|
|
6
|
+
import { trailsMcpSurfaceOptions } from './mcp-options.js';
|
|
7
|
+
|
|
8
|
+
await surface(trailsMcpApp, trailsMcpSurfaceOptions);
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, renameSync } from 'node:fs';
|
|
2
|
+
import { dirname, relative, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DRAFT_ID_PREFIX,
|
|
6
|
+
deriveSafePath,
|
|
7
|
+
InternalError,
|
|
8
|
+
Result,
|
|
9
|
+
ValidationError,
|
|
10
|
+
} from '@ontrails/core';
|
|
11
|
+
import type { Result as TrailsResult } from '@ontrails/core';
|
|
12
|
+
|
|
13
|
+
export const PROJECT_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
14
|
+
export const PROJECT_NAME_MESSAGE =
|
|
15
|
+
'Project name must start with a lowercase letter or digit and contain only lowercase letters, digits, ".", "_", or "-".';
|
|
16
|
+
|
|
17
|
+
export const TRAIL_ID_PATTERN =
|
|
18
|
+
/^(?:_draft\.)?[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)*$/u;
|
|
19
|
+
export const TRAIL_ID_MESSAGE =
|
|
20
|
+
'Trail ID must be lowercase dotted segments, optionally prefixed with "_draft.", with each non-draft segment starting with a letter and containing only letters or digits.';
|
|
21
|
+
|
|
22
|
+
const asError = (error: unknown): Error =>
|
|
23
|
+
error instanceof Error ? error : new Error(String(error));
|
|
24
|
+
|
|
25
|
+
export type PlannedProjectOperation =
|
|
26
|
+
| { readonly kind: 'mkdir'; readonly path: string }
|
|
27
|
+
| { readonly kind: 'rename'; readonly from: string; readonly to: string }
|
|
28
|
+
| { readonly kind: 'write'; readonly path: string };
|
|
29
|
+
|
|
30
|
+
export type ProjectWriteOperation =
|
|
31
|
+
| { readonly kind: 'mkdir'; readonly path: string }
|
|
32
|
+
| { readonly kind: 'rename'; readonly from: string; readonly to: string }
|
|
33
|
+
| {
|
|
34
|
+
readonly content: string | Uint8Array;
|
|
35
|
+
readonly kind: 'write';
|
|
36
|
+
readonly path: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
interface ProjectOperationOptions {
|
|
40
|
+
readonly existing?: 'overwrite' | 'preserve';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const validateProjectName = (
|
|
44
|
+
name: string
|
|
45
|
+
): TrailsResult<string, ValidationError> =>
|
|
46
|
+
PROJECT_NAME_PATTERN.test(name)
|
|
47
|
+
? Result.ok(name)
|
|
48
|
+
: Result.err(new ValidationError(PROJECT_NAME_MESSAGE));
|
|
49
|
+
|
|
50
|
+
export const validateTrailId = (
|
|
51
|
+
trailId: string
|
|
52
|
+
): TrailsResult<string, ValidationError> =>
|
|
53
|
+
TRAIL_ID_PATTERN.test(trailId)
|
|
54
|
+
? Result.ok(trailId)
|
|
55
|
+
: Result.err(new ValidationError(TRAIL_ID_MESSAGE));
|
|
56
|
+
|
|
57
|
+
export const trailIdToModuleName = (trailId: string): string =>
|
|
58
|
+
trailId.startsWith(DRAFT_ID_PREFIX)
|
|
59
|
+
? `${DRAFT_ID_PREFIX}${trailId.slice(DRAFT_ID_PREFIX.length).replaceAll('.', '-')}`
|
|
60
|
+
: trailId.replaceAll('.', '-');
|
|
61
|
+
|
|
62
|
+
export const trailIdToExportName = (trailId: string): string =>
|
|
63
|
+
trailId.replaceAll('.', '_');
|
|
64
|
+
|
|
65
|
+
export const resolveProjectDir = (
|
|
66
|
+
parentDir: string,
|
|
67
|
+
projectName: string
|
|
68
|
+
): TrailsResult<string, Error> => {
|
|
69
|
+
const validated = validateProjectName(projectName);
|
|
70
|
+
if (validated.isErr()) {
|
|
71
|
+
return validated;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return deriveSafePath(resolve(parentDir), validated.value);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const resolveProjectPath = (
|
|
78
|
+
projectDir: string,
|
|
79
|
+
relativePath: string
|
|
80
|
+
): TrailsResult<string, Error> => deriveSafePath(projectDir, relativePath);
|
|
81
|
+
|
|
82
|
+
export const projectPathExists = (
|
|
83
|
+
projectDir: string,
|
|
84
|
+
pathWithinProject: string
|
|
85
|
+
): TrailsResult<boolean, Error> => {
|
|
86
|
+
const target = resolveProjectPath(projectDir, pathWithinProject);
|
|
87
|
+
if (target.isErr()) {
|
|
88
|
+
return target;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Result.ok(existsSync(target.value));
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/** Write a generated project-relative file and return the relative path. */
|
|
95
|
+
export const writeProjectFile = async (
|
|
96
|
+
projectDir: string,
|
|
97
|
+
relativePath: string,
|
|
98
|
+
content: string | Uint8Array
|
|
99
|
+
): Promise<TrailsResult<string, Error>> => {
|
|
100
|
+
const target = resolveProjectPath(projectDir, relativePath);
|
|
101
|
+
if (target.isErr()) {
|
|
102
|
+
return target;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
mkdirSync(dirname(target.value), { recursive: true });
|
|
107
|
+
await Bun.write(target.value, content);
|
|
108
|
+
return Result.ok(relativePath);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return Result.err(
|
|
111
|
+
new InternalError(`Failed to write project file "${relativePath}"`, {
|
|
112
|
+
cause: asError(error),
|
|
113
|
+
context: { projectDir, relativePath },
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** Write an already-derived path that must stay contained under the project. */
|
|
120
|
+
export const writeContainedProjectPath = async (
|
|
121
|
+
projectDir: string,
|
|
122
|
+
pathWithinProject: string,
|
|
123
|
+
content: string | Uint8Array
|
|
124
|
+
): Promise<TrailsResult<string, Error>> => {
|
|
125
|
+
const target = resolveProjectPath(projectDir, pathWithinProject);
|
|
126
|
+
if (target.isErr()) {
|
|
127
|
+
return target;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
mkdirSync(dirname(target.value), { recursive: true });
|
|
132
|
+
await Bun.write(target.value, content);
|
|
133
|
+
return Result.ok(target.value);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
return Result.err(
|
|
136
|
+
new InternalError(
|
|
137
|
+
`Failed to write contained project path "${pathWithinProject}"`,
|
|
138
|
+
{
|
|
139
|
+
cause: asError(error),
|
|
140
|
+
context: { pathWithinProject, projectDir },
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export const renameContainedProjectPath = (
|
|
148
|
+
projectDir: string,
|
|
149
|
+
fromPath: string,
|
|
150
|
+
toPath: string
|
|
151
|
+
): TrailsResult<void, Error> => {
|
|
152
|
+
const from = resolveProjectPath(projectDir, fromPath);
|
|
153
|
+
if (from.isErr()) {
|
|
154
|
+
return Result.err(from.error);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const to = resolveProjectPath(projectDir, toPath);
|
|
158
|
+
if (to.isErr()) {
|
|
159
|
+
return Result.err(to.error);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
renameSync(from.value, to.value);
|
|
164
|
+
return Result.ok();
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return Result.err(
|
|
167
|
+
new InternalError(
|
|
168
|
+
`Failed to rename contained project path "${fromPath}"`,
|
|
169
|
+
{
|
|
170
|
+
cause: asError(error),
|
|
171
|
+
context: { fromPath, projectDir, toPath },
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const toProjectRelativePath = (
|
|
179
|
+
projectDir: string,
|
|
180
|
+
pathWithinProject: string
|
|
181
|
+
): TrailsResult<string, Error> => {
|
|
182
|
+
const target = resolveProjectPath(projectDir, pathWithinProject);
|
|
183
|
+
if (target.isErr()) {
|
|
184
|
+
return target;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return Result.ok(
|
|
188
|
+
relative(resolve(projectDir), target.value).replaceAll('\\', '/')
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const planProjectOperation = (
|
|
193
|
+
projectDir: string,
|
|
194
|
+
operation: ProjectWriteOperation
|
|
195
|
+
): TrailsResult<PlannedProjectOperation, Error> => {
|
|
196
|
+
switch (operation.kind) {
|
|
197
|
+
case 'mkdir': {
|
|
198
|
+
const path = toProjectRelativePath(projectDir, operation.path);
|
|
199
|
+
return path.isErr()
|
|
200
|
+
? path
|
|
201
|
+
: Result.ok({ kind: 'mkdir', path: path.value });
|
|
202
|
+
}
|
|
203
|
+
case 'rename': {
|
|
204
|
+
const from = toProjectRelativePath(projectDir, operation.from);
|
|
205
|
+
if (from.isErr()) {
|
|
206
|
+
return from;
|
|
207
|
+
}
|
|
208
|
+
const to = toProjectRelativePath(projectDir, operation.to);
|
|
209
|
+
return to.isErr()
|
|
210
|
+
? to
|
|
211
|
+
: Result.ok({ from: from.value, kind: 'rename', to: to.value });
|
|
212
|
+
}
|
|
213
|
+
case 'write': {
|
|
214
|
+
const path = toProjectRelativePath(projectDir, operation.path);
|
|
215
|
+
return path.isErr()
|
|
216
|
+
? path
|
|
217
|
+
: Result.ok({ kind: 'write', path: path.value });
|
|
218
|
+
}
|
|
219
|
+
default: {
|
|
220
|
+
return Result.err(
|
|
221
|
+
new InternalError('Unknown project operation kind', {
|
|
222
|
+
context: { operation },
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const shouldApplyProjectOperation = (
|
|
230
|
+
projectDir: string,
|
|
231
|
+
operation: ProjectWriteOperation,
|
|
232
|
+
options: ProjectOperationOptions
|
|
233
|
+
): TrailsResult<boolean, Error> => {
|
|
234
|
+
if (options.existing !== 'preserve' || operation.kind === 'rename') {
|
|
235
|
+
return Result.ok(true);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { path } = operation;
|
|
239
|
+
const target = resolveProjectPath(projectDir, path);
|
|
240
|
+
if (target.isErr()) {
|
|
241
|
+
return Result.err(target.error);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return Result.ok(!existsSync(target.value));
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const selectProjectOperations = (
|
|
248
|
+
projectDir: string,
|
|
249
|
+
operations: readonly ProjectWriteOperation[],
|
|
250
|
+
options: ProjectOperationOptions
|
|
251
|
+
): TrailsResult<ProjectWriteOperation[], Error> => {
|
|
252
|
+
const selected: ProjectWriteOperation[] = [];
|
|
253
|
+
for (const operation of operations) {
|
|
254
|
+
const shouldApply = shouldApplyProjectOperation(
|
|
255
|
+
projectDir,
|
|
256
|
+
operation,
|
|
257
|
+
options
|
|
258
|
+
);
|
|
259
|
+
if (shouldApply.isErr()) {
|
|
260
|
+
return Result.err(shouldApply.error);
|
|
261
|
+
}
|
|
262
|
+
if (shouldApply.value) {
|
|
263
|
+
selected.push(operation);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return Result.ok(selected);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
export const planProjectOperations = (
|
|
271
|
+
projectDir: string,
|
|
272
|
+
operations: readonly ProjectWriteOperation[],
|
|
273
|
+
options: ProjectOperationOptions = {}
|
|
274
|
+
): TrailsResult<PlannedProjectOperation[], Error> => {
|
|
275
|
+
const selected = selectProjectOperations(projectDir, operations, options);
|
|
276
|
+
if (selected.isErr()) {
|
|
277
|
+
return Result.err(selected.error);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const planned: PlannedProjectOperation[] = [];
|
|
281
|
+
for (const operation of selected.value) {
|
|
282
|
+
const result = planProjectOperation(projectDir, operation);
|
|
283
|
+
if (result.isErr()) {
|
|
284
|
+
return Result.err(result.error);
|
|
285
|
+
}
|
|
286
|
+
planned.push(result.value);
|
|
287
|
+
}
|
|
288
|
+
return Result.ok(planned);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const applyProjectOperation = async (
|
|
292
|
+
projectDir: string,
|
|
293
|
+
operation: ProjectWriteOperation
|
|
294
|
+
): Promise<TrailsResult<void, Error>> => {
|
|
295
|
+
switch (operation.kind) {
|
|
296
|
+
case 'mkdir': {
|
|
297
|
+
const target = resolveProjectPath(projectDir, operation.path);
|
|
298
|
+
if (target.isErr()) {
|
|
299
|
+
return Result.err(target.error);
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
mkdirSync(target.value, { recursive: true });
|
|
303
|
+
return Result.ok();
|
|
304
|
+
} catch (error) {
|
|
305
|
+
return Result.err(
|
|
306
|
+
new InternalError(
|
|
307
|
+
`Failed to create project directory "${operation.path}"`,
|
|
308
|
+
{
|
|
309
|
+
cause: asError(error),
|
|
310
|
+
context: { projectDir, relativePath: operation.path },
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
case 'rename': {
|
|
317
|
+
return renameContainedProjectPath(
|
|
318
|
+
projectDir,
|
|
319
|
+
operation.from,
|
|
320
|
+
operation.to
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
case 'write': {
|
|
324
|
+
const target = resolveProjectPath(projectDir, operation.path);
|
|
325
|
+
if (target.isErr()) {
|
|
326
|
+
return Result.err(target.error);
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
mkdirSync(dirname(target.value), { recursive: true });
|
|
330
|
+
await Bun.write(target.value, operation.content);
|
|
331
|
+
return Result.ok();
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return Result.err(
|
|
334
|
+
new InternalError(
|
|
335
|
+
`Failed to write project file "${operation.path}"`,
|
|
336
|
+
{
|
|
337
|
+
cause: asError(error),
|
|
338
|
+
context: { projectDir, relativePath: operation.path },
|
|
339
|
+
}
|
|
340
|
+
)
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
default: {
|
|
345
|
+
return Result.err(
|
|
346
|
+
new InternalError('Unknown project operation kind', {
|
|
347
|
+
context: { operation },
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
export const applyProjectOperations = async (
|
|
355
|
+
projectDir: string,
|
|
356
|
+
operations: readonly ProjectWriteOperation[],
|
|
357
|
+
options: ProjectOperationOptions = {}
|
|
358
|
+
): Promise<TrailsResult<PlannedProjectOperation[], Error>> => {
|
|
359
|
+
const selected = selectProjectOperations(projectDir, operations, options);
|
|
360
|
+
if (selected.isErr()) {
|
|
361
|
+
return Result.err(selected.error);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const planned = planProjectOperations(projectDir, selected.value);
|
|
365
|
+
if (planned.isErr()) {
|
|
366
|
+
return Result.err(planned.error);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for (const operation of selected.value) {
|
|
370
|
+
const applied = await applyProjectOperation(projectDir, operation);
|
|
371
|
+
if (applied.isErr()) {
|
|
372
|
+
return Result.err(applied.error);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return planned;
|
|
377
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const releaseBindingKindValues = ['native', 'adapter'] as const;
|
|
2
|
+
export type ReleaseBindingKind = (typeof releaseBindingKindValues)[number];
|
|
3
|
+
|
|
4
|
+
export const releaseBindingPlacementValues = [
|
|
5
|
+
'same-package',
|
|
6
|
+
'subpath',
|
|
7
|
+
'extracted',
|
|
8
|
+
] as const;
|
|
9
|
+
export type ReleaseBindingPlacement =
|
|
10
|
+
(typeof releaseBindingPlacementValues)[number];
|
|
11
|
+
|
|
12
|
+
export const releaseBindingCapabilityValues = [
|
|
13
|
+
'pack-check',
|
|
14
|
+
'publish',
|
|
15
|
+
'registry-preflight',
|
|
16
|
+
] as const;
|
|
17
|
+
export type ReleaseBindingCapability =
|
|
18
|
+
(typeof releaseBindingCapabilityValues)[number];
|
|
19
|
+
|
|
20
|
+
export interface ReleaseBindingDescriptor {
|
|
21
|
+
readonly boundary: 'foreign' | 'trails-owned';
|
|
22
|
+
readonly capabilities: readonly ReleaseBindingCapability[];
|
|
23
|
+
readonly description: string;
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly kind: ReleaseBindingKind;
|
|
26
|
+
readonly placement: ReleaseBindingPlacement;
|
|
27
|
+
readonly runtime: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const nativeBunReleaseBinding = {
|
|
31
|
+
boundary: 'trails-owned',
|
|
32
|
+
capabilities: ['pack-check', 'publish', 'registry-preflight'],
|
|
33
|
+
description:
|
|
34
|
+
'Built-in Bun release binding for Trails-owned package pack checks, npm registry preflight, and lockstep package publication.',
|
|
35
|
+
id: 'release.binding.native-bun',
|
|
36
|
+
kind: 'native',
|
|
37
|
+
placement: 'same-package',
|
|
38
|
+
runtime: 'bun',
|
|
39
|
+
} satisfies ReleaseBindingDescriptor;
|