@ontrails/trails 1.0.0-beta.43 → 1.0.0-beta.46

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.
@@ -43,6 +43,7 @@ export type RegistryResult =
43
43
  readonly status: 'published';
44
44
  readonly version: string;
45
45
  readonly versionPublished: boolean | undefined;
46
+ readonly versionProof?: RegistryVersionProof | undefined;
46
47
  readonly workspaceVersion: string;
47
48
  }
48
49
  | {
@@ -82,18 +83,20 @@ export interface PackageRegistryFacts {
82
83
  readonly error?: string | undefined;
83
84
  }
84
85
 
86
+ /** Consumer-facing evidence for an exact package version. */
87
+ export type RegistryVersionProof =
88
+ | { readonly kind: 'consumer-pack'; readonly published: true }
89
+ | { readonly kind: 'exact-metadata'; readonly published: true }
90
+ | { readonly kind: 'unavailable'; readonly published: false };
91
+
85
92
  /**
86
93
  * Classify a package's registry state from two orthogonal facts — whether the
87
94
  * target version is published, and where the dist-tag points relative to it —
88
95
  * plus reachability. Members are mutually exclusive by construction.
89
96
  *
90
- * `versionPublished` is read strictly only in the behind-tag case: a behind tag
91
- * becomes a `needs-tag-repair` only when the target is known published (`true`);
92
- * `undefined` or `false` route to `needs-publish`, preserving the conservative
93
- * release-policy behavior. When the tag already points at the target, an
94
- * unprobed (`undefined`) state counts as published and yields `complete`, which
95
- * matches the policy `versionPublished ?? true` default. `undefined` means the
96
- * exact-version probe was not run, including policy inputs and compatibility
97
+ * `complete` requires affirmative consumer proof. A matching dist-tag cannot
98
+ * substitute for exact-version metadata or an equivalent package fetch.
99
+ * `undefined` means the consumer probe was not run, including compatibility
97
100
  * callers that supply an injected registry view without a version probe.
98
101
  */
99
102
  export const classifyPackageRegistryState = (
@@ -120,9 +123,9 @@ export const classifyPackageRegistryState = (
120
123
  return { currentTagVersion: expectedTagVersion, kind: 'tag-points-ahead' };
121
124
  }
122
125
  if (tagAtTarget) {
123
- return versionPublished === false
124
- ? { kind: 'needs-publish' }
125
- : { kind: 'complete' };
126
+ return versionPublished === true
127
+ ? { kind: 'complete' }
128
+ : { kind: 'needs-publish' };
126
129
  }
127
130
  if (versionPublished === true) {
128
131
  return { currentTagVersion: expectedTagVersion, kind: 'needs-tag-repair' };
@@ -131,13 +134,16 @@ export const classifyPackageRegistryState = (
131
134
  };
132
135
 
133
136
  /** Map a registry probe result into the classifier's fact shape. */
134
- const factsFromResult = (result: RegistryResult): PackageRegistryFacts => {
137
+ export const factsFromRegistryResult = (
138
+ result: RegistryResult
139
+ ): PackageRegistryFacts => {
135
140
  if (result.status === 'published') {
136
141
  return {
137
142
  expectedTagVersion: result.expectedTagVersion,
138
143
  status: 'published',
139
144
  targetVersion: result.workspaceVersion,
140
- versionPublished: result.versionPublished,
145
+ versionPublished:
146
+ result.versionProof?.published ?? result.versionPublished,
141
147
  };
142
148
  }
143
149
  if (result.status === 'inaccessible') {
@@ -165,7 +171,8 @@ Options:
165
171
  --tag <tag> Expected npm dist-tag. Defaults to .changeset/pre.json
166
172
  tag while in prerelease mode, otherwise "latest".
167
173
  --require-published Fail when any workspace package is missing from npm.
168
- Use after publication to verify every package exists.
174
+ Use after publication to require exact metadata or
175
+ equivalent consumer package-fetch proof.
169
176
  -h, --help Show this help and exit.
170
177
 
171
178
  Exit codes: 0 success, 1 registry posture failure, 2 arg-parse error.`;
@@ -459,12 +466,23 @@ export type RegistryVersionView = (
459
466
  version: string
460
467
  ) => Promise<boolean | undefined>;
461
468
 
469
+ /** Probe with evidence that distinguishes metadata from package fetch proof. */
470
+ export type RegistryVersionProofView = (
471
+ name: string,
472
+ version: string
473
+ ) => Promise<RegistryVersionProof>;
474
+
475
+ export type RegistryVersionProbeView = (
476
+ name: string,
477
+ version: string
478
+ ) => Promise<boolean | RegistryVersionProof | undefined>;
479
+
462
480
  const UNKNOWN_REGISTRY_VERSION_STATE: { readonly published?: boolean } = {};
463
481
  const unknownRegistryVersionView: RegistryVersionView = async () =>
464
482
  UNKNOWN_REGISTRY_VERSION_STATE.published;
465
483
 
466
- export const createNpmRegistryVersionView =
467
- (runNpm: NpmCommandRunner = runNpmCommand): RegistryVersionView =>
484
+ export const createNpmRegistryVersionProofView =
485
+ (runNpm: NpmCommandRunner = runNpmCommand): RegistryVersionProofView =>
468
486
  async (name, version) => {
469
487
  const { exitCode, stderr, stdout } = await runNpm([
470
488
  'view',
@@ -474,12 +492,14 @@ export const createNpmRegistryVersionView =
474
492
  ]);
475
493
 
476
494
  if (exitCode === 0) {
477
- return JSON.parse(stdout.trim()) === version;
495
+ return JSON.parse(stdout.trim()) === version
496
+ ? { kind: 'exact-metadata', published: true }
497
+ : { kind: 'unavailable', published: false };
478
498
  }
479
- if (isNpmExactVersionMissingOutput(stdout, stderr)) {
480
- return false;
481
- }
482
- if (!isNpmNotFoundOutput(stdout, stderr)) {
499
+ if (
500
+ !isNpmExactVersionMissingOutput(stdout, stderr) &&
501
+ !isNpmNotFoundOutput(stdout, stderr)
502
+ ) {
483
503
  throw new Error(
484
504
  stderr.trim() || `npm view failed for ${name}@${version}`
485
505
  );
@@ -496,21 +516,35 @@ export const createNpmRegistryVersionView =
496
516
  packResult.stdout,
497
517
  name,
498
518
  version
499
- );
519
+ )
520
+ ? { kind: 'consumer-pack', published: true }
521
+ : { kind: 'unavailable', published: false };
500
522
  }
501
523
  if (isNpmExactVersionMissingOutput(packResult.stdout, packResult.stderr)) {
502
- return false;
524
+ return { kind: 'unavailable', published: false };
503
525
  }
504
526
  if (isNpmNotFoundOutput(packResult.stdout, packResult.stderr)) {
505
- return false;
527
+ return { kind: 'unavailable', published: false };
506
528
  }
507
529
  throw new Error(
508
530
  packResult.stderr.trim() || `npm pack failed for ${name}@${version}`
509
531
  );
510
532
  };
511
533
 
534
+ export const createNpmRegistryVersionView = (
535
+ runNpm: NpmCommandRunner = runNpmCommand
536
+ ): RegistryVersionView => {
537
+ const proofView = createNpmRegistryVersionProofView(runNpm);
538
+ return async (name, version) => {
539
+ const proof = await proofView(name, version);
540
+ return proof.published;
541
+ };
542
+ };
543
+
512
544
  export const npmRegistryVersionView: RegistryVersionView =
513
545
  createNpmRegistryVersionView();
546
+ export const npmRegistryVersionProofView: RegistryVersionProofView =
547
+ createNpmRegistryVersionProofView();
514
548
 
515
549
  /** Run async tasks with a bounded number in flight, preserving input order. */
516
550
  const mapBounded = async <T, R>(
@@ -536,7 +570,7 @@ const mapBounded = async <T, R>(
536
570
  const checkWorkspaceRegistryPosture = async (
537
571
  workspace: RegistryWorkspace,
538
572
  view: RegistryView,
539
- versionView: RegistryVersionView,
573
+ versionView: RegistryVersionProbeView,
540
574
  expectedTag: string
541
575
  ): Promise<RegistryResult> => {
542
576
  try {
@@ -549,13 +583,19 @@ const checkWorkspaceRegistryPosture = async (
549
583
  };
550
584
  }
551
585
  const distTags = registry['dist-tags'] ?? {};
586
+ const versionProbe = await versionView(workspace.name, workspace.version);
587
+ const versionProof =
588
+ typeof versionProbe === 'object' ? versionProbe : undefined;
589
+ const versionPublished =
590
+ typeof versionProbe === 'object' ? versionProbe.published : versionProbe;
552
591
  return {
553
592
  distTags,
554
593
  expectedTagVersion: distTags[expectedTag],
555
594
  name: workspace.name,
556
595
  status: 'published',
557
596
  version: registry.version ?? '(unknown)',
558
- versionPublished: await versionView(workspace.name, workspace.version),
597
+ ...(versionProof === undefined ? {} : { versionProof }),
598
+ versionPublished,
559
599
  workspaceVersion: workspace.version,
560
600
  };
561
601
  } catch (error) {
@@ -569,14 +609,14 @@ const checkWorkspaceRegistryPosture = async (
569
609
  };
570
610
 
571
611
  type CheckRegistryPostureArgs =
572
- | readonly [versionView: RegistryVersionView, expectedTag: string]
612
+ | readonly [versionView: RegistryVersionProbeView, expectedTag: string]
573
613
  | readonly [expectedTag: string];
574
614
 
575
615
  const normalizeCheckRegistryPostureArgs = (
576
616
  args: CheckRegistryPostureArgs
577
617
  ): {
578
618
  readonly expectedTag: string;
579
- readonly versionView: RegistryVersionView;
619
+ readonly versionView: RegistryVersionProbeView;
580
620
  } => {
581
621
  if (args.length === 1) {
582
622
  return { expectedTag: args[0], versionView: unknownRegistryVersionView };
@@ -592,7 +632,7 @@ export function checkRegistryPosture(
592
632
  export function checkRegistryPosture(
593
633
  workspaces: readonly RegistryWorkspace[],
594
634
  view: RegistryView,
595
- versionView: RegistryVersionView,
635
+ versionView: RegistryVersionProbeView,
596
636
  expectedTag: string
597
637
  ): Promise<RegistryResult[]>;
598
638
  export async function checkRegistryPosture(
@@ -624,6 +664,19 @@ const normalizeRegistryCheckPhase = (
624
664
  return phaseOrRequirePublished ? 'published' : 'ready';
625
665
  };
626
666
 
667
+ const targetVersionFailure = (result: RegistryResult): string => {
668
+ if (result.status !== 'published') {
669
+ return 'is not published';
670
+ }
671
+ if (result.versionProof?.kind === 'unavailable') {
672
+ return 'lacks exact-version metadata and consumer pack proof';
673
+ }
674
+ if (result.versionPublished === undefined) {
675
+ return 'publish state was not probed';
676
+ }
677
+ return 'is not published';
678
+ };
679
+
627
680
  export const registryPostureErrors = (
628
681
  results: readonly RegistryResult[],
629
682
  expectedTag: string,
@@ -632,7 +685,7 @@ export const registryPostureErrors = (
632
685
  const phase = normalizeRegistryCheckPhase(phaseOrRequirePublished);
633
686
  const errors: string[] = [];
634
687
  for (const result of results) {
635
- const state = classifyPackageRegistryState(factsFromResult(result));
688
+ const state = classifyPackageRegistryState(factsFromRegistryResult(result));
636
689
  if (state.kind === 'registry-inaccessible') {
637
690
  errors.push(`${result.name}: registry probe failed: ${state.error}`);
638
691
  continue;
@@ -649,12 +702,8 @@ export const registryPostureErrors = (
649
702
  if (state.kind === 'first-time-package') {
650
703
  errors.push(`${result.name}: package is missing from the registry`);
651
704
  } else if (state.kind === 'needs-publish') {
652
- const targetState =
653
- result.status === 'published' && result.versionPublished === undefined
654
- ? 'publish state was not probed'
655
- : 'is not published';
656
705
  errors.push(
657
- `${result.name}: target version ${result.workspaceVersion} ${targetState}`
706
+ `${result.name}: target version ${result.workspaceVersion} ${targetVersionFailure(result)}`
658
707
  );
659
708
  } else if (state.kind === 'needs-tag-repair') {
660
709
  errors.push(
@@ -673,8 +722,18 @@ export const formatDistTagSummary = (
673
722
  );
674
723
 
675
724
  const formatTargetVersionStatus = (
725
+ proof: RegistryVersionProof | undefined,
676
726
  versionPublished: boolean | undefined
677
727
  ): string => {
728
+ if (proof?.kind === 'exact-metadata') {
729
+ return 'exact-version metadata available';
730
+ }
731
+ if (proof?.kind === 'consumer-pack') {
732
+ return 'exact-version metadata unavailable, consumer pack available';
733
+ }
734
+ if (proof?.kind === 'unavailable') {
735
+ return 'exact-version metadata and consumer pack unavailable';
736
+ }
678
737
  if (versionPublished === true) {
679
738
  return 'target version published';
680
739
  }
@@ -691,7 +750,10 @@ const printResults = (
691
750
  console.log(`Registry preflight for dist-tag "${expectedTag}"`);
692
751
  for (const result of results) {
693
752
  if (result.status === 'published') {
694
- const targetStatus = formatTargetVersionStatus(result.versionPublished);
753
+ const targetStatus = formatTargetVersionStatus(
754
+ result.versionProof,
755
+ result.versionPublished
756
+ );
695
757
  console.log(
696
758
  `✓ ${result.name}@${result.workspaceVersion}: package exists, ${targetStatus} (registry version ${result.version}, expected ${expectedTag}=${result.expectedTagVersion ?? 'missing'}, tags ${formatDistTagSummary(result.distTags)})`
697
759
  );
@@ -709,11 +771,14 @@ const normalizeRegistryPreflightViews = (
709
771
  view: RegistryView | undefined,
710
772
  versionView: RegistryVersionView | undefined
711
773
  ): {
712
- readonly versionView: RegistryVersionView;
774
+ readonly versionView: RegistryVersionProbeView;
713
775
  readonly view: RegistryView;
714
776
  } => {
715
777
  if (view === undefined) {
716
- return { versionView: npmRegistryVersionView, view: npmRegistryView };
778
+ return {
779
+ versionView: npmRegistryVersionProofView,
780
+ view: npmRegistryView,
781
+ };
717
782
  }
718
783
  return { versionView: versionView ?? unknownRegistryVersionView, view };
719
784
  };
@@ -999,13 +999,13 @@ const registryPackagesFromResults = (
999
999
  const readRegistryPackages = async (
1000
1000
  distTag: string
1001
1001
  ): Promise<readonly ReleasePolicyRegistryPackage[]> => {
1002
- const { checkRegistryPosture, npmRegistryView, npmRegistryVersionView } =
1002
+ const { checkRegistryPosture, npmRegistryView, npmRegistryVersionProofView } =
1003
1003
  await import('./native-bun-registry.js');
1004
1004
  const workspaces = await discoverRegistryWorkspaces(repoRoot);
1005
1005
  const results = await checkRegistryPosture(
1006
1006
  workspaces,
1007
1007
  npmRegistryView,
1008
- npmRegistryVersionView,
1008
+ npmRegistryVersionProofView,
1009
1009
  distTag
1010
1010
  );
1011
1011
  return registryPackagesFromResults(results);
@@ -0,0 +1,47 @@
1
+ const structuredOutputValues = new Set(['json', 'jsonl']);
2
+
3
+ /** Whether the current CLI invocation reserves output channels for JSON. */
4
+ export const usesStructuredCliOutput = (
5
+ argv: readonly string[],
6
+ env: Readonly<Record<string, string | undefined>>
7
+ ): boolean => {
8
+ const optionTerminator = argv.indexOf('--');
9
+ const optionArguments =
10
+ optionTerminator === -1 ? argv : argv.slice(0, optionTerminator);
11
+ if (
12
+ optionArguments.includes('--json') ||
13
+ optionArguments.includes('--jsonl')
14
+ ) {
15
+ return true;
16
+ }
17
+
18
+ let outputValue: string | undefined;
19
+ for (const [index, argument] of optionArguments.entries()) {
20
+ if (argument === '--output') {
21
+ const next = optionArguments[index + 1];
22
+ outputValue =
23
+ next === undefined || next.startsWith('-') ? undefined : next;
24
+ continue;
25
+ }
26
+ const longAttached = argument.match(/^--output=(.*)$/)?.[1];
27
+ if (longAttached !== undefined) {
28
+ outputValue = longAttached === '' ? undefined : longAttached;
29
+ continue;
30
+ }
31
+ const shortOutput = argument.match(/^-q*o=?(.*)$/)?.[1];
32
+ if (shortOutput !== undefined) {
33
+ if (shortOutput !== '') {
34
+ outputValue = shortOutput;
35
+ continue;
36
+ }
37
+ const next = optionArguments[index + 1];
38
+ outputValue =
39
+ next === undefined || next.startsWith('-') ? undefined : next;
40
+ }
41
+ }
42
+
43
+ if (outputValue !== undefined) {
44
+ return structuredOutputValues.has(outputValue);
45
+ }
46
+ return env['TRAILS_JSON'] === '1' || env['TRAILS_JSONL'] === '1';
47
+ };
@@ -228,6 +228,7 @@ const OXFMTRC_CONTENT = `{
228
228
  "arrowParens": "always",
229
229
  "proseWrap": "never",
230
230
  "printWidth": 80,
231
+ "ignorePatterns": ["**/.trails/regrade/history/**"],
231
232
  }
232
233
  `;
233
234