@ontrails/trails 1.0.0-beta.47 → 1.0.0-beta.49
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 +13 -0
- package/README.md +2 -0
- package/package.json +16 -16
- package/src/regrade/receipt-history.ts +1 -0
- package/src/release/cli-bundle.ts +575 -0
- package/src/release/homebrew.ts +220 -0
- package/src/release/index.ts +26 -0
- package/src/release/policy.ts +18 -5
- package/src/trails/topo-support.ts +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# trails
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.49
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`822b403`](https://github.com/outfitter-dev/trails/commit/822b40375cc2947f0824d5f4e291b370b5062866): Ship deterministic macOS and Linux CLI bundles for the GitHub release and hand published releases to the Outfitter Homebrew tap through a reviewable formula PR.
|
|
8
|
+
|
|
9
|
+
## 1.0.0-beta.48
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`d8a2e7a`](https://github.com/outfitter-dev/trails/commit/d8a2e7a3e89ac06c1f594ef845ea18acaf58ea64): Preserve governed conversion provenance when Regrade appends a run to an existing v3 receipt.
|
|
14
|
+
- [`7cca56b`](https://github.com/outfitter-dev/trails/commit/7cca56bdf3802826f2d4d338ee490d261cb8e52b): Keep ordinary source pull request labels out of generated release policy by requiring the canonical `changeset-release/main` head route during release PR discovery.
|
|
15
|
+
|
|
3
16
|
## 1.0.0-beta.47
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Command-line tools for working with Trails projects.
|
|
4
4
|
|
|
5
|
+
Install, update, or uninstall the standalone CLI with Homebrew using the canonical [installation guide](../../docs/getting-started.md#installation). The formula requires Bun at runtime.
|
|
6
|
+
|
|
5
7
|
Use the CLI to scaffold a Trails app, add surfaces, inspect the current topo, run warden checks, manage draft state, and keep local Trails project state tidy.
|
|
6
8
|
|
|
7
9
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/trails",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.49",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/outfitter-dev/trails.git",
|
|
@@ -32,25 +32,25 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@clack/prompts": "^1.1.0",
|
|
35
|
-
"@ontrails/adapter-kit": "^1.0.0-beta.
|
|
36
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
37
|
-
"@ontrails/commander": "^1.0.0-beta.
|
|
38
|
-
"@ontrails/config": "^1.0.0-beta.
|
|
39
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
40
|
-
"@ontrails/http": "^1.0.0-beta.
|
|
41
|
-
"@ontrails/mcp": "^1.0.0-beta.
|
|
42
|
-
"@ontrails/observability": "^1.0.0-beta.
|
|
43
|
-
"@ontrails/permits": "^1.0.0-beta.
|
|
44
|
-
"@ontrails/regrade": "^1.0.0-beta.
|
|
45
|
-
"@ontrails/source": "^1.0.0-beta.
|
|
46
|
-
"@ontrails/topography": "^1.0.0-beta.
|
|
47
|
-
"@ontrails/warden": "^1.0.0-beta.
|
|
35
|
+
"@ontrails/adapter-kit": "^1.0.0-beta.49",
|
|
36
|
+
"@ontrails/cli": "^1.0.0-beta.49",
|
|
37
|
+
"@ontrails/commander": "^1.0.0-beta.49",
|
|
38
|
+
"@ontrails/config": "^1.0.0-beta.49",
|
|
39
|
+
"@ontrails/core": "^1.0.0-beta.49",
|
|
40
|
+
"@ontrails/http": "^1.0.0-beta.49",
|
|
41
|
+
"@ontrails/mcp": "^1.0.0-beta.49",
|
|
42
|
+
"@ontrails/observability": "^1.0.0-beta.49",
|
|
43
|
+
"@ontrails/permits": "^1.0.0-beta.49",
|
|
44
|
+
"@ontrails/regrade": "^1.0.0-beta.49",
|
|
45
|
+
"@ontrails/source": "^1.0.0-beta.49",
|
|
46
|
+
"@ontrails/topography": "^1.0.0-beta.49",
|
|
47
|
+
"@ontrails/warden": "^1.0.0-beta.49",
|
|
48
48
|
"commander": "^14.0.3",
|
|
49
49
|
"typescript": "^5.9.3",
|
|
50
50
|
"zod": "^4.3.5"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@ontrails/cloudflare": "^1.0.0-beta.
|
|
54
|
-
"@ontrails/testing": "^1.0.0-beta.
|
|
53
|
+
"@ontrails/cloudflare": "^1.0.0-beta.49",
|
|
54
|
+
"@ontrails/testing": "^1.0.0-beta.49"
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -405,6 +405,7 @@ export const buildRegradeHistoryReceipt = (params: {
|
|
|
405
405
|
transitionId: params.transitionId,
|
|
406
406
|
};
|
|
407
407
|
const receipt = {
|
|
408
|
+
conversion: params.prior?.artifact.conversion,
|
|
408
409
|
id: params.transitionId,
|
|
409
410
|
kind: 'regrade-history',
|
|
410
411
|
path: params.historyPath,
|
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
cpSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
mkdtempSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
} from 'node:fs';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
14
|
+
|
|
15
|
+
export const trailsCliReleasePlatformValues = [
|
|
16
|
+
'darwin-arm64',
|
|
17
|
+
'darwin-x64',
|
|
18
|
+
'linux-arm64',
|
|
19
|
+
'linux-x64',
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
export type TrailsCliReleasePlatform =
|
|
23
|
+
(typeof trailsCliReleasePlatformValues)[number];
|
|
24
|
+
|
|
25
|
+
export interface TrailsCliReleasePlatformDescriptor {
|
|
26
|
+
readonly oxcParserBinding: string;
|
|
27
|
+
readonly oxcResolverBinding: string;
|
|
28
|
+
readonly platform: TrailsCliReleasePlatform;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const trailsCliReleasePlatforms = [
|
|
32
|
+
{
|
|
33
|
+
oxcParserBinding: '@oxc-parser/binding-darwin-arm64',
|
|
34
|
+
oxcResolverBinding: '@oxc-resolver/binding-darwin-arm64',
|
|
35
|
+
platform: 'darwin-arm64',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
oxcParserBinding: '@oxc-parser/binding-darwin-x64',
|
|
39
|
+
oxcResolverBinding: '@oxc-resolver/binding-darwin-x64',
|
|
40
|
+
platform: 'darwin-x64',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
oxcParserBinding: '@oxc-parser/binding-linux-arm64-gnu',
|
|
44
|
+
oxcResolverBinding: '@oxc-resolver/binding-linux-arm64-gnu',
|
|
45
|
+
platform: 'linux-arm64',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
oxcParserBinding: '@oxc-parser/binding-linux-x64-gnu',
|
|
49
|
+
oxcResolverBinding: '@oxc-resolver/binding-linux-x64-gnu',
|
|
50
|
+
platform: 'linux-x64',
|
|
51
|
+
},
|
|
52
|
+
] as const satisfies readonly TrailsCliReleasePlatformDescriptor[];
|
|
53
|
+
|
|
54
|
+
export interface BuildTrailsCliReleaseOptions {
|
|
55
|
+
readonly outDir: string;
|
|
56
|
+
readonly platform?: TrailsCliReleasePlatform;
|
|
57
|
+
readonly repoRoot?: string;
|
|
58
|
+
readonly version?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface TrailsCliReleaseArtifact {
|
|
62
|
+
readonly archiveName: string;
|
|
63
|
+
readonly archivePath: string;
|
|
64
|
+
readonly checksum: string;
|
|
65
|
+
readonly checksumPath: string;
|
|
66
|
+
readonly platform: TrailsCliReleasePlatform;
|
|
67
|
+
readonly version: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const DEFAULT_REPO_ROOT = resolve(import.meta.dir, '../../../..');
|
|
71
|
+
const textDecoder = new TextDecoder();
|
|
72
|
+
const BUNDLE_EXTERNALS = ['oxc-parser', 'oxc-resolver'] as const;
|
|
73
|
+
|
|
74
|
+
const platformDescriptor = (
|
|
75
|
+
platform: TrailsCliReleasePlatform
|
|
76
|
+
): TrailsCliReleasePlatformDescriptor => {
|
|
77
|
+
const descriptor = trailsCliReleasePlatforms.find(
|
|
78
|
+
(candidate) => candidate.platform === platform
|
|
79
|
+
);
|
|
80
|
+
if (!descriptor) {
|
|
81
|
+
throw new Error(`Unsupported Trails CLI release platform: ${platform}`);
|
|
82
|
+
}
|
|
83
|
+
return descriptor;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const currentTrailsCliReleasePlatform = (
|
|
87
|
+
platform = process.platform,
|
|
88
|
+
architecture = process.arch
|
|
89
|
+
): TrailsCliReleasePlatform => {
|
|
90
|
+
const candidate = `${platform}-${architecture}`;
|
|
91
|
+
if (
|
|
92
|
+
trailsCliReleasePlatformValues.includes(
|
|
93
|
+
candidate as TrailsCliReleasePlatform
|
|
94
|
+
)
|
|
95
|
+
) {
|
|
96
|
+
return candidate as TrailsCliReleasePlatform;
|
|
97
|
+
}
|
|
98
|
+
throw new Error(`Unsupported Trails CLI release host: ${candidate}`);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const trailsCliArchiveName = (
|
|
102
|
+
version: string,
|
|
103
|
+
platform: TrailsCliReleasePlatform
|
|
104
|
+
): string => `trails-v${version}-${platform}.tar.gz`;
|
|
105
|
+
|
|
106
|
+
export const trailsCliChecksumName = (version: string): string =>
|
|
107
|
+
`trails-v${version}-SHA256SUMS`;
|
|
108
|
+
|
|
109
|
+
export const expectedTrailsCliReleaseAssetNames = (
|
|
110
|
+
version: string
|
|
111
|
+
): readonly string[] => [
|
|
112
|
+
...trailsCliReleasePlatformValues.map((platform) =>
|
|
113
|
+
trailsCliArchiveName(version, platform)
|
|
114
|
+
),
|
|
115
|
+
trailsCliChecksumName(version),
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const findPackageRoot = (entryPath: string, expectedName: string): string => {
|
|
119
|
+
let candidate = dirname(entryPath);
|
|
120
|
+
while (candidate !== dirname(candidate)) {
|
|
121
|
+
const packagePath = join(candidate, 'package.json');
|
|
122
|
+
if (existsSync(packagePath)) {
|
|
123
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8')) as {
|
|
124
|
+
name?: string;
|
|
125
|
+
};
|
|
126
|
+
if (packageJson.name === expectedName) {
|
|
127
|
+
return candidate;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
candidate = dirname(candidate);
|
|
131
|
+
}
|
|
132
|
+
throw new Error(`Could not locate package root for ${expectedName}`);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const resolvePackageRoot = (
|
|
136
|
+
packageName: string,
|
|
137
|
+
fromDirectory: string
|
|
138
|
+
): string =>
|
|
139
|
+
findPackageRoot(Bun.resolveSync(packageName, fromDirectory), packageName);
|
|
140
|
+
|
|
141
|
+
const copyPackage = (
|
|
142
|
+
packageName: string,
|
|
143
|
+
sourceRoot: string,
|
|
144
|
+
bundleRoot: string
|
|
145
|
+
): { readonly name: string; readonly version: string } => {
|
|
146
|
+
const scope = packageName.startsWith('@') ? packageName.split('/')[0] : null;
|
|
147
|
+
const target = join(bundleRoot, 'node_modules', packageName);
|
|
148
|
+
if (scope) {
|
|
149
|
+
mkdirSync(join(bundleRoot, 'node_modules', scope), { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
cpSync(sourceRoot, target, { dereference: true, recursive: true });
|
|
152
|
+
const packageJson = JSON.parse(
|
|
153
|
+
readFileSync(join(sourceRoot, 'package.json'), 'utf8')
|
|
154
|
+
) as { name?: string; version?: string };
|
|
155
|
+
if (packageJson.name !== packageName || !packageJson.version) {
|
|
156
|
+
throw new Error(`Invalid package metadata for ${packageName}`);
|
|
157
|
+
}
|
|
158
|
+
return { name: packageName, version: packageJson.version };
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const launcherSource = `#!/bin/sh
|
|
162
|
+
set -eu
|
|
163
|
+
|
|
164
|
+
source_path=$0
|
|
165
|
+
while [ -L "$source_path" ]; do
|
|
166
|
+
source_dir=$(CDPATH= cd -P -- "$(dirname -- "$source_path")" && pwd)
|
|
167
|
+
source_path=$(readlink "$source_path")
|
|
168
|
+
case $source_path in
|
|
169
|
+
/*) ;;
|
|
170
|
+
*) source_path=$source_dir/$source_path ;;
|
|
171
|
+
esac
|
|
172
|
+
done
|
|
173
|
+
source_dir=$(CDPATH= cd -P -- "$(dirname -- "$source_path")" && pwd)
|
|
174
|
+
exec bun "$source_dir/../lib/trails.js" "$@"
|
|
175
|
+
`;
|
|
176
|
+
|
|
177
|
+
interface ArchiveEntry {
|
|
178
|
+
readonly bytes: Buffer;
|
|
179
|
+
readonly mode: number;
|
|
180
|
+
readonly path: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const collectArchiveEntries = (
|
|
184
|
+
rootDir: string,
|
|
185
|
+
archiveRoot: string
|
|
186
|
+
): readonly ArchiveEntry[] => {
|
|
187
|
+
const entries: ArchiveEntry[] = [];
|
|
188
|
+
const visit = (directory: string): void => {
|
|
189
|
+
for (const entry of readdirSync(directory, {
|
|
190
|
+
withFileTypes: true,
|
|
191
|
+
}).toSorted((left, right) => left.name.localeCompare(right.name))) {
|
|
192
|
+
const absolutePath = join(directory, entry.name);
|
|
193
|
+
if (entry.isDirectory()) {
|
|
194
|
+
visit(absolutePath);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (!entry.isFile()) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Release bundles cannot contain links: ${absolutePath}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const relativePath = relative(rootDir, absolutePath).replaceAll(
|
|
203
|
+
'\\',
|
|
204
|
+
'/'
|
|
205
|
+
);
|
|
206
|
+
entries.push({
|
|
207
|
+
bytes: readFileSync(absolutePath),
|
|
208
|
+
mode: relativePath === 'bin/trails' ? 0o755 : 0o644,
|
|
209
|
+
path: `${archiveRoot}/${relativePath}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
visit(rootDir);
|
|
214
|
+
return entries;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const writeText = (
|
|
218
|
+
target: Buffer,
|
|
219
|
+
offset: number,
|
|
220
|
+
length: number,
|
|
221
|
+
value: string
|
|
222
|
+
): void => {
|
|
223
|
+
const encoded = Buffer.from(value);
|
|
224
|
+
if (encoded.length > length) {
|
|
225
|
+
throw new Error(`Tar header field is too long: ${value}`);
|
|
226
|
+
}
|
|
227
|
+
encoded.copy(target, offset);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const writeOctal = (
|
|
231
|
+
target: Buffer,
|
|
232
|
+
offset: number,
|
|
233
|
+
length: number,
|
|
234
|
+
value: number
|
|
235
|
+
): void => {
|
|
236
|
+
const encoded = value.toString(8).padStart(length - 1, '0');
|
|
237
|
+
writeText(target, offset, length - 1, encoded);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const splitTarPath = (
|
|
241
|
+
path: string
|
|
242
|
+
): { readonly name: string; readonly prefix: string } => {
|
|
243
|
+
if (Buffer.byteLength(path) <= 100) {
|
|
244
|
+
return { name: path, prefix: '' };
|
|
245
|
+
}
|
|
246
|
+
for (
|
|
247
|
+
let index = path.lastIndexOf('/');
|
|
248
|
+
index > 0;
|
|
249
|
+
index = path.lastIndexOf('/', index - 1)
|
|
250
|
+
) {
|
|
251
|
+
const prefix = path.slice(0, index);
|
|
252
|
+
const name = path.slice(index + 1);
|
|
253
|
+
if (Buffer.byteLength(prefix) <= 155 && Buffer.byteLength(name) <= 100) {
|
|
254
|
+
return { name, prefix };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
throw new Error(`Tar path is too long: ${path}`);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const tarHeader = (entry: ArchiveEntry): Buffer => {
|
|
261
|
+
const header = Buffer.alloc(512);
|
|
262
|
+
const { name, prefix } = splitTarPath(entry.path);
|
|
263
|
+
writeText(header, 0, 100, name);
|
|
264
|
+
writeOctal(header, 100, 8, entry.mode);
|
|
265
|
+
writeOctal(header, 108, 8, 0);
|
|
266
|
+
writeOctal(header, 116, 8, 0);
|
|
267
|
+
writeOctal(header, 124, 12, entry.bytes.length);
|
|
268
|
+
writeOctal(header, 136, 12, 0);
|
|
269
|
+
header.fill(0x20, 148, 156);
|
|
270
|
+
header[156] = 0x30;
|
|
271
|
+
writeText(header, 257, 6, 'ustar');
|
|
272
|
+
writeText(header, 263, 2, '00');
|
|
273
|
+
writeText(header, 345, 155, prefix);
|
|
274
|
+
const checksum = header.reduce((sum, byte) => sum + byte, 0);
|
|
275
|
+
writeText(header, 148, 8, `${checksum.toString(8).padStart(6, '0')}\0 `);
|
|
276
|
+
return header;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export const createDeterministicTarGzip = (
|
|
280
|
+
rootDir: string,
|
|
281
|
+
archiveRoot: string
|
|
282
|
+
): Uint8Array => {
|
|
283
|
+
const chunks: Buffer[] = [];
|
|
284
|
+
for (const entry of collectArchiveEntries(rootDir, archiveRoot)) {
|
|
285
|
+
chunks.push(tarHeader(entry), entry.bytes);
|
|
286
|
+
const padding = (512 - (entry.bytes.length % 512)) % 512;
|
|
287
|
+
if (padding > 0) {
|
|
288
|
+
chunks.push(Buffer.alloc(padding));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
chunks.push(Buffer.alloc(1024));
|
|
292
|
+
return Bun.gzipSync(Buffer.concat(chunks), { level: 9 });
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const sha256 = (bytes: Uint8Array): string =>
|
|
296
|
+
new Bun.CryptoHasher('sha256').update(bytes).digest('hex');
|
|
297
|
+
|
|
298
|
+
const readVersion = (repoRoot: string): string => {
|
|
299
|
+
const packageJson = JSON.parse(
|
|
300
|
+
readFileSync(join(repoRoot, 'apps/trails/package.json'), 'utf8')
|
|
301
|
+
) as { version?: string };
|
|
302
|
+
if (!packageJson.version) {
|
|
303
|
+
throw new Error('apps/trails/package.json does not declare a version');
|
|
304
|
+
}
|
|
305
|
+
return packageJson.version;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export const verifyTrailsCliArchive = (
|
|
309
|
+
artifact: Pick<
|
|
310
|
+
TrailsCliReleaseArtifact,
|
|
311
|
+
'archivePath' | 'platform' | 'version'
|
|
312
|
+
>
|
|
313
|
+
): void => {
|
|
314
|
+
const host = currentTrailsCliReleasePlatform();
|
|
315
|
+
if (artifact.platform !== host) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Cannot cold-start ${artifact.platform} on the ${host} release host`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const extractRoot = mkdtempSync(join(tmpdir(), 'trails-cli-cold-'));
|
|
321
|
+
try {
|
|
322
|
+
const extract = Bun.spawnSync([
|
|
323
|
+
'tar',
|
|
324
|
+
'-xzf',
|
|
325
|
+
artifact.archivePath,
|
|
326
|
+
'-C',
|
|
327
|
+
extractRoot,
|
|
328
|
+
]);
|
|
329
|
+
if (extract.exitCode !== 0) {
|
|
330
|
+
throw new Error(textDecoder.decode(extract.stderr));
|
|
331
|
+
}
|
|
332
|
+
const rootName = artifact.archivePath
|
|
333
|
+
.split('/')
|
|
334
|
+
.at(-1)
|
|
335
|
+
?.replace(/\.tar\.gz$/u, '');
|
|
336
|
+
if (!rootName) {
|
|
337
|
+
throw new Error(`Invalid archive path: ${artifact.archivePath}`);
|
|
338
|
+
}
|
|
339
|
+
const launcher = join(extractRoot, rootName, 'bin/trails');
|
|
340
|
+
const bundleRoot = join(extractRoot, rootName);
|
|
341
|
+
for (const [argument, expected] of [
|
|
342
|
+
['--version', artifact.version],
|
|
343
|
+
['--help', 'Usage: trails'],
|
|
344
|
+
] as const) {
|
|
345
|
+
const result = Bun.spawnSync([launcher, argument], {
|
|
346
|
+
cwd: extractRoot,
|
|
347
|
+
env: {
|
|
348
|
+
HOME: extractRoot,
|
|
349
|
+
PATH: process.env['PATH'] ?? '',
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
const output = textDecoder.decode(result.stdout);
|
|
353
|
+
if (result.exitCode !== 0 || !output.includes(expected)) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Cold Trails CLI ${argument} check failed: ${textDecoder.decode(result.stderr) || output}`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const example = Bun.spawnSync(
|
|
360
|
+
[
|
|
361
|
+
launcher,
|
|
362
|
+
'run',
|
|
363
|
+
'example',
|
|
364
|
+
'survey.brief',
|
|
365
|
+
'Brief capability report',
|
|
366
|
+
'--root-dir',
|
|
367
|
+
'.',
|
|
368
|
+
'--module',
|
|
369
|
+
'lib/app.js',
|
|
370
|
+
'--permit',
|
|
371
|
+
'{"id":"release-cold-verifier","scopes":["trails:run"]}',
|
|
372
|
+
],
|
|
373
|
+
{
|
|
374
|
+
cwd: bundleRoot,
|
|
375
|
+
env: {
|
|
376
|
+
HOME: extractRoot,
|
|
377
|
+
PATH: process.env['PATH'] ?? '',
|
|
378
|
+
},
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
const exampleOutput = textDecoder.decode(example.stdout);
|
|
382
|
+
const expectedExampleOutput = 'OK survey.brief :: Brief capability report';
|
|
383
|
+
if (
|
|
384
|
+
example.exitCode !== 0 ||
|
|
385
|
+
!exampleOutput.split(/\r?\n/u).includes(expectedExampleOutput)
|
|
386
|
+
) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Cold Trails CLI authored example check failed: ${textDecoder.decode(example.stderr) || exampleOutput}`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
} finally {
|
|
392
|
+
rmSync(extractRoot, { force: true, recursive: true });
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
export const buildTrailsCliReleaseArtifact = async (
|
|
397
|
+
options: BuildTrailsCliReleaseOptions
|
|
398
|
+
): Promise<TrailsCliReleaseArtifact> => {
|
|
399
|
+
const repoRoot = resolve(options.repoRoot ?? DEFAULT_REPO_ROOT);
|
|
400
|
+
const platform = options.platform ?? currentTrailsCliReleasePlatform();
|
|
401
|
+
const host = currentTrailsCliReleasePlatform();
|
|
402
|
+
if (platform !== host) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Build ${platform} on its matching host so Bun installs the correct optional bindings; current host is ${host}`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
const descriptor = platformDescriptor(platform);
|
|
408
|
+
const packageVersion = readVersion(repoRoot);
|
|
409
|
+
const version = options.version ?? packageVersion;
|
|
410
|
+
if (version !== packageVersion) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
`Requested version ${version} does not match apps/trails/package.json ${packageVersion}`
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const outputDir = resolve(options.outDir);
|
|
417
|
+
mkdirSync(outputDir, { recursive: true });
|
|
418
|
+
const stagingDir = mkdtempSync(join(tmpdir(), 'trails-cli-release-'));
|
|
419
|
+
const archiveName = trailsCliArchiveName(version, platform);
|
|
420
|
+
const archiveRoot = archiveName.replace(/\.tar\.gz$/u, '');
|
|
421
|
+
const bundleRoot = join(stagingDir, archiveRoot);
|
|
422
|
+
try {
|
|
423
|
+
mkdirSync(join(bundleRoot, 'bin'), { recursive: true });
|
|
424
|
+
mkdirSync(join(bundleRoot, 'lib'), { recursive: true });
|
|
425
|
+
for (const [entrypoint, output, label] of [
|
|
426
|
+
['apps/trails/bin/trails.ts', 'trails.js', 'CLI'],
|
|
427
|
+
['apps/trails/src/app.ts', 'app.js', 'app module'],
|
|
428
|
+
] as const) {
|
|
429
|
+
const build = Bun.spawnSync(
|
|
430
|
+
[
|
|
431
|
+
process.execPath,
|
|
432
|
+
'build',
|
|
433
|
+
entrypoint,
|
|
434
|
+
'--target=bun',
|
|
435
|
+
...BUNDLE_EXTERNALS.flatMap((external) => ['--external', external]),
|
|
436
|
+
'--outfile',
|
|
437
|
+
join(bundleRoot, 'lib', output),
|
|
438
|
+
],
|
|
439
|
+
{ cwd: repoRoot }
|
|
440
|
+
);
|
|
441
|
+
if (build.exitCode !== 0) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
`Could not bundle the Trails ${label}: ${textDecoder.decode(build.stderr)}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const parserRoot = resolvePackageRoot(
|
|
449
|
+
'oxc-parser',
|
|
450
|
+
join(repoRoot, 'packages/source/src')
|
|
451
|
+
);
|
|
452
|
+
const resolverRoot = resolvePackageRoot(
|
|
453
|
+
'oxc-resolver',
|
|
454
|
+
join(repoRoot, 'packages/warden/src')
|
|
455
|
+
);
|
|
456
|
+
const includedPackages = [
|
|
457
|
+
copyPackage('oxc-parser', parserRoot, bundleRoot),
|
|
458
|
+
copyPackage('oxc-resolver', resolverRoot, bundleRoot),
|
|
459
|
+
copyPackage(
|
|
460
|
+
descriptor.oxcParserBinding,
|
|
461
|
+
resolvePackageRoot(descriptor.oxcParserBinding, parserRoot),
|
|
462
|
+
bundleRoot
|
|
463
|
+
),
|
|
464
|
+
copyPackage(
|
|
465
|
+
descriptor.oxcResolverBinding,
|
|
466
|
+
resolvePackageRoot(descriptor.oxcResolverBinding, resolverRoot),
|
|
467
|
+
bundleRoot
|
|
468
|
+
),
|
|
469
|
+
];
|
|
470
|
+
const dependencies: Record<string, string> = {};
|
|
471
|
+
for (const {
|
|
472
|
+
name,
|
|
473
|
+
version: dependencyVersion,
|
|
474
|
+
} of includedPackages.toSorted((left, right) =>
|
|
475
|
+
left.name.localeCompare(right.name)
|
|
476
|
+
)) {
|
|
477
|
+
dependencies[name] = dependencyVersion;
|
|
478
|
+
}
|
|
479
|
+
writeFileSync(
|
|
480
|
+
join(bundleRoot, 'package.json'),
|
|
481
|
+
`${JSON.stringify(
|
|
482
|
+
{
|
|
483
|
+
dependencies,
|
|
484
|
+
name: '@ontrails/trails-homebrew-bundle',
|
|
485
|
+
private: true,
|
|
486
|
+
type: 'module',
|
|
487
|
+
version,
|
|
488
|
+
},
|
|
489
|
+
null,
|
|
490
|
+
2
|
|
491
|
+
)}\n`
|
|
492
|
+
);
|
|
493
|
+
const launcherPath = join(bundleRoot, 'bin/trails');
|
|
494
|
+
writeFileSync(launcherPath, launcherSource);
|
|
495
|
+
chmodSync(launcherPath, 0o755);
|
|
496
|
+
|
|
497
|
+
const archiveBytes = createDeterministicTarGzip(bundleRoot, archiveRoot);
|
|
498
|
+
const archivePath = join(outputDir, archiveName);
|
|
499
|
+
const checksum = sha256(archiveBytes);
|
|
500
|
+
const checksumPath = `${archivePath}.sha256`;
|
|
501
|
+
writeFileSync(archivePath, archiveBytes);
|
|
502
|
+
writeFileSync(checksumPath, `${checksum} ${archiveName}\n`);
|
|
503
|
+
const artifact = {
|
|
504
|
+
archiveName,
|
|
505
|
+
archivePath,
|
|
506
|
+
checksum,
|
|
507
|
+
checksumPath,
|
|
508
|
+
platform,
|
|
509
|
+
version,
|
|
510
|
+
} satisfies TrailsCliReleaseArtifact;
|
|
511
|
+
verifyTrailsCliArchive(artifact);
|
|
512
|
+
return artifact;
|
|
513
|
+
} finally {
|
|
514
|
+
rmSync(stagingDir, { force: true, recursive: true });
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const parseCliArgs = (
|
|
519
|
+
args: readonly string[]
|
|
520
|
+
): BuildTrailsCliReleaseOptions => {
|
|
521
|
+
let outDir: string | undefined;
|
|
522
|
+
let platform: TrailsCliReleasePlatform | undefined;
|
|
523
|
+
let repoRoot: string | undefined;
|
|
524
|
+
let version: string | undefined;
|
|
525
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
526
|
+
const argument = args[index];
|
|
527
|
+
const value = args[index + 1];
|
|
528
|
+
if (!value) {
|
|
529
|
+
throw new Error(`Missing value for ${argument}`);
|
|
530
|
+
}
|
|
531
|
+
if (argument === '--out-dir') {
|
|
532
|
+
outDir = value;
|
|
533
|
+
} else if (argument === '--platform') {
|
|
534
|
+
if (
|
|
535
|
+
!trailsCliReleasePlatformValues.includes(
|
|
536
|
+
value as TrailsCliReleasePlatform
|
|
537
|
+
)
|
|
538
|
+
) {
|
|
539
|
+
throw new Error(`Unsupported Trails CLI release platform: ${value}`);
|
|
540
|
+
}
|
|
541
|
+
platform = value as TrailsCliReleasePlatform;
|
|
542
|
+
} else if (argument === '--repo-root') {
|
|
543
|
+
repoRoot = value;
|
|
544
|
+
} else if (argument === '--version') {
|
|
545
|
+
version = value;
|
|
546
|
+
} else {
|
|
547
|
+
throw new Error(`Unknown Trails CLI release argument: ${argument}`);
|
|
548
|
+
}
|
|
549
|
+
index += 1;
|
|
550
|
+
}
|
|
551
|
+
if (!outDir) {
|
|
552
|
+
throw new Error('Missing required argument: --out-dir');
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
outDir,
|
|
556
|
+
...(platform ? { platform } : {}),
|
|
557
|
+
...(repoRoot ? { repoRoot } : {}),
|
|
558
|
+
...(version ? { version } : {}),
|
|
559
|
+
};
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
export const runTrailsCliReleaseCli = async (
|
|
563
|
+
args: readonly string[]
|
|
564
|
+
): Promise<number> => {
|
|
565
|
+
try {
|
|
566
|
+
const artifact = await buildTrailsCliReleaseArtifact(parseCliArgs(args));
|
|
567
|
+
console.log(
|
|
568
|
+
`Built and cold-verified ${artifact.archiveName} (${artifact.checksum})`
|
|
569
|
+
);
|
|
570
|
+
return 0;
|
|
571
|
+
} catch (error) {
|
|
572
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
573
|
+
return 1;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
expectedTrailsCliReleaseAssetNames,
|
|
6
|
+
trailsCliArchiveName,
|
|
7
|
+
trailsCliChecksumName,
|
|
8
|
+
trailsCliReleasePlatformValues,
|
|
9
|
+
} from './cli-bundle.js';
|
|
10
|
+
import type { TrailsCliReleasePlatform } from './cli-bundle.js';
|
|
11
|
+
|
|
12
|
+
export interface GitHubReleaseAsset {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GitHubReleaseMetadata {
|
|
17
|
+
readonly assets: readonly GitHubReleaseAsset[];
|
|
18
|
+
readonly draft: boolean;
|
|
19
|
+
readonly published_at: string | null;
|
|
20
|
+
readonly tag_name: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type TrailsCliReleaseChecksums = Readonly<
|
|
24
|
+
Record<TrailsCliReleasePlatform, string>
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
const versionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
|
|
28
|
+
const checksumPattern = /^[0-9a-f]{64}$/u;
|
|
29
|
+
|
|
30
|
+
const requireVersion = (version: string): void => {
|
|
31
|
+
if (!versionPattern.test(version)) {
|
|
32
|
+
throw new Error(`Invalid Trails release version: ${version}`);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const validateTrailsHomebrewRelease = (
|
|
37
|
+
release: GitHubReleaseMetadata,
|
|
38
|
+
version: string
|
|
39
|
+
): void => {
|
|
40
|
+
requireVersion(version);
|
|
41
|
+
if (release.draft || !release.published_at) {
|
|
42
|
+
throw new Error(`GitHub release v${version} is not published`);
|
|
43
|
+
}
|
|
44
|
+
if (release.tag_name !== `v${version}`) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`GitHub release tag ${release.tag_name} does not match v${version}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
for (const expectedName of expectedTrailsCliReleaseAssetNames(version)) {
|
|
50
|
+
const count = release.assets.filter(
|
|
51
|
+
({ name }) => name === expectedName
|
|
52
|
+
).length;
|
|
53
|
+
if (count !== 1) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Expected exactly one ${expectedName} release asset, found ${count}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const parseTrailsCliChecksums = (
|
|
62
|
+
contents: string,
|
|
63
|
+
version: string
|
|
64
|
+
): TrailsCliReleaseChecksums => {
|
|
65
|
+
requireVersion(version);
|
|
66
|
+
const found = new Map<TrailsCliReleasePlatform, string>();
|
|
67
|
+
for (const line of contents.split('\n').filter(Boolean)) {
|
|
68
|
+
const match = /^(?<checksum>[0-9a-f]{64}) {2}(?<name>.+)$/u.exec(line);
|
|
69
|
+
if (!match?.groups) {
|
|
70
|
+
throw new Error(`Invalid Trails CLI checksum line: ${line}`);
|
|
71
|
+
}
|
|
72
|
+
const platform = trailsCliReleasePlatformValues.find(
|
|
73
|
+
(candidate) =>
|
|
74
|
+
trailsCliArchiveName(version, candidate) === match.groups?.['name']
|
|
75
|
+
);
|
|
76
|
+
if (!platform) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`Unexpected Trails CLI checksum asset: ${match.groups['name']}`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (found.has(platform)) {
|
|
82
|
+
throw new Error(`Duplicate Trails CLI checksum for ${platform}`);
|
|
83
|
+
}
|
|
84
|
+
const parsedChecksum = match.groups['checksum'];
|
|
85
|
+
if (!parsedChecksum) {
|
|
86
|
+
throw new Error(`Missing Trails CLI checksum in line: ${line}`);
|
|
87
|
+
}
|
|
88
|
+
found.set(platform, parsedChecksum);
|
|
89
|
+
}
|
|
90
|
+
for (const platform of trailsCliReleasePlatformValues) {
|
|
91
|
+
if (!found.has(platform)) {
|
|
92
|
+
throw new Error(`Missing Trails CLI checksum for ${platform}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return Object.fromEntries(found) as TrailsCliReleaseChecksums;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const verifyTrailsHomebrewAssetDirectory = async (
|
|
99
|
+
assetsDir: string,
|
|
100
|
+
release: GitHubReleaseMetadata,
|
|
101
|
+
version: string
|
|
102
|
+
): Promise<TrailsCliReleaseChecksums> => {
|
|
103
|
+
validateTrailsHomebrewRelease(release, version);
|
|
104
|
+
const checksumPath = join(assetsDir, trailsCliChecksumName(version));
|
|
105
|
+
const checksums = parseTrailsCliChecksums(
|
|
106
|
+
readFileSync(checksumPath, 'utf8'),
|
|
107
|
+
version
|
|
108
|
+
);
|
|
109
|
+
for (const platform of trailsCliReleasePlatformValues) {
|
|
110
|
+
const archiveName = trailsCliArchiveName(version, platform);
|
|
111
|
+
const bytes = await Bun.file(join(assetsDir, archiveName)).bytes();
|
|
112
|
+
const actual = new Bun.CryptoHasher('sha256').update(bytes).digest('hex');
|
|
113
|
+
if (actual !== checksums[platform]) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Checksum mismatch for ${archiveName}: expected ${checksums[platform]}, found ${actual}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return checksums;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const renderTrailsHomebrewFormula = (
|
|
123
|
+
version: string,
|
|
124
|
+
checksums: TrailsCliReleaseChecksums
|
|
125
|
+
): string => {
|
|
126
|
+
requireVersion(version);
|
|
127
|
+
for (const [platform, checksum] of Object.entries(checksums)) {
|
|
128
|
+
if (!checksumPattern.test(checksum)) {
|
|
129
|
+
throw new Error(`Invalid ${platform} SHA256: ${checksum}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return `class Trails < Formula
|
|
133
|
+
desc "Agent-native, contract-first TypeScript framework"
|
|
134
|
+
homepage "https://github.com/outfitter-dev/trails"
|
|
135
|
+
version "${version}"
|
|
136
|
+
license "MIT"
|
|
137
|
+
|
|
138
|
+
depends_on "bun"
|
|
139
|
+
|
|
140
|
+
on_macos do
|
|
141
|
+
on_arm do
|
|
142
|
+
url "https://github.com/outfitter-dev/trails/releases/download/v#{version}/trails-v#{version}-darwin-arm64.tar.gz"
|
|
143
|
+
sha256 "${checksums['darwin-arm64']}"
|
|
144
|
+
end
|
|
145
|
+
on_intel do
|
|
146
|
+
url "https://github.com/outfitter-dev/trails/releases/download/v#{version}/trails-v#{version}-darwin-x64.tar.gz"
|
|
147
|
+
sha256 "${checksums['darwin-x64']}"
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
on_linux do
|
|
152
|
+
on_arm do
|
|
153
|
+
url "https://github.com/outfitter-dev/trails/releases/download/v#{version}/trails-v#{version}-linux-arm64.tar.gz"
|
|
154
|
+
sha256 "${checksums['linux-arm64']}"
|
|
155
|
+
end
|
|
156
|
+
on_intel do
|
|
157
|
+
url "https://github.com/outfitter-dev/trails/releases/download/v#{version}/trails-v#{version}-linux-x64.tar.gz"
|
|
158
|
+
sha256 "${checksums['linux-x64']}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def install
|
|
163
|
+
libexec.install Dir["*"]
|
|
164
|
+
bin.install_symlink libexec/"bin/trails"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
test do
|
|
168
|
+
assert_match version.to_s, shell_output("#{bin}/trails --version")
|
|
169
|
+
assert_match "Usage: trails", shell_output("#{bin}/trails --help")
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
`;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const readOption = (args: readonly string[], flag: string): string => {
|
|
176
|
+
const index = args.indexOf(flag);
|
|
177
|
+
const value = index === -1 ? undefined : args[index + 1];
|
|
178
|
+
if (!value) {
|
|
179
|
+
throw new Error(`Missing required argument: ${flag}`);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const runTrailsHomebrewCli = async (
|
|
185
|
+
args: readonly string[]
|
|
186
|
+
): Promise<number> => {
|
|
187
|
+
try {
|
|
188
|
+
const [command, ...options] = args;
|
|
189
|
+
const version = readOption(options, '--version');
|
|
190
|
+
if (command === 'validate') {
|
|
191
|
+
const releasePath = resolve(readOption(options, '--release-json'));
|
|
192
|
+
const assetsDir = resolve(readOption(options, '--assets-dir'));
|
|
193
|
+
const release = JSON.parse(
|
|
194
|
+
readFileSync(releasePath, 'utf8')
|
|
195
|
+
) as GitHubReleaseMetadata;
|
|
196
|
+
await verifyTrailsHomebrewAssetDirectory(assetsDir, release, version);
|
|
197
|
+
console.log(`Validated Homebrew release assets for v${version}`);
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
if (command === 'render') {
|
|
201
|
+
const checksumsPath = resolve(readOption(options, '--checksums'));
|
|
202
|
+
const outputPath = resolve(readOption(options, '--output'));
|
|
203
|
+
const checksums = parseTrailsCliChecksums(
|
|
204
|
+
readFileSync(checksumsPath, 'utf8'),
|
|
205
|
+
version
|
|
206
|
+
);
|
|
207
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
208
|
+
writeFileSync(
|
|
209
|
+
outputPath,
|
|
210
|
+
renderTrailsHomebrewFormula(version, checksums)
|
|
211
|
+
);
|
|
212
|
+
console.log(`Rendered ${outputPath}`);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
throw new Error(`Unknown Homebrew release command: ${command ?? '<none>'}`);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
};
|
package/src/release/index.ts
CHANGED
|
@@ -56,6 +56,32 @@ export {
|
|
|
56
56
|
type ReleaseRule,
|
|
57
57
|
type ReleaseRuleInput,
|
|
58
58
|
} from './config.js';
|
|
59
|
+
export {
|
|
60
|
+
buildTrailsCliReleaseArtifact,
|
|
61
|
+
createDeterministicTarGzip,
|
|
62
|
+
currentTrailsCliReleasePlatform,
|
|
63
|
+
expectedTrailsCliReleaseAssetNames,
|
|
64
|
+
runTrailsCliReleaseCli,
|
|
65
|
+
trailsCliArchiveName,
|
|
66
|
+
trailsCliChecksumName,
|
|
67
|
+
trailsCliReleasePlatforms,
|
|
68
|
+
trailsCliReleasePlatformValues,
|
|
69
|
+
verifyTrailsCliArchive,
|
|
70
|
+
type BuildTrailsCliReleaseOptions,
|
|
71
|
+
type TrailsCliReleaseArtifact,
|
|
72
|
+
type TrailsCliReleasePlatform,
|
|
73
|
+
type TrailsCliReleasePlatformDescriptor,
|
|
74
|
+
} from './cli-bundle.js';
|
|
75
|
+
export {
|
|
76
|
+
parseTrailsCliChecksums,
|
|
77
|
+
renderTrailsHomebrewFormula,
|
|
78
|
+
runTrailsHomebrewCli,
|
|
79
|
+
validateTrailsHomebrewRelease,
|
|
80
|
+
verifyTrailsHomebrewAssetDirectory,
|
|
81
|
+
type GitHubReleaseAsset,
|
|
82
|
+
type GitHubReleaseMetadata,
|
|
83
|
+
type TrailsCliReleaseChecksums,
|
|
84
|
+
} from './homebrew.js';
|
|
59
85
|
export {
|
|
60
86
|
createNpmPublishCommand,
|
|
61
87
|
findPackedFirstPartyDependencyMismatches,
|
package/src/release/policy.ts
CHANGED
|
@@ -407,7 +407,8 @@ const makeReport = (
|
|
|
407
407
|
}
|
|
408
408
|
): ReleasePolicyReport => {
|
|
409
409
|
const canPublish =
|
|
410
|
-
|
|
410
|
+
input.releasePullRequest !== undefined &&
|
|
411
|
+
(options.decision === 'auto' || options.decision === 'manual');
|
|
411
412
|
const packagesPublished = registryComplete(input.registryPackages);
|
|
412
413
|
const reasons = [...options.reasons];
|
|
413
414
|
if (canPublish) {
|
|
@@ -1125,6 +1126,20 @@ const managedGitHubLabels = [
|
|
|
1125
1126
|
},
|
|
1126
1127
|
] as const;
|
|
1127
1128
|
|
|
1129
|
+
export const selectGeneratedReleasePullRequest = <
|
|
1130
|
+
PullRequest extends {
|
|
1131
|
+
readonly base: { readonly ref: string };
|
|
1132
|
+
readonly head: { readonly ref: string };
|
|
1133
|
+
},
|
|
1134
|
+
>(
|
|
1135
|
+
pulls: readonly PullRequest[]
|
|
1136
|
+
): PullRequest | undefined =>
|
|
1137
|
+
pulls.find(
|
|
1138
|
+
(candidate) =>
|
|
1139
|
+
candidate.base.ref === 'main' &&
|
|
1140
|
+
candidate.head.ref === 'changeset-release/main'
|
|
1141
|
+
);
|
|
1142
|
+
|
|
1128
1143
|
const readReleasePullRequest = async (
|
|
1129
1144
|
repository: string,
|
|
1130
1145
|
sha: string
|
|
@@ -1133,7 +1148,7 @@ const readReleasePullRequest = async (
|
|
|
1133
1148
|
repository,
|
|
1134
1149
|
`/commits/${sha}/pulls`
|
|
1135
1150
|
);
|
|
1136
|
-
const pull = pulls
|
|
1151
|
+
const pull = selectGeneratedReleasePullRequest(pulls);
|
|
1137
1152
|
if (!pull) {
|
|
1138
1153
|
return undefined;
|
|
1139
1154
|
}
|
|
@@ -1161,9 +1176,7 @@ const readOpenReleasePullRequest = async (
|
|
|
1161
1176
|
repository,
|
|
1162
1177
|
'/pulls?state=open&base=main&per_page=100'
|
|
1163
1178
|
);
|
|
1164
|
-
const pull = pulls
|
|
1165
|
-
(candidate) => candidate.head.ref === 'changeset-release/main'
|
|
1166
|
-
);
|
|
1179
|
+
const pull = selectGeneratedReleasePullRequest(pulls);
|
|
1167
1180
|
if (!pull) {
|
|
1168
1181
|
return undefined;
|
|
1169
1182
|
}
|
|
@@ -35,7 +35,15 @@ export const topoSnapshotOutput = z.object({
|
|
|
35
35
|
|
|
36
36
|
export const DEFAULT_TOPO_HISTORY_LIMIT = 10;
|
|
37
37
|
export const LOCK_PATH = 'trails.lock';
|
|
38
|
-
const
|
|
38
|
+
const sourceExampleAppModule = fileURLToPath(
|
|
39
|
+
new URL('../app.ts', import.meta.url)
|
|
40
|
+
);
|
|
41
|
+
const bundledExampleAppModule = fileURLToPath(
|
|
42
|
+
new URL('app.js', import.meta.url)
|
|
43
|
+
);
|
|
44
|
+
const EXAMPLE_APP_MODULE = existsSync(sourceExampleAppModule)
|
|
45
|
+
? sourceExampleAppModule
|
|
46
|
+
: bundledExampleAppModule;
|
|
39
47
|
|
|
40
48
|
const uniqueExampleRootName = (name: string): string =>
|
|
41
49
|
`${name}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|