@principles/pd-cli 1.115.0 → 1.116.0

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.
Files changed (44) hide show
  1. package/dist/commands/diagnose.d.ts.map +1 -1
  2. package/dist/commands/diagnose.js +153 -132
  3. package/dist/commands/diagnose.js.map +1 -1
  4. package/dist/commands/runtime-features.d.ts.map +1 -1
  5. package/dist/commands/runtime-features.js +2 -7
  6. package/dist/commands/runtime-features.js.map +1 -1
  7. package/dist/commands/runtime-internalization-integrity-repair.d.ts.map +1 -1
  8. package/dist/commands/runtime-internalization-integrity-repair.js +15 -31
  9. package/dist/commands/runtime-internalization-integrity-repair.js.map +1 -1
  10. package/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  11. package/dist/commands/runtime-internalization-run-once.js +246 -326
  12. package/dist/commands/runtime-internalization-run-once.js.map +1 -1
  13. package/dist/commands/runtime-recovery.d.ts.map +1 -1
  14. package/dist/commands/runtime-recovery.js +9 -8
  15. package/dist/commands/runtime-recovery.js.map +1 -1
  16. package/dist/services/__tests__/cli-output.test.d.ts +18 -0
  17. package/dist/services/__tests__/cli-output.test.d.ts.map +1 -0
  18. package/dist/services/__tests__/cli-output.test.js +103 -0
  19. package/dist/services/__tests__/cli-output.test.js.map +1 -0
  20. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts +18 -0
  21. package/dist/services/__tests__/runtime-adapter-resolver.test.d.ts.map +1 -0
  22. package/dist/services/__tests__/runtime-adapter-resolver.test.js +651 -0
  23. package/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -0
  24. package/dist/services/cli-output.d.ts +61 -0
  25. package/dist/services/cli-output.d.ts.map +1 -0
  26. package/dist/services/cli-output.js +72 -0
  27. package/dist/services/cli-output.js.map +1 -0
  28. package/dist/services/runtime-adapter-resolver.d.ts +105 -0
  29. package/dist/services/runtime-adapter-resolver.d.ts.map +1 -0
  30. package/dist/services/runtime-adapter-resolver.js +188 -0
  31. package/dist/services/runtime-adapter-resolver.js.map +1 -0
  32. package/package.json +1 -1
  33. package/src/commands/diagnose.ts +146 -138
  34. package/src/commands/runtime-features.ts +2 -6
  35. package/src/commands/runtime-internalization-integrity-repair.ts +16 -28
  36. package/src/commands/runtime-internalization-run-once.ts +242 -353
  37. package/src/commands/runtime-recovery.ts +9 -7
  38. package/src/services/__tests__/cli-output.test.ts +130 -0
  39. package/src/services/__tests__/runtime-adapter-resolver.test.ts +772 -0
  40. package/src/services/cli-output.ts +95 -0
  41. package/src/services/runtime-adapter-resolver.ts +339 -0
  42. package/tests/commands/diagnose.test.ts +7 -3
  43. package/tests/commands/runtime-internalization-run-once.test.ts +11 -0
  44. package/tests/commands/runtime-recovery.test.ts +27 -4
@@ -3,6 +3,7 @@ import { createRecoverySweepService } from '@principles/core/runtime-v2';
3
3
  import { resolveWorkspaceDir } from '../resolve-workspace.js';
4
4
  import { createRemediationResult, remediationAction } from './remediation-output.js';
5
5
  import type { RemediationResult } from './remediation-output.js';
6
+ import { emitResult, emitFlagConflict } from '../services/cli-output.js';
6
7
 
7
8
  interface RecoverySweepOptions {
8
9
  workspace?: string;
@@ -40,8 +41,10 @@ function formatTextOutput(output: RemediationResult): string {
40
41
 
41
42
  export async function handleRuntimeRecoverySweep(opts: RecoverySweepOptions): Promise<void> {
42
43
  if (opts.dryRun && opts.confirm) {
43
- console.error('Error: --dry-run and --confirm are mutually exclusive');
44
- process.exitCode = 1;
44
+ // PRI-432: Bug fix — flag conflict now emits JSON when --json is set
45
+ // (previously always used console.error, violating CLI Operator Gate rule #1)
46
+ const exitCode = emitFlagConflict({ json: opts.json ?? false });
47
+ process.exit(exitCode);
45
48
  return;
46
49
  }
47
50
  const workspaceDir = opts.workspace ? path.resolve(opts.workspace) : resolveWorkspaceDir();
@@ -92,11 +95,10 @@ export async function handleRuntimeRecoverySweep(opts: RecoverySweepOptions): Pr
92
95
  safeToConfirm: isDryRun && expiredLeaseTaskIds.length > 0,
93
96
  });
94
97
 
95
- if (opts.json) {
96
- console.log(JSON.stringify(output, null, 2));
97
- } else {
98
- console.log(formatTextOutput(output));
99
- }
98
+ emitResult(output, {
99
+ json: opts.json ?? false,
100
+ formatText: formatTextOutput,
101
+ });
100
102
 
101
103
  if (expiredLeaseTaskIds.length > 0 && isDryRun) {
102
104
  process.exitCode = 1;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * PRI-432: Tests for shared CLI output module.
3
+ *
4
+ * Extracts common output patterns from:
5
+ * - runtime-features.ts (JSON/text emit + exit code)
6
+ * - runtime-recovery.ts (dry-run/confirm conflict + JSON/text emit)
7
+ * - runtime-internalization-integrity-repair.ts (conflict + error catch + JSON/text emit)
8
+ *
9
+ * TDD flow: these tests are RED until cli-output.ts is implemented.
10
+ *
11
+ * ERR refs:
12
+ * - ERR-001 (no any): all types explicit
13
+ * - ERR-005 (no as bypass): no type casts
14
+ * - ERR-009 (fail-loud): emitError returns exit code, does not swallow
15
+ * - ERR-002 (graceful degradation with reason): all error paths include reason + nextAction
16
+ */
17
+
18
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
19
+
20
+ const { emitResult, emitFlagConflict, emitError } = await import('../cli-output.js');
21
+
22
+ describe('cli-output module (PRI-432)', () => {
23
+ let consoleLogSpy: ReturnType<typeof vi.spyOn>;
24
+ let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
25
+
26
+ beforeEach(() => {
27
+ vi.clearAllMocks();
28
+ consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
29
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
30
+ });
31
+
32
+ // ── emitResult ──────────────────────────────────────────────────────────
33
+
34
+ describe('emitResult', () => {
35
+ it('emits JSON to stdout when json=true', () => {
36
+ const output = { status: 'ok', count: 5 };
37
+ const formatText = (o: typeof output) => `Status: ${o.status}`;
38
+
39
+ emitResult(output, { json: true, formatText });
40
+
41
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1);
42
+ const emitted = JSON.parse(consoleLogSpy.mock.calls[0][0] as string);
43
+ expect(emitted).toEqual(output);
44
+ });
45
+
46
+ it('emits text to stdout when json=false', () => {
47
+ const output = { status: 'ok', count: 5 };
48
+ const formatText = (o: typeof output) => `Status: ${o.status}`;
49
+
50
+ emitResult(output, { json: false, formatText });
51
+
52
+ expect(consoleLogSpy).toHaveBeenCalledWith('Status: ok');
53
+ });
54
+
55
+ it('uses formatText callback for text output', () => {
56
+ const output = { status: 'failed', reason: 'bad config' };
57
+ const formatText = (o: typeof output) => `ERROR: ${o.reason}`;
58
+
59
+ emitResult(output, { json: false, formatText });
60
+
61
+ expect(consoleLogSpy).toHaveBeenCalledWith('ERROR: bad config');
62
+ });
63
+ });
64
+
65
+ // ── emitFlagConflict ────────────────────────────────────────────────────
66
+
67
+ describe('emitFlagConflict', () => {
68
+ it('emits JSON error to stdout when json=true', () => {
69
+ const exitCode = emitFlagConflict({ json: true });
70
+
71
+ expect(exitCode).toBe(1);
72
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1);
73
+ const emitted = JSON.parse(consoleLogSpy.mock.calls[0][0] as string);
74
+ expect(emitted.ok).toBe(false);
75
+ expect(emitted.reason).toContain('mutually exclusive');
76
+ expect(emitted.nextAction).toContain('--dry-run');
77
+ });
78
+
79
+ it('emits text error to stderr when json=false', () => {
80
+ const exitCode = emitFlagConflict({ json: false });
81
+
82
+ expect(exitCode).toBe(1);
83
+ expect(consoleErrorSpy).toHaveBeenCalledTimes(1);
84
+ const msg = consoleErrorSpy.mock.calls[0][0] as string;
85
+ expect(msg).toContain('mutually exclusive');
86
+ });
87
+
88
+ it('always returns exit code 1', () => {
89
+ expect(emitFlagConflict({ json: true })).toBe(1);
90
+ expect(emitFlagConflict({ json: false })).toBe(1);
91
+ });
92
+ });
93
+
94
+ // ── emitError ───────────────────────────────────────────────────────────
95
+
96
+ describe('emitError', () => {
97
+ it('emits JSON error to stdout when json=true', () => {
98
+ const err = new Error('DB connection failed');
99
+ const exitCode = emitError(err, { json: true, nextAction: 'Check DB connectivity' });
100
+
101
+ expect(exitCode).toBe(1);
102
+ expect(consoleLogSpy).toHaveBeenCalledTimes(1);
103
+ const emitted = JSON.parse(consoleLogSpy.mock.calls[0][0] as string);
104
+ expect(emitted.ok).toBe(false);
105
+ expect(emitted.reason).toBe('DB connection failed');
106
+ expect(emitted.nextAction).toBe('Check DB connectivity');
107
+ });
108
+
109
+ it('emits text error to stderr when json=false', () => {
110
+ const err = new Error('DB connection failed');
111
+ const exitCode = emitError(err, { json: false, nextAction: 'Check DB connectivity' });
112
+
113
+ expect(exitCode).toBe(1);
114
+ expect(consoleErrorSpy).toHaveBeenCalledWith('Error: DB connection failed');
115
+ });
116
+
117
+ it('handles non-Error throwables via String()', () => {
118
+ const exitCode = emitError('string error', { json: true, nextAction: 'retry' });
119
+
120
+ expect(exitCode).toBe(1);
121
+ const emitted = JSON.parse(consoleLogSpy.mock.calls[0][0] as string);
122
+ expect(emitted.reason).toBe('string error');
123
+ });
124
+
125
+ it('always returns exit code 1', () => {
126
+ expect(emitError(new Error('x'), { json: true, nextAction: 'y' })).toBe(1);
127
+ expect(emitError(new Error('x'), { json: false, nextAction: 'y' })).toBe(1);
128
+ });
129
+ });
130
+ });