@ontrails/trails 1.0.0-beta.29 → 1.0.0-beta.30

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,26 @@
1
1
  # trails
2
2
 
3
+ ## 1.0.0-beta.30
4
+
5
+ ### Patch Changes
6
+
7
+ - 5510807: Treat npm `ETARGET` exact-version probes as unpublished target versions during registry readiness checks.
8
+ - b0ff8b9: Teach the registry preflight to verify first-time package publishes when npm's package summary lags behind dist-tags and tarball availability.
9
+ - @ontrails/commander@1.0.0-beta.30
10
+ - @ontrails/adapter-kit@1.0.0-beta.30
11
+ - @ontrails/cli@1.0.0-beta.30
12
+ - @ontrails/config@1.0.0-beta.30
13
+ - @ontrails/core@1.0.0-beta.30
14
+ - @ontrails/http@1.0.0-beta.30
15
+ - @ontrails/mcp@1.0.0-beta.30
16
+ - @ontrails/observe@1.0.0-beta.30
17
+ - @ontrails/permits@1.0.0-beta.30
18
+ - @ontrails/regrade@1.0.0-beta.30
19
+ - @ontrails/topographer@1.0.0-beta.30
20
+ - @ontrails/tracing@1.0.0-beta.30
21
+ - @ontrails/warden@1.0.0-beta.30
22
+ - @ontrails/wayfinder@1.0.0-beta.30
23
+
3
24
  ## 1.0.0-beta.29
4
25
 
5
26
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ontrails/trails",
3
- "version": "1.0.0-beta.29",
3
+ "version": "1.0.0-beta.30",
4
4
  "bin": {
5
5
  "trails": "./bin/trails.ts"
6
6
  },
@@ -27,25 +27,25 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@clack/prompts": "^1.1.0",
30
- "@ontrails/adapter-kit": "^1.0.0-beta.29",
31
- "@ontrails/cli": "^1.0.0-beta.29",
32
- "@ontrails/commander": "^1.0.0-beta.29",
33
- "@ontrails/config": "^1.0.0-beta.29",
34
- "@ontrails/core": "^1.0.0-beta.29",
35
- "@ontrails/http": "^1.0.0-beta.29",
36
- "@ontrails/mcp": "^1.0.0-beta.29",
37
- "@ontrails/observe": "^1.0.0-beta.29",
38
- "@ontrails/permits": "^1.0.0-beta.29",
39
- "@ontrails/regrade": "^1.0.0-beta.29",
40
- "@ontrails/topographer": "^1.0.0-beta.29",
41
- "@ontrails/tracing": "^1.0.0-beta.29",
42
- "@ontrails/warden": "^1.0.0-beta.29",
43
- "@ontrails/wayfinder": "^1.0.0-beta.29",
30
+ "@ontrails/adapter-kit": "^1.0.0-beta.30",
31
+ "@ontrails/cli": "^1.0.0-beta.30",
32
+ "@ontrails/commander": "^1.0.0-beta.30",
33
+ "@ontrails/config": "^1.0.0-beta.30",
34
+ "@ontrails/core": "^1.0.0-beta.30",
35
+ "@ontrails/http": "^1.0.0-beta.30",
36
+ "@ontrails/mcp": "^1.0.0-beta.30",
37
+ "@ontrails/observe": "^1.0.0-beta.30",
38
+ "@ontrails/permits": "^1.0.0-beta.30",
39
+ "@ontrails/regrade": "^1.0.0-beta.30",
40
+ "@ontrails/topographer": "^1.0.0-beta.30",
41
+ "@ontrails/tracing": "^1.0.0-beta.30",
42
+ "@ontrails/warden": "^1.0.0-beta.30",
43
+ "@ontrails/wayfinder": "^1.0.0-beta.30",
44
44
  "commander": "^14.0.3",
45
45
  "typescript": "^5.9.3",
46
46
  "zod": "^4.3.5"
47
47
  },
48
48
  "devDependencies": {
49
- "@ontrails/testing": "^1.0.0-beta.29"
49
+ "@ontrails/testing": "^1.0.0-beta.30"
50
50
  }
51
51
  }
@@ -304,27 +304,143 @@ export const discoverRegistryWorkspaces = async (
304
304
 
305
305
  export type RegistryView = (name: string) => Promise<NpmView | null>;
306
306
 
307
- export const npmRegistryView: RegistryView = async (name) => {
308
- const proc = Bun.spawn(
309
- ['npm', 'view', name, 'name', 'version', 'dist-tags', '--json'],
310
- { stderr: 'pipe', stdin: 'ignore', stdout: 'pipe' }
311
- );
307
+ export interface NpmCommandResult {
308
+ readonly exitCode: number;
309
+ readonly stderr: string;
310
+ readonly stdout: string;
311
+ }
312
+
313
+ export type NpmCommandRunner = (
314
+ args: readonly string[]
315
+ ) => Promise<NpmCommandResult>;
316
+
317
+ const readSpawnResult = async (
318
+ proc: Bun.Subprocess<'ignore', 'pipe', 'pipe'>
319
+ ): Promise<NpmCommandResult> => {
312
320
  const [stdout, stderr, exitCode] = await Promise.all([
313
321
  new Response(proc.stdout).text(),
314
322
  new Response(proc.stderr).text(),
315
323
  proc.exited,
316
324
  ]);
325
+ return { exitCode, stderr, stdout };
326
+ };
317
327
 
318
- if (exitCode === 0) {
319
- return JSON.parse(stdout) as NpmView;
320
- }
328
+ const runNpmCommand: NpmCommandRunner = async (args) =>
329
+ readSpawnResult(
330
+ Bun.spawn(['npm', ...args], {
331
+ stderr: 'pipe',
332
+ stdin: 'ignore',
333
+ stdout: 'pipe',
334
+ })
335
+ );
336
+
337
+ const isNpmNotFoundOutput = (stdout: string, stderr: string): boolean => {
321
338
  const combined = `${stdout}\n${stderr}`;
322
- if (combined.includes('E404') || combined.includes('404 Not Found')) {
339
+ return combined.includes('E404') || combined.includes('404 Not Found');
340
+ };
341
+
342
+ const isNpmExactVersionMissingOutput = (
343
+ stdout: string,
344
+ stderr: string
345
+ ): boolean => {
346
+ const combined = `${stdout}\n${stderr}`;
347
+ return (
348
+ combined.includes('ETARGET') ||
349
+ combined.includes('No matching version found')
350
+ );
351
+ };
352
+
353
+ export const parseNpmDistTagListOutput = (
354
+ stdout: string
355
+ ): Record<string, string> => {
356
+ const distTags: Record<string, string> = {};
357
+ for (const line of stdout.split(/\r?\n/)) {
358
+ const trimmed = line.trim();
359
+ if (trimmed.length === 0) {
360
+ continue;
361
+ }
362
+ const separator = trimmed.indexOf(':');
363
+ if (separator < 1) {
364
+ continue;
365
+ }
366
+ const tag = trimmed.slice(0, separator).trim();
367
+ const version = trimmed.slice(separator + 1).trim();
368
+ if (tag.length > 0 && version.length > 0) {
369
+ distTags[tag] = version;
370
+ }
371
+ }
372
+ return distTags;
373
+ };
374
+
375
+ export const parseNpmPackDryRunPublishedVersion = (
376
+ stdout: string,
377
+ name: string,
378
+ version: string
379
+ ): boolean => {
380
+ const parsed = JSON.parse(stdout.trim()) as unknown;
381
+ if (!Array.isArray(parsed)) {
382
+ return false;
383
+ }
384
+ return parsed.some((entry) => {
385
+ if (typeof entry !== 'object' || entry === null) {
386
+ return false;
387
+ }
388
+ const candidate = entry as {
389
+ readonly id?: unknown;
390
+ readonly name?: unknown;
391
+ readonly version?: unknown;
392
+ };
393
+ return (
394
+ candidate.id === `${name}@${version}` ||
395
+ (candidate.name === name && candidate.version === version)
396
+ );
397
+ });
398
+ };
399
+
400
+ const npmDistTagRegistryView = async (
401
+ name: string,
402
+ runNpm: NpmCommandRunner
403
+ ): Promise<NpmView | null> => {
404
+ const { exitCode, stderr, stdout } = await runNpm(['dist-tag', 'ls', name]);
405
+ if (exitCode !== 0) {
406
+ if (isNpmNotFoundOutput(stdout, stderr)) {
407
+ return null;
408
+ }
409
+ throw new Error(stderr.trim() || `npm dist-tag ls failed for ${name}`);
410
+ }
411
+
412
+ const distTags = parseNpmDistTagListOutput(stdout);
413
+ const version =
414
+ distTags['latest'] ?? distTags['beta'] ?? Object.values(distTags)[0];
415
+ if (version === undefined) {
323
416
  return null;
324
417
  }
325
- throw new Error(stderr.trim() || `npm view failed for ${name}`);
418
+ return { 'dist-tags': distTags, name, version };
326
419
  };
327
420
 
421
+ export const createNpmRegistryView =
422
+ (runNpm: NpmCommandRunner = runNpmCommand): RegistryView =>
423
+ async (name) => {
424
+ const { exitCode, stderr, stdout } = await runNpm([
425
+ 'view',
426
+ name,
427
+ 'name',
428
+ 'version',
429
+ 'dist-tags',
430
+ '--json',
431
+ ]);
432
+
433
+ if (exitCode === 0) {
434
+ return JSON.parse(stdout) as NpmView;
435
+ }
436
+ if (isNpmNotFoundOutput(stdout, stderr)) {
437
+ return npmDistTagRegistryView(name, runNpm);
438
+ }
439
+ throw new Error(stderr.trim() || `npm view failed for ${name}`);
440
+ };
441
+
442
+ export const npmRegistryView: RegistryView = createNpmRegistryView();
443
+
328
444
  /** Probe whether an exact `name@version` is published. The missing fact that
329
445
  * a tag/version summary alone cannot answer. */
330
446
  export type RegistryVersionView = (
@@ -336,29 +452,54 @@ const UNKNOWN_REGISTRY_VERSION_STATE: { readonly published?: boolean } = {};
336
452
  const unknownRegistryVersionView: RegistryVersionView = async () =>
337
453
  UNKNOWN_REGISTRY_VERSION_STATE.published;
338
454
 
339
- export const npmRegistryVersionView: RegistryVersionView = async (
340
- name,
341
- version
342
- ) => {
343
- const proc = Bun.spawn(
344
- ['npm', 'view', `${name}@${version}`, 'version', '--json'],
345
- { stderr: 'pipe', stdin: 'ignore', stdout: 'pipe' }
346
- );
347
- const [stdout, stderr, exitCode] = await Promise.all([
348
- new Response(proc.stdout).text(),
349
- new Response(proc.stderr).text(),
350
- proc.exited,
351
- ]);
455
+ export const createNpmRegistryVersionView =
456
+ (runNpm: NpmCommandRunner = runNpmCommand): RegistryVersionView =>
457
+ async (name, version) => {
458
+ const { exitCode, stderr, stdout } = await runNpm([
459
+ 'view',
460
+ `${name}@${version}`,
461
+ 'version',
462
+ '--json',
463
+ ]);
464
+
465
+ if (exitCode === 0) {
466
+ return JSON.parse(stdout.trim()) === version;
467
+ }
468
+ if (isNpmExactVersionMissingOutput(stdout, stderr)) {
469
+ return false;
470
+ }
471
+ if (!isNpmNotFoundOutput(stdout, stderr)) {
472
+ throw new Error(
473
+ stderr.trim() || `npm view failed for ${name}@${version}`
474
+ );
475
+ }
352
476
 
353
- if (exitCode === 0) {
354
- return JSON.parse(stdout.trim()) === version;
355
- }
356
- const combined = `${stdout}\n${stderr}`;
357
- if (combined.includes('E404') || combined.includes('404 Not Found')) {
358
- return false;
359
- }
360
- throw new Error(stderr.trim() || `npm view failed for ${name}@${version}`);
361
- };
477
+ const packResult = await runNpm([
478
+ 'pack',
479
+ `${name}@${version}`,
480
+ '--dry-run',
481
+ '--json',
482
+ ]);
483
+ if (packResult.exitCode === 0) {
484
+ return parseNpmPackDryRunPublishedVersion(
485
+ packResult.stdout,
486
+ name,
487
+ version
488
+ );
489
+ }
490
+ if (isNpmExactVersionMissingOutput(packResult.stdout, packResult.stderr)) {
491
+ return false;
492
+ }
493
+ if (isNpmNotFoundOutput(packResult.stdout, packResult.stderr)) {
494
+ return false;
495
+ }
496
+ throw new Error(
497
+ packResult.stderr.trim() || `npm pack failed for ${name}@${version}`
498
+ );
499
+ };
500
+
501
+ export const npmRegistryVersionView: RegistryVersionView =
502
+ createNpmRegistryVersionView();
362
503
 
363
504
  /** Run async tasks with a bounded number in flight, preserving input order. */
364
505
  const mapBounded = async <T, R>(