dialekt 0.1.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.
- package/README.md +62 -0
- package/TESTING.md +66 -0
- package/dist/cli/main.d.mts +1 -0
- package/dist/cli/main.mjs +412 -0
- package/dist/formatters-De4Q-X1d.mjs +577 -0
- package/dist/index.d.mts +329 -0
- package/dist/index.mjs +60 -0
- package/package.json +39 -0
- package/pnpm-workspace.yaml +7 -0
- package/src/adapter/types.test.ts +98 -0
- package/src/adapter/types.ts +73 -0
- package/src/benchmark/metrics.test.ts +180 -0
- package/src/benchmark/metrics.ts +69 -0
- package/src/benchmark/report.test.ts +129 -0
- package/src/benchmark/report.ts +21 -0
- package/src/benchmark/runner.test.ts +162 -0
- package/src/benchmark/runner.ts +27 -0
- package/src/cli/commands/add.test.ts +267 -0
- package/src/cli/commands/add.ts +123 -0
- package/src/cli/commands/benchmark.test.ts +346 -0
- package/src/cli/commands/benchmark.ts +148 -0
- package/src/cli/commands/languages.test.ts +127 -0
- package/src/cli/commands/languages.ts +42 -0
- package/src/cli/commands/missing.test.ts +256 -0
- package/src/cli/commands/missing.ts +88 -0
- package/src/cli/commands/translate.test.ts +384 -0
- package/src/cli/commands/translate.ts +106 -0
- package/src/cli/commands/unused.test.ts +192 -0
- package/src/cli/commands/unused.ts +87 -0
- package/src/cli/commands/validate.test.ts +245 -0
- package/src/cli/commands/validate.ts +96 -0
- package/src/cli/config-resolution.test.ts +99 -0
- package/src/cli/config-resolution.ts +29 -0
- package/src/cli/format.test.ts +117 -0
- package/src/cli/format.ts +205 -0
- package/src/cli/formatters.test.ts +186 -0
- package/src/cli/formatters.ts +350 -0
- package/src/cli/main.ts +31 -0
- package/src/config/define-config.test.ts +66 -0
- package/src/config/define-config.ts +5 -0
- package/src/config/load-config.test.ts +35 -0
- package/src/config/load-config.ts +21 -0
- package/src/config/types.test.ts +101 -0
- package/src/config/types.ts +28 -0
- package/src/index.ts +56 -0
- package/src/keys/flatten.test.ts +111 -0
- package/src/keys/flatten.ts +41 -0
- package/src/sdk/file-io.test.ts +139 -0
- package/src/sdk/file-io.ts +21 -0
- package/src/sdk/node-layer.test.ts +54 -0
- package/src/sdk/node-layer.ts +10 -0
- package/src/sdk/php-array-reader.test.ts +114 -0
- package/src/sdk/php-array-reader.ts +26 -0
- package/src/translation/chunking.test.ts +118 -0
- package/src/translation/chunking.ts +57 -0
- package/src/translation/missing-keys.test.ts +179 -0
- package/src/translation/missing-keys.ts +36 -0
- package/src/translation/model-registry.test.ts +54 -0
- package/src/translation/model-registry.ts +43 -0
- package/src/translation/one-shot-strategy.test.ts +259 -0
- package/src/translation/one-shot-strategy.ts +48 -0
- package/src/translation/orchestrator.test.ts +276 -0
- package/src/translation/orchestrator.ts +83 -0
- package/src/translation/prompt.test.ts +149 -0
- package/src/translation/prompt.ts +42 -0
- package/src/translation/tool-loop-strategy.test.ts +279 -0
- package/src/translation/tool-loop-strategy.ts +68 -0
- package/src/translation/types.test.ts +37 -0
- package/src/translation/types.ts +21 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/tsdown.config.ts +7 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal formatting core utilities for the dialekt CLI output.
|
|
3
|
+
*
|
|
4
|
+
* Two output modes:
|
|
5
|
+
* - `pretty` — lush human-readable output with colours and grouping (TTY only)
|
|
6
|
+
* - `json` — single compact JSON document for AI agents / machines
|
|
7
|
+
*
|
|
8
|
+
* stdout is the data contract in every mode; status / banners go to stderr.
|
|
9
|
+
* All decoration is gated behind `isTTY` so the output is never mojibake-prone
|
|
10
|
+
* when piped or consumed by another process.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ─── Output format ──────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export type OutputFormat = 'pretty' | 'json';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Environment variables that signal dialekt is running inside an AI agent.
|
|
19
|
+
* When any is set (truthy), or stdout is not a TTY, JSON mode is the default.
|
|
20
|
+
*/
|
|
21
|
+
export const AGENT_ENV_VARS = [
|
|
22
|
+
'CLAUDE_CODE',
|
|
23
|
+
'CLAUDECODE',
|
|
24
|
+
'CURSOR',
|
|
25
|
+
'CURSOR_TRACE_ID',
|
|
26
|
+
'DEVIN',
|
|
27
|
+
'GEMINI_CLI',
|
|
28
|
+
'AGENT_TASK_ID',
|
|
29
|
+
'AIDER_CHAT',
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolves the output format from explicit flag and environment.
|
|
34
|
+
* Precedence: explicit `--format` > auto-detection.
|
|
35
|
+
*
|
|
36
|
+
* Auto-detection picks `json` when stdout is not a TTY or an agent env var
|
|
37
|
+
* is present; otherwise `pretty`.
|
|
38
|
+
*/
|
|
39
|
+
export function detectFormat(explicit?: OutputFormat | undefined): OutputFormat {
|
|
40
|
+
if (explicit !== undefined) return explicit;
|
|
41
|
+
if (!process.stdout.isTTY) return 'json';
|
|
42
|
+
if (AGENT_ENV_VARS.some((k) => process.env[k])) return 'json';
|
|
43
|
+
return 'pretty';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── ANSI colours ────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const C = {
|
|
49
|
+
reset: '\x1b[0m',
|
|
50
|
+
bold: '\x1b[1m',
|
|
51
|
+
dim: '\x1b[2m',
|
|
52
|
+
red: '\x1b[31m',
|
|
53
|
+
green: '\x1b[32m',
|
|
54
|
+
yellow: '\x1b[33m',
|
|
55
|
+
blue: '\x1b[34m',
|
|
56
|
+
cyan: '\x1b[36m',
|
|
57
|
+
white: '\x1b[37m',
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
function isTty(): boolean {
|
|
61
|
+
return process.stdout.isTTY === true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Wraps text in ANSI codes only when stdout is a TTY; otherwise returns it bare. */
|
|
65
|
+
export function color(text: string, ...codes: string[]): string {
|
|
66
|
+
if (!isTty()) return text;
|
|
67
|
+
return `${codes.join('')}${text}${C.reset}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ─── Box-drawing glyphs ──────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
interface Glyphs {
|
|
73
|
+
hLine: string;
|
|
74
|
+
vLine: string;
|
|
75
|
+
cornerTL: string;
|
|
76
|
+
cornerTR: string;
|
|
77
|
+
cornerBL: string;
|
|
78
|
+
cornerBR: string;
|
|
79
|
+
teeRight: string;
|
|
80
|
+
teeLeft: string;
|
|
81
|
+
teeDown: string;
|
|
82
|
+
teeUp: string;
|
|
83
|
+
cross: string;
|
|
84
|
+
bullet: string;
|
|
85
|
+
arrow: string;
|
|
86
|
+
check: string;
|
|
87
|
+
crossMark: string;
|
|
88
|
+
warn: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const PRETTY_GLYPHS: Glyphs = {
|
|
92
|
+
hLine: String.fromCharCode(0x2500),
|
|
93
|
+
vLine: String.fromCharCode(0x2502),
|
|
94
|
+
cornerTL: String.fromCharCode(0x250C),
|
|
95
|
+
cornerTR: String.fromCharCode(0x2510),
|
|
96
|
+
cornerBL: String.fromCharCode(0x2514),
|
|
97
|
+
cornerBR: String.fromCharCode(0x2518),
|
|
98
|
+
teeRight: String.fromCharCode(0x251C),
|
|
99
|
+
teeLeft: String.fromCharCode(0x2524),
|
|
100
|
+
teeDown: String.fromCharCode(0x252C),
|
|
101
|
+
teeUp: String.fromCharCode(0x2534),
|
|
102
|
+
cross: String.fromCharCode(0x253C),
|
|
103
|
+
bullet: String.fromCharCode(0x2022),
|
|
104
|
+
arrow: String.fromCharCode(0x2192),
|
|
105
|
+
check: String.fromCharCode(0x2713),
|
|
106
|
+
crossMark: String.fromCharCode(0x2717),
|
|
107
|
+
warn: String.fromCharCode(0x26A0),
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const ASCII_GLYPHS: Glyphs = {
|
|
111
|
+
hLine: '-',
|
|
112
|
+
vLine: '|',
|
|
113
|
+
cornerTL: '+',
|
|
114
|
+
cornerTR: '+',
|
|
115
|
+
cornerBL: '+',
|
|
116
|
+
cornerBR: '+',
|
|
117
|
+
teeRight: '+',
|
|
118
|
+
teeLeft: '+',
|
|
119
|
+
teeDown: '+',
|
|
120
|
+
teeUp: '+',
|
|
121
|
+
cross: '+',
|
|
122
|
+
bullet: '*',
|
|
123
|
+
arrow: '>',
|
|
124
|
+
check: '+',
|
|
125
|
+
crossMark: 'x',
|
|
126
|
+
warn: '!',
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export function glyphs(): Glyphs {
|
|
130
|
+
return isTty() ? PRETTY_GLYPHS : ASCII_GLYPHS;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ─── Table helpers ───────────────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
export function drawTable(
|
|
136
|
+
headers: readonly string[],
|
|
137
|
+
rows: readonly (readonly string[])[]
|
|
138
|
+
): string {
|
|
139
|
+
const g = glyphs();
|
|
140
|
+
const colWidths = headers.map((h, i) =>
|
|
141
|
+
Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length))
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const pad = (text: string, width: number) => text.padEnd(width);
|
|
145
|
+
|
|
146
|
+
const hLine = g.cornerTL +
|
|
147
|
+
colWidths.map((w) => g.hLine.repeat(w + 2)).join(g.teeDown) +
|
|
148
|
+
g.cornerTR;
|
|
149
|
+
|
|
150
|
+
const headerRow = g.vLine +
|
|
151
|
+
headers.map((h, i) => ` ${color(pad(h, colWidths[i]!), C.bold)} `).join(g.vLine) +
|
|
152
|
+
g.vLine;
|
|
153
|
+
|
|
154
|
+
const separator = g.teeRight +
|
|
155
|
+
colWidths.map((w) => g.hLine.repeat(w + 2)).join(g.cross) +
|
|
156
|
+
g.teeLeft;
|
|
157
|
+
|
|
158
|
+
const dataRows = rows.map((row) =>
|
|
159
|
+
g.vLine +
|
|
160
|
+
row.map((cell, i) => ` ${pad(cell, colWidths[i]!)} `).join(g.vLine) +
|
|
161
|
+
g.vLine
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const bottomLine = g.cornerBL +
|
|
165
|
+
colWidths.map((w) => g.hLine.repeat(w + 2)).join(g.teeUp) +
|
|
166
|
+
g.cornerBR;
|
|
167
|
+
|
|
168
|
+
return [hLine, headerRow, separator, ...dataRows, bottomLine].join('\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── Banner / header helpers ─────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
export function banner(title: string): string {
|
|
174
|
+
const g = glyphs();
|
|
175
|
+
const line = g.hLine.repeat(Math.max(title.length + 4, 40));
|
|
176
|
+
return `${color(line, C.dim)}\n ${color(title, C.bold + C.cyan)}\n${color(line, C.dim)}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function sectionHeader(label: string): string {
|
|
180
|
+
const g = glyphs();
|
|
181
|
+
return `\n${color(`${g.arrow} ${label}`, C.bold + C.cyan)}`;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function success(text: string): string {
|
|
185
|
+
const g = glyphs();
|
|
186
|
+
return `${color(`${g.check} ${text}`, C.green)}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function failure(text: string): string {
|
|
190
|
+
const g = glyphs();
|
|
191
|
+
return `${color(`${g.crossMark} ${text}`, C.red)}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function warning(text: string): string {
|
|
195
|
+
const g = glyphs();
|
|
196
|
+
return `${color(`${g.warn} ${text}`, C.yellow)}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function info(text: string): string {
|
|
200
|
+
return color(text, C.dim);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function keyValue(key: string, value: string): string {
|
|
204
|
+
return ` ${color(key, C.bold)} ${value}`;
|
|
205
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
formatMissingKeys,
|
|
4
|
+
formatUnusedKeys,
|
|
5
|
+
formatValidate,
|
|
6
|
+
formatLanguages,
|
|
7
|
+
formatTranslate,
|
|
8
|
+
formatAdd,
|
|
9
|
+
formatBenchmark,
|
|
10
|
+
formatError,
|
|
11
|
+
} from './formatters.js';
|
|
12
|
+
|
|
13
|
+
describe('formatMissingKeys', () => {
|
|
14
|
+
it('returns JSON array in json mode', () => {
|
|
15
|
+
const entries = [{ adapter: 'a', locale: 'de', resource: 'r', key: 'k' }];
|
|
16
|
+
const result = formatMissingKeys(entries, 'json');
|
|
17
|
+
expect(JSON.parse(result)).toEqual(entries);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns success message when empty in pretty mode', () => {
|
|
21
|
+
const result = formatMissingKeys([], 'pretty');
|
|
22
|
+
expect(result).toContain('complete');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('groups by adapter, locale, resource in pretty mode', () => {
|
|
26
|
+
const entries = [
|
|
27
|
+
{ adapter: 'a1', locale: 'de', resource: 'auth', key: 'k1' },
|
|
28
|
+
{ adapter: 'a1', locale: 'de', resource: 'auth', key: 'k2' },
|
|
29
|
+
{ adapter: 'a1', locale: 'fr', resource: 'auth', key: 'k3' },
|
|
30
|
+
];
|
|
31
|
+
const result = formatMissingKeys(entries, 'pretty');
|
|
32
|
+
expect(result).toContain('a1');
|
|
33
|
+
expect(result).toContain('de');
|
|
34
|
+
expect(result).toContain('fr');
|
|
35
|
+
expect(result).toContain('auth');
|
|
36
|
+
expect(result).toContain('k1');
|
|
37
|
+
expect(result).toContain('k2');
|
|
38
|
+
expect(result).toContain('k3');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('formatUnusedKeys', () => {
|
|
43
|
+
it('returns JSON array in json mode', () => {
|
|
44
|
+
const entries = [{ adapter: 'a', locale: 'en', resource: 'r', key: 'old' }];
|
|
45
|
+
const result = formatUnusedKeys(entries, 'json');
|
|
46
|
+
expect(JSON.parse(result)).toEqual(entries);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('returns success message when empty in pretty mode', () => {
|
|
50
|
+
const result = formatUnusedKeys([], 'pretty');
|
|
51
|
+
expect(result).toContain('referenced');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('groups entries in pretty mode', () => {
|
|
55
|
+
const entries = [{ adapter: 'a', locale: 'en', resource: 'r', key: 'old' }];
|
|
56
|
+
const result = formatUnusedKeys(entries, 'pretty');
|
|
57
|
+
expect(result).toContain('a');
|
|
58
|
+
expect(result).toContain('old');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('formatValidate', () => {
|
|
63
|
+
it('returns JSON in json mode', () => {
|
|
64
|
+
const result = formatValidate({ passing: true, entries: [] }, 'json');
|
|
65
|
+
const parsed = JSON.parse(result);
|
|
66
|
+
expect(parsed.passing).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns success message when passing in pretty mode', () => {
|
|
70
|
+
const result = formatValidate({ passing: true, entries: [] }, 'pretty');
|
|
71
|
+
expect(result).toContain('up to date');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('returns table when failing in pretty mode', () => {
|
|
75
|
+
const result = formatValidate(
|
|
76
|
+
{ passing: false, entries: [{ adapter: 'a', locale: 'de', resource: 'r', count: 3 }] },
|
|
77
|
+
'pretty',
|
|
78
|
+
);
|
|
79
|
+
expect(result).toContain('Missing');
|
|
80
|
+
expect(result).toContain('a');
|
|
81
|
+
expect(result).toContain('de');
|
|
82
|
+
expect(result).toContain('3');
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('formatLanguages', () => {
|
|
87
|
+
it('returns JSON in json mode', () => {
|
|
88
|
+
const result = formatLanguages([{ adapter: 'a', locales: ['en', 'de'] }], 'json');
|
|
89
|
+
expect(JSON.parse(result)).toEqual([{ adapter: 'a', locales: ['en', 'de'] }]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('returns warning when empty in pretty mode', () => {
|
|
93
|
+
const result = formatLanguages([], 'pretty');
|
|
94
|
+
expect(result).toContain('No adapters');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('lists adapters and locales in pretty mode', () => {
|
|
98
|
+
const result = formatLanguages([{ adapter: 'laravel', locales: ['en', 'de'] }], 'pretty');
|
|
99
|
+
expect(result).toContain('laravel');
|
|
100
|
+
expect(result).toContain('en');
|
|
101
|
+
expect(result).toContain('de');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('formatTranslate', () => {
|
|
106
|
+
it('returns JSON in json mode', () => {
|
|
107
|
+
const result = formatTranslate({ success: true, message: 'Done' }, 'json');
|
|
108
|
+
expect(JSON.parse(result)).toEqual({ success: true, message: 'Done' });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('returns success text in pretty mode', () => {
|
|
112
|
+
const result = formatTranslate({ success: true, message: 'Done' }, 'pretty');
|
|
113
|
+
expect(result).toContain('Done');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('includes stats in pretty mode', () => {
|
|
117
|
+
const result = formatTranslate(
|
|
118
|
+
{ success: true, message: 'Done', stats: { adaptersProcessed: 1, localesTranslated: 2, keysTranslated: 5 } },
|
|
119
|
+
'pretty',
|
|
120
|
+
);
|
|
121
|
+
expect(result).toContain('Adapters:');
|
|
122
|
+
expect(result).toContain('1');
|
|
123
|
+
expect(result).toContain('Locales:');
|
|
124
|
+
expect(result).toContain('2');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('returns failure text in pretty mode', () => {
|
|
128
|
+
const result = formatTranslate({ success: false, message: 'Failed' }, 'pretty');
|
|
129
|
+
expect(result).toContain('Failed');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('formatAdd', () => {
|
|
134
|
+
it('returns JSON in json mode', () => {
|
|
135
|
+
const result = formatAdd({ success: true, message: 'Done' }, 'json');
|
|
136
|
+
expect(JSON.parse(result)).toEqual({ success: true, message: 'Done' });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('returns success text in pretty mode', () => {
|
|
140
|
+
const result = formatAdd({ success: true, message: 'Done' }, 'pretty');
|
|
141
|
+
expect(result).toContain('Done');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('lists added resources in pretty mode', () => {
|
|
145
|
+
const result = formatAdd(
|
|
146
|
+
{ success: true, message: 'Done', addedResources: ['a/en/messages'] },
|
|
147
|
+
'pretty',
|
|
148
|
+
);
|
|
149
|
+
expect(result).toContain('a/en/messages');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('formatBenchmark', () => {
|
|
154
|
+
it('returns JSON in json mode', () => {
|
|
155
|
+
const entries = [{ strategyName: 's', totalChunks: 1, succeededChunks: 1, failedChunks: 0, totalDurationMs: 100, averageDurationMsPerChunk: 100, totalAttempts: 1 }];
|
|
156
|
+
const result = formatBenchmark(entries, 'json');
|
|
157
|
+
expect(JSON.parse(result)).toEqual(entries);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('returns warning when empty in pretty mode', () => {
|
|
161
|
+
const result = formatBenchmark([], 'pretty');
|
|
162
|
+
expect(result).toContain('No benchmark');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('renders table in pretty mode', () => {
|
|
166
|
+
const entries = [
|
|
167
|
+
{ strategyName: 'one-shot', totalChunks: 2, succeededChunks: 2, failedChunks: 0, totalDurationMs: 200, averageDurationMsPerChunk: 100, totalAttempts: 2 },
|
|
168
|
+
];
|
|
169
|
+
const result = formatBenchmark(entries, 'pretty');
|
|
170
|
+
expect(result).toContain('Benchmark');
|
|
171
|
+
expect(result).toContain('one-shot');
|
|
172
|
+
expect(result).toContain('2/2');
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('formatError', () => {
|
|
177
|
+
it('returns JSON in json mode', () => {
|
|
178
|
+
const result = formatError('Something broke', 'json');
|
|
179
|
+
expect(JSON.parse(result)).toEqual({ error: 'Something broke' });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('returns failure text in pretty mode', () => {
|
|
183
|
+
const result = formatError('Something broke', 'pretty');
|
|
184
|
+
expect(result).toContain('Something broke');
|
|
185
|
+
});
|
|
186
|
+
});
|