@ontrails/trails 0.2.0 → 0.2.2

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # trails
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`0ae6a5d`](https://github.com/outfitter-dev/trails/commit/0ae6a5de15fd36bff2997a163b2ce64772151514): Wait up to two minutes for npm metadata and dist-tags to converge during post-publish verification. Retain confirmed package proofs, bound in-flight npm probes by the same deadline, and fail immediately on registry access errors or newer tags without retrying publication.
8
+
9
+ ## 0.2.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [`d785a07`](https://github.com/outfitter-dev/trails/commit/d785a07b458899a7da14999e9b7856b8a7446e66): Align current installation examples with the 0.2.1 scaffold correction and keep Homebrew version checks explicit while preserving the 0.2.0 repair guidance.
14
+ - [`d785a07`](https://github.com/outfitter-dev/trails/commit/d785a07b458899a7da14999e9b7856b8a7446e66): Keep automatic release eligibility when canonical version generation updates the root Bun lockfile and bundled skill framework metadata, while retaining the existing release provenance checks and rejecting other plugin files.
15
+ - [`fdef61f`](https://github.com/outfitter-dev/trails/commit/fdef61feeef9b2250d537640db147cb9a9e484bd): Update package installation guidance to reflect the verified npm and Homebrew 0.2.0 release, with the published CLI scaffold repair linked from its README.
16
+ - [`fdef61f`](https://github.com/outfitter-dev/trails/commit/fdef61feeef9b2250d537640db147cb9a9e484bd): Generate an explicit `@types/node` development dependency in fresh standalone and workspace apps so Node built-in imports resolve in cold installs.
17
+ - [`ef3c802`](https://github.com/outfitter-dev/trails/commit/ef3c802f7e5b6ca92b32b11d76c26517ef388b13): Exclude unrelated `.worktrees` checkouts from lock round-trip copies while preserving tracked inputs and explicitly selected lock fixtures.
18
+
3
19
  ## 0.2.0
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -2,17 +2,19 @@
2
2
 
3
3
  Command-line tools for working with Trails projects.
4
4
 
5
- Use the canonical [Homebrew installation guide](../../docs/getting-started.md#installation) for installation, updates, and removal. Stable `0.2.0` installation requires the release assets and tap update; that handoff is pending. The formula requires Bun at runtime.
5
+ Use the canonical [Homebrew installation guide](../../docs/getting-started.md#installation) for installation, updates, and removal. The CLI requires Bun at runtime; use `trails --version` to check the installed release.
6
6
 
7
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.
8
8
 
9
- These commands target stable `0.2.0`. Run them after that version is published to npm.
9
+ These installation examples target Trails `0.2.1` on the normal npm release line.
10
10
 
11
11
  ```bash
12
- bunx @ontrails/trails@0.2.0 create my-app --permit '{"id":"local-dev","scopes":["project:write"]}'
13
- bunx @ontrails/trails@0.2.0 create my-app --workspace --permit '{"id":"local-dev","scopes":["project:write"]}'
12
+ bunx @ontrails/trails@0.2.1 create my-app --permit '{"id":"local-dev","scopes":["project:write"]}'
13
+ bunx @ontrails/trails@0.2.1 create my-app --workspace --permit '{"id":"local-dev","scopes":["project:write"]}'
14
14
  ```
15
15
 
16
+ Apps generated by the published `0.2.0` CLI need an explicit Node types dependency before typechecking. Follow the [0.2.0 scaffold repair](../../docs/getting-started.md#repair-apps-generated-by-020).
17
+
16
18
  Common workflows:
17
19
 
18
20
  - `trails create` starts a standalone app by default; `--workspace` creates a configured workspace with that app under `apps/<name>`. Generated surfaces live under `bin/`, app source stays side-effect-free, and `--dry-run` returns the complete write plan without touching disk. Workspace creation rejects targets nested beneath an existing configured workspace while allowing a child workspace beneath an ancestor standalone Config. On a rerun, a preserved app entry also rejects when its runtime-selected topo ID can be statically proven to conflict with `workspace.apps`; dynamic, indirect, and post-construction mutation forms remain preserved and are validated by runtime commands such as `trails compile`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/trails",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/outfitter-dev/trails.git",
@@ -33,25 +33,25 @@
33
33
  "dependencies": {
34
34
  "@clack/prompts": "^1.1.0",
35
35
  "@manypkg/get-packages": "1.1.3",
36
- "@ontrails/adapter-kit": "^0.2.0",
37
- "@ontrails/cli": "^0.2.0",
38
- "@ontrails/commander": "^0.2.0",
39
- "@ontrails/config": "^0.2.0",
40
- "@ontrails/core": "^0.2.0",
41
- "@ontrails/http": "^0.2.0",
42
- "@ontrails/mcp": "^0.2.0",
43
- "@ontrails/observability": "^0.2.0",
44
- "@ontrails/permits": "^0.2.0",
45
- "@ontrails/regrade": "^0.2.0",
46
- "@ontrails/source": "^0.2.0",
47
- "@ontrails/topography": "^0.2.0",
48
- "@ontrails/warden": "^0.2.0",
36
+ "@ontrails/adapter-kit": "^0.2.2",
37
+ "@ontrails/cli": "^0.2.2",
38
+ "@ontrails/commander": "^0.2.2",
39
+ "@ontrails/config": "^0.2.2",
40
+ "@ontrails/core": "^0.2.2",
41
+ "@ontrails/http": "^0.2.2",
42
+ "@ontrails/mcp": "^0.2.2",
43
+ "@ontrails/observability": "^0.2.2",
44
+ "@ontrails/permits": "^0.2.2",
45
+ "@ontrails/regrade": "^0.2.2",
46
+ "@ontrails/source": "^0.2.2",
47
+ "@ontrails/topography": "^0.2.2",
48
+ "@ontrails/warden": "^0.2.2",
49
49
  "commander": "^14.0.3",
50
50
  "typescript": "5.9.3",
51
51
  "zod": "^4.3.5"
52
52
  },
53
53
  "devDependencies": {
54
- "@ontrails/cloudflare": "^0.2.0",
55
- "@ontrails/testing": "^0.2.0"
54
+ "@ontrails/cloudflare": "^0.2.2",
55
+ "@ontrails/testing": "^0.2.2"
56
56
  }
57
57
  }
@@ -116,8 +116,6 @@ export {
116
116
  npmRegistryVersionView,
117
117
  npmRegistryView,
118
118
  registryPostureErrors,
119
- runRegistryPreflight,
120
- runRegistryPreflightCli,
121
119
  type PackageRegistryFacts,
122
120
  type PackageRegistryState,
123
121
  type RegistryCheckPhase,
@@ -127,6 +125,10 @@ export {
127
125
  type RegistryView,
128
126
  type RegistryWorkspace,
129
127
  } from './native-bun-registry.js';
128
+ export {
129
+ runRegistryPreflight,
130
+ runRegistryPreflightCli,
131
+ } from './native-bun-registry-preflight.js';
130
132
  export {
131
133
  collectReleaseNotesInput,
132
134
  dedupeReleaseChanges,
@@ -39,6 +39,7 @@ const isDisposablePath = (segments: readonly string[]): boolean =>
39
39
  part === '.tmp-tests' ||
40
40
  part === '.trails-tmp' ||
41
41
  part === '.turbo' ||
42
+ part === '.worktrees' ||
42
43
  (part === 'worktrees' && segments[index - 1] === '.claude') ||
43
44
  (part === 'notes' && segments[index - 1] === '.agents')
44
45
  );
@@ -0,0 +1,333 @@
1
+ /* oxlint-disable max-statements -- release preflight CLI with explicit reporting */
2
+ import { join, resolve } from 'node:path';
3
+
4
+ import {
5
+ checkRegistryPosture,
6
+ classifyPackageRegistryState,
7
+ createNpmRegistryVersionProofView,
8
+ createNpmRegistryView,
9
+ discoverRegistryWorkspaces,
10
+ factsFromRegistryResult,
11
+ formatDistTagSummary,
12
+ registryPostureErrors,
13
+ runNpmRegistryCommand,
14
+ } from './native-bun-registry.js';
15
+ import type {
16
+ RegistryPreflightOptions,
17
+ RegistryResult,
18
+ RegistryVersionProof,
19
+ RegistryVersionView,
20
+ RegistryView,
21
+ RegistryWorkspace,
22
+ } from './native-bun-registry.js';
23
+
24
+ const REPO_ROOT = resolve(process.cwd());
25
+
26
+ interface RegistryVerificationRuntime {
27
+ readonly now: () => number;
28
+ readonly sleep: (ms: number) => Promise<void>;
29
+ readonly timeoutMs: number;
30
+ }
31
+
32
+ interface RegistryVerificationReport {
33
+ readonly attempts: number;
34
+ readonly elapsedMs: number;
35
+ readonly results: readonly RegistryResult[];
36
+ readonly state: 'complete' | 'failed' | 'timeout';
37
+ }
38
+
39
+ type RegistryProbe = (
40
+ workspaces: readonly RegistryWorkspace[],
41
+ signal?: AbortSignal
42
+ ) => Promise<RegistryResult[]>;
43
+
44
+ const verificationRuntime: RegistryVerificationRuntime = {
45
+ now: () => performance.now(),
46
+ sleep: async (ms) => {
47
+ await Bun.sleep(ms);
48
+ },
49
+ timeoutMs: 120_000,
50
+ };
51
+
52
+ const USAGE = `Usage: bun scripts/check-registry-preflight.ts [options]
53
+
54
+ Read-only npm registry preflight for public @ontrails/* workspaces.
55
+
56
+ Options:
57
+ --tag <tag> Expected npm dist-tag. Defaults to .changeset/pre.json
58
+ tag while in prerelease mode, otherwise "latest".
59
+ --require-published Fail when any workspace package is missing from npm.
60
+ Use after publication to require exact metadata or
61
+ equivalent consumer package-fetch proof. Wait up to
62
+ two minutes for missing metadata or lagging tags.
63
+ -h, --help Show this help and exit.
64
+
65
+ Exit codes: 0 success, 1 registry posture failure, 2 arg-parse error.`;
66
+
67
+ const parseArgs = (argv: readonly string[]): RegistryPreflightOptions => {
68
+ let requirePublished = false;
69
+ let tag: string | undefined;
70
+
71
+ const needsValue = (flag: string, value: string | undefined): string => {
72
+ if (value === undefined || value.startsWith('--')) {
73
+ console.error(`${flag} requires a value`);
74
+ console.error(USAGE);
75
+ process.exit(2);
76
+ }
77
+ return value;
78
+ };
79
+
80
+ let i = 0;
81
+ while (i < argv.length) {
82
+ const arg = argv[i] as string;
83
+ if (arg === '--require-published') {
84
+ requirePublished = true;
85
+ } else if (arg === '--tag') {
86
+ i += 1;
87
+ tag = needsValue('--tag', argv[i]);
88
+ } else if (arg === '-h' || arg === '--help') {
89
+ console.log(USAGE);
90
+ process.exit(0);
91
+ } else {
92
+ console.error(`Unknown argument: ${arg}`);
93
+ console.error(USAGE);
94
+ process.exit(2);
95
+ }
96
+ i += 1;
97
+ }
98
+
99
+ return { requirePublished, tag };
100
+ };
101
+
102
+ const resolveDefaultTag = async (): Promise<string> => {
103
+ const prePath = join(REPO_ROOT, '.changeset', 'pre.json');
104
+ if (!(await Bun.file(prePath).exists())) {
105
+ return 'latest';
106
+ }
107
+ const pre = (await Bun.file(prePath).json()) as {
108
+ mode?: string;
109
+ tag?: string;
110
+ };
111
+ if (pre.mode !== 'pre') {
112
+ return 'latest';
113
+ }
114
+ if (typeof pre.tag === 'string' && pre.tag.length > 0) {
115
+ return pre.tag;
116
+ }
117
+ throw new Error(`${prePath} is in prerelease mode but has no tag`);
118
+ };
119
+
120
+ const formatTargetVersionStatus = (
121
+ proof: RegistryVersionProof | undefined,
122
+ versionPublished: boolean | undefined
123
+ ): string => {
124
+ if (proof?.kind === 'exact-metadata') {
125
+ return 'exact-version metadata available';
126
+ }
127
+ if (proof?.kind === 'consumer-pack') {
128
+ return 'exact-version metadata unavailable, consumer pack available';
129
+ }
130
+ if (proof?.kind === 'unavailable') {
131
+ return 'exact-version metadata and consumer pack unavailable';
132
+ }
133
+ if (versionPublished === true) {
134
+ return 'target version published';
135
+ }
136
+ if (versionPublished === false) {
137
+ return 'target version not published yet';
138
+ }
139
+ return 'target version publish state unknown';
140
+ };
141
+
142
+ const printResults = (
143
+ results: readonly RegistryResult[],
144
+ expectedTag: string
145
+ ): void => {
146
+ console.log(`Registry preflight for dist-tag "${expectedTag}"`);
147
+ for (const result of results) {
148
+ if (result.status === 'published') {
149
+ const targetStatus = formatTargetVersionStatus(
150
+ result.versionProof,
151
+ result.versionPublished
152
+ );
153
+ console.log(
154
+ `✓ ${result.name}@${result.workspaceVersion}: package exists, ${targetStatus} (registry version ${result.version}, expected ${expectedTag}=${result.expectedTagVersion ?? 'missing'}, tags ${formatDistTagSummary(result.distTags)})`
155
+ );
156
+ } else if (result.status === 'missing') {
157
+ console.log(
158
+ `• ${result.name}@${result.workspaceVersion}: first-time package candidate (not found on registry)`
159
+ );
160
+ } else {
161
+ console.log(`✗ ${result.name}: registry probe failed: ${result.error}`);
162
+ }
163
+ }
164
+ };
165
+
166
+ /**
167
+ * Wait for npm observations to converge after publication, retaining complete
168
+ * package proofs. The shared registry classifier remains the verdict owner;
169
+ * only missing versions/packages and lagging tags earn another read-only probe.
170
+ * The internal runtime seam makes the deadline and backoff testable without
171
+ * exposing release tuning flags or sleeping through regression tests.
172
+ */
173
+ export const verifyPublishedRegistry = async (
174
+ workspaces: readonly RegistryWorkspace[],
175
+ expectedTag: string,
176
+ probe: RegistryProbe,
177
+ runtime: RegistryVerificationRuntime = verificationRuntime
178
+ ): Promise<RegistryVerificationReport> => {
179
+ const start = runtime.now();
180
+ const deadline = start + runtime.timeoutMs;
181
+ const controller = new AbortController();
182
+ const timer = setTimeout(() => controller.abort(), runtime.timeoutMs);
183
+ const results = new Map<string, RegistryResult>();
184
+ let pending = workspaces;
185
+ let attempts = 0;
186
+ let delayMs = 5000;
187
+ const report = (
188
+ state: RegistryVerificationReport['state']
189
+ ): RegistryVerificationReport => ({
190
+ attempts,
191
+ elapsedMs: runtime.now() - start,
192
+ results: [...results.values()],
193
+ state,
194
+ });
195
+
196
+ try {
197
+ while (pending.length > 0) {
198
+ if (controller.signal.aborted || runtime.now() >= deadline) {
199
+ controller.abort();
200
+ return report('timeout');
201
+ }
202
+ attempts += 1;
203
+ const observed = await probe(pending, controller.signal);
204
+ const timedOut = controller.signal.aborted || runtime.now() >= deadline;
205
+ for (const result of observed) {
206
+ // Aborting npm must not erase the last useful propagation evidence.
207
+ if (
208
+ timedOut &&
209
+ result.status === 'inaccessible' &&
210
+ results.has(result.name)
211
+ ) {
212
+ continue;
213
+ }
214
+ results.set(result.name, result);
215
+ }
216
+ const pendingNames = new Set(
217
+ observed
218
+ .filter(
219
+ (result) =>
220
+ classifyPackageRegistryState(
221
+ factsFromRegistryResult(result, expectedTag)
222
+ ).kind !== 'complete'
223
+ )
224
+ .map(({ name }) => name)
225
+ );
226
+ pending = pending.filter(({ name }) => pendingNames.has(name));
227
+ if (pending.length === 0) {
228
+ return report('complete');
229
+ }
230
+ if (timedOut) {
231
+ controller.abort();
232
+ return report('timeout');
233
+ }
234
+ if (
235
+ registryPostureErrors(observed, expectedTag, 'ready').length > 0 ||
236
+ observed.some(
237
+ (result) =>
238
+ result.status === 'published' &&
239
+ factsFromRegistryResult(result, expectedTag).versionPublished ===
240
+ undefined
241
+ )
242
+ ) {
243
+ return report('failed');
244
+ }
245
+ const waitMs = Math.min(delayMs, deadline - runtime.now());
246
+ console.log(
247
+ `Waiting for registry propagation: ${pending.length}/${workspaces.length} packages pending; retrying in ${waitMs / 1000}s (${pending.map(({ name }) => name).join(', ')}).`
248
+ );
249
+ await runtime.sleep(waitMs);
250
+ delayMs = Math.min(delayMs * 2, 30_000);
251
+ }
252
+ return report('complete');
253
+ } finally {
254
+ clearTimeout(timer);
255
+ }
256
+ };
257
+
258
+ const createRegistryProbe =
259
+ (
260
+ expectedTag: string,
261
+ view: RegistryView | undefined,
262
+ versionView: RegistryVersionView | undefined
263
+ ): RegistryProbe =>
264
+ async (workspaces, signal) => {
265
+ if (view !== undefined) {
266
+ return versionView === undefined
267
+ ? checkRegistryPosture(workspaces, view, expectedTag)
268
+ : checkRegistryPosture(workspaces, view, versionView, expectedTag);
269
+ }
270
+ const runNpm = (args: readonly string[]) =>
271
+ runNpmRegistryCommand(args, signal);
272
+ return checkRegistryPosture(
273
+ workspaces,
274
+ createNpmRegistryView(runNpm),
275
+ createNpmRegistryVersionProofView(runNpm),
276
+ expectedTag
277
+ );
278
+ };
279
+
280
+ /** Run the read-only readiness check or bounded post-publish verification. */
281
+ export const runRegistryPreflight = async (
282
+ options: RegistryPreflightOptions,
283
+ view?: RegistryView,
284
+ versionView?: RegistryVersionView
285
+ ): Promise<number> => {
286
+ const expectedTag = options.tag ?? (await resolveDefaultTag());
287
+ const workspaces = await discoverRegistryWorkspaces();
288
+ const probe = createRegistryProbe(expectedTag, view, versionView);
289
+ const verification = options.requirePublished
290
+ ? await verifyPublishedRegistry(workspaces, expectedTag, probe)
291
+ : undefined;
292
+ const results = verification?.results ?? (await probe(workspaces));
293
+ printResults(results, expectedTag);
294
+ const errors = registryPostureErrors(
295
+ results,
296
+ expectedTag,
297
+ options.requirePublished ? 'published' : 'ready'
298
+ );
299
+ if (verification?.state === 'timeout') {
300
+ console.error(
301
+ `\nPost-publish registry verification timed out after ${Math.round(verification.elapsedMs / 1000)}s (${verification.attempts} probes). No publication was retried. Inspect the remaining registry observations before recovery.`
302
+ );
303
+ } else if (errors.length > 0) {
304
+ console.error(
305
+ options.requirePublished
306
+ ? '\nPost-publish registry verification failed; no publication was retried:'
307
+ : '\nRegistry preflight failed:'
308
+ );
309
+ }
310
+ if (errors.length > 0 || verification?.state === 'timeout') {
311
+ for (const error of errors) {
312
+ console.error(`- ${error}`);
313
+ }
314
+ return 1;
315
+ }
316
+ console.log('\nRegistry preflight passed.');
317
+ return 0;
318
+ };
319
+
320
+ export const runRegistryPreflightCli = async (
321
+ args: readonly string[] = process.argv.slice(2)
322
+ ): Promise<number> => {
323
+ try {
324
+ return await runRegistryPreflight(parseArgs(args));
325
+ } catch (error) {
326
+ console.error(error instanceof Error ? error.message : String(error));
327
+ return 1;
328
+ }
329
+ };
330
+
331
+ if (import.meta.main) {
332
+ process.exit(await runRegistryPreflightCli(process.argv.slice(2)));
333
+ }
@@ -1,4 +1,3 @@
1
- /* oxlint-disable max-statements -- release preflight CLI with explicit reporting */
2
1
  import { readdir } from 'node:fs/promises';
3
2
  import { join, relative, resolve } from 'node:path';
4
3
 
@@ -179,55 +178,6 @@ export const factsFromRegistryResult = (
179
178
  };
180
179
  };
181
180
 
182
- const USAGE = `Usage: bun scripts/check-registry-preflight.ts [options]
183
-
184
- Read-only npm registry preflight for public @ontrails/* workspaces.
185
-
186
- Options:
187
- --tag <tag> Expected npm dist-tag. Defaults to .changeset/pre.json
188
- tag while in prerelease mode, otherwise "latest".
189
- --require-published Fail when any workspace package is missing from npm.
190
- Use after publication to require exact metadata or
191
- equivalent consumer package-fetch proof.
192
- -h, --help Show this help and exit.
193
-
194
- Exit codes: 0 success, 1 registry posture failure, 2 arg-parse error.`;
195
-
196
- const parseArgs = (argv: readonly string[]): RegistryPreflightOptions => {
197
- let requirePublished = false;
198
- let tag: string | undefined;
199
-
200
- const needsValue = (flag: string, value: string | undefined): string => {
201
- if (value === undefined || value.startsWith('--')) {
202
- console.error(`${flag} requires a value`);
203
- console.error(USAGE);
204
- process.exit(2);
205
- }
206
- return value;
207
- };
208
-
209
- let i = 0;
210
- while (i < argv.length) {
211
- const arg = argv[i] as string;
212
- if (arg === '--require-published') {
213
- requirePublished = true;
214
- } else if (arg === '--tag') {
215
- i += 1;
216
- tag = needsValue('--tag', argv[i]);
217
- } else if (arg === '-h' || arg === '--help') {
218
- console.log(USAGE);
219
- process.exit(0);
220
- } else {
221
- console.error(`Unknown argument: ${arg}`);
222
- console.error(USAGE);
223
- process.exit(2);
224
- }
225
- i += 1;
226
- }
227
-
228
- return { requirePublished, tag };
229
- };
230
-
231
181
  const readJson = async <T>(path: string): Promise<T> => {
232
182
  const file = Bun.file(path);
233
183
  if (!(await file.exists())) {
@@ -244,21 +194,6 @@ const errorCode = (error: unknown): string | undefined => {
244
194
  return typeof code === 'string' ? code : undefined;
245
195
  };
246
196
 
247
- const resolveDefaultTag = async (): Promise<string> => {
248
- const prePath = join(REPO_ROOT, '.changeset', 'pre.json');
249
- if (!(await Bun.file(prePath).exists())) {
250
- return 'latest';
251
- }
252
- const pre = await readJson<{ mode?: string; tag?: string }>(prePath);
253
- if (pre.mode !== 'pre') {
254
- return 'latest';
255
- }
256
- if (typeof pre.tag === 'string' && pre.tag.length > 0) {
257
- return pre.tag;
258
- }
259
- throw new Error(`${prePath} is in prerelease mode but has no tag`);
260
- };
261
-
262
197
  const discoverWorkspaceDirs = async (
263
198
  repoRoot: string,
264
199
  patterns: readonly string[]
@@ -348,14 +283,21 @@ const readSpawnResult = async (
348
283
  return { exitCode, stderr, stdout };
349
284
  };
350
285
 
351
- const runNpmCommand: NpmCommandRunner = async (args) =>
352
- readSpawnResult(
286
+ /** Run a read-only npm registry command, aborting its subprocess at the verification deadline. */
287
+ export const runNpmRegistryCommand = async (
288
+ args: readonly string[],
289
+ signal?: AbortSignal
290
+ ): Promise<NpmCommandResult> => {
291
+ signal?.throwIfAborted();
292
+ return readSpawnResult(
353
293
  Bun.spawn(['npm', ...args], {
354
294
  stderr: 'pipe',
355
295
  stdin: 'ignore',
356
296
  stdout: 'pipe',
297
+ ...(signal === undefined ? {} : { killSignal: 'SIGKILL', signal }),
357
298
  })
358
299
  );
300
+ };
359
301
 
360
302
  const isNpmNotFoundOutput = (stdout: string, stderr: string): boolean => {
361
303
  const combined = `${stdout}\n${stderr}`;
@@ -453,7 +395,7 @@ const npmDistTagRegistryView = async (
453
395
  };
454
396
 
455
397
  export const createNpmRegistryView =
456
- (runNpm: NpmCommandRunner = runNpmCommand): RegistryView =>
398
+ (runNpm: NpmCommandRunner = runNpmRegistryCommand): RegistryView =>
457
399
  async (name) => {
458
400
  const { exitCode, stderr, stdout } = await runNpm([
459
401
  'view',
@@ -498,7 +440,9 @@ const unknownRegistryVersionView: RegistryVersionView = async () =>
498
440
  UNKNOWN_REGISTRY_VERSION_STATE.published;
499
441
 
500
442
  export const createNpmRegistryVersionProofView =
501
- (runNpm: NpmCommandRunner = runNpmCommand): RegistryVersionProofView =>
443
+ (
444
+ runNpm: NpmCommandRunner = runNpmRegistryCommand
445
+ ): RegistryVersionProofView =>
502
446
  async (name, version) => {
503
447
  const { exitCode, stderr, stdout } = await runNpm([
504
448
  'view',
@@ -548,7 +492,7 @@ export const createNpmRegistryVersionProofView =
548
492
  };
549
493
 
550
494
  export const createNpmRegistryVersionView = (
551
- runNpm: NpmCommandRunner = runNpmCommand
495
+ runNpm: NpmCommandRunner = runNpmRegistryCommand
552
496
  ): RegistryVersionView => {
553
497
  const proofView = createNpmRegistryVersionProofView(runNpm);
554
498
  return async (name, version) => {
@@ -738,111 +682,3 @@ export const formatDistTagSummary = (
738
682
  SUMMARY_DIST_TAGS.map((tag) => `${tag}=${distTags[tag] ?? 'missing'}`).join(
739
683
  ', '
740
684
  );
741
-
742
- const formatTargetVersionStatus = (
743
- proof: RegistryVersionProof | undefined,
744
- versionPublished: boolean | undefined
745
- ): string => {
746
- if (proof?.kind === 'exact-metadata') {
747
- return 'exact-version metadata available';
748
- }
749
- if (proof?.kind === 'consumer-pack') {
750
- return 'exact-version metadata unavailable, consumer pack available';
751
- }
752
- if (proof?.kind === 'unavailable') {
753
- return 'exact-version metadata and consumer pack unavailable';
754
- }
755
- if (versionPublished === true) {
756
- return 'target version published';
757
- }
758
- if (versionPublished === false) {
759
- return 'target version not published yet';
760
- }
761
- return 'target version publish state unknown';
762
- };
763
-
764
- const printResults = (
765
- results: readonly RegistryResult[],
766
- expectedTag: string
767
- ): void => {
768
- console.log(`Registry preflight for dist-tag "${expectedTag}"`);
769
- for (const result of results) {
770
- if (result.status === 'published') {
771
- const targetStatus = formatTargetVersionStatus(
772
- result.versionProof,
773
- result.versionPublished
774
- );
775
- console.log(
776
- `✓ ${result.name}@${result.workspaceVersion}: package exists, ${targetStatus} (registry version ${result.version}, expected ${expectedTag}=${result.expectedTagVersion ?? 'missing'}, tags ${formatDistTagSummary(result.distTags)})`
777
- );
778
- } else if (result.status === 'missing') {
779
- console.log(
780
- `• ${result.name}@${result.workspaceVersion}: first-time package candidate (not found on registry)`
781
- );
782
- } else {
783
- console.log(`✗ ${result.name}: registry probe failed: ${result.error}`);
784
- }
785
- }
786
- };
787
-
788
- const normalizeRegistryPreflightViews = (
789
- view: RegistryView | undefined,
790
- versionView: RegistryVersionView | undefined
791
- ): {
792
- readonly versionView: RegistryVersionProbeView;
793
- readonly view: RegistryView;
794
- } => {
795
- if (view === undefined) {
796
- return {
797
- versionView: npmRegistryVersionProofView,
798
- view: npmRegistryView,
799
- };
800
- }
801
- return { versionView: versionView ?? unknownRegistryVersionView, view };
802
- };
803
-
804
- export const runRegistryPreflight = async (
805
- options: RegistryPreflightOptions,
806
- view?: RegistryView,
807
- versionView?: RegistryVersionView
808
- ): Promise<number> => {
809
- const registryViews = normalizeRegistryPreflightViews(view, versionView);
810
- const expectedTag = options.tag ?? (await resolveDefaultTag());
811
- const workspaces = await discoverRegistryWorkspaces();
812
- const results = await checkRegistryPosture(
813
- workspaces,
814
- registryViews.view,
815
- registryViews.versionView,
816
- expectedTag
817
- );
818
- printResults(results, expectedTag);
819
- const errors = registryPostureErrors(
820
- results,
821
- expectedTag,
822
- options.requirePublished ? 'published' : 'ready'
823
- );
824
- if (errors.length > 0) {
825
- console.error('\nRegistry preflight failed:');
826
- for (const error of errors) {
827
- console.error(`- ${error}`);
828
- }
829
- return 1;
830
- }
831
- console.log('\nRegistry preflight passed.');
832
- return 0;
833
- };
834
-
835
- export const runRegistryPreflightCli = async (
836
- args: readonly string[] = process.argv.slice(2)
837
- ): Promise<number> => {
838
- try {
839
- return await runRegistryPreflight(parseArgs(args));
840
- } catch (error) {
841
- console.error(error instanceof Error ? error.message : String(error));
842
- return 1;
843
- }
844
- };
845
-
846
- if (import.meta.main) {
847
- process.exit(await runRegistryPreflightCli(process.argv.slice(2)));
848
- }
@@ -728,6 +728,14 @@ const evaluateGeneratedReleaseDiff = (
728
728
  let hasPackageVersionFile = false;
729
729
 
730
730
  for (const file of changedFiles) {
731
+ if (
732
+ (file.path === 'bun.lock' ||
733
+ file.path === 'plugin/skills/trails/SKILL.md') &&
734
+ file.status === 'M'
735
+ ) {
736
+ continue;
737
+ }
738
+
731
739
  if (file.path === '.changeset/pre.json' && file.status === 'M') {
732
740
  continue;
733
741
  }
@@ -95,6 +95,12 @@ const loadScaffoldVersions = async (
95
95
  'devDependencies',
96
96
  rootPackageJsonPath
97
97
  ),
98
+ nodeTypes: requireValue(
99
+ devDeps['@types/node'],
100
+ '@types/node',
101
+ 'devDependencies',
102
+ rootPackageJsonPath
103
+ ),
98
104
  oxfmt: requireValue(
99
105
  devDeps['oxfmt'],
100
106
  'oxfmt',
@@ -4,6 +4,7 @@ export const scaffoldDependencyVersions = {
4
4
  bunTypes: '^1.3.11',
5
5
  commander: '^14.0.3',
6
6
  lefthook: '^2.1.1',
7
+ nodeTypes: '^25.5.0',
7
8
  oxfmt: '0.47.0',
8
9
  oxlint: '1.62.0',
9
10
  typescript: '^5.9.3',
@@ -92,6 +92,7 @@ const generateAppPackageJson = (name: string): string => {
92
92
  Object.entries({
93
93
  '@ontrails/trails': ontrailsPackageRange,
94
94
  '@types/bun': scaffoldDependencyVersions.bunTypes,
95
+ '@types/node': scaffoldDependencyVersions.nodeTypes,
95
96
  oxfmt: scaffoldDependencyVersions.oxfmt,
96
97
  oxlint: scaffoldDependencyVersions.oxlint,
97
98
  typescript: scaffoldDependencyVersions.typescript,