@babelize/cli 1.1.0 → 1.1.3
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 +14 -10
- package/dist/client.d.ts +9 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +73 -31
- package/dist/client.js.map +1 -1
- package/dist/commands/account.js +1 -1
- package/dist/commands/account.js.map +1 -1
- package/dist/commands/admin.js +5 -5
- package/dist/commands/admin.js.map +1 -1
- package/dist/commands/analytics.js +1 -1
- package/dist/commands/analytics.js.map +1 -1
- package/dist/commands/api-keys.d.ts +6 -0
- package/dist/commands/api-keys.d.ts.map +1 -1
- package/dist/commands/api-keys.js +42 -12
- package/dist/commands/api-keys.js.map +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +23 -12
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/babels.d.ts.map +1 -1
- package/dist/commands/babels.js +16 -20
- package/dist/commands/babels.js.map +1 -1
- package/dist/commands/billing.js +1 -1
- package/dist/commands/billing.js.map +1 -1
- package/dist/commands/checkout.js +2 -2
- package/dist/commands/checkout.js.map +1 -1
- package/dist/commands/comments.js +5 -5
- package/dist/commands/comments.js.map +1 -1
- package/dist/commands/glossary.d.ts +15 -0
- package/dist/commands/glossary.d.ts.map +1 -1
- package/dist/commands/glossary.js +104 -24
- package/dist/commands/glossary.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +35 -22
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/invitations.d.ts.map +1 -1
- package/dist/commands/invitations.js +3 -1
- package/dist/commands/invitations.js.map +1 -1
- package/dist/commands/languages.d.ts.map +1 -1
- package/dist/commands/languages.js +38 -17
- package/dist/commands/languages.js.map +1 -1
- package/dist/commands/members.js +7 -7
- package/dist/commands/members.js.map +1 -1
- package/dist/commands/notifications.js +1 -1
- package/dist/commands/notifications.js.map +1 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +6 -4
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/privacy.d.ts.map +1 -1
- package/dist/commands/privacy.js +7 -5
- package/dist/commands/privacy.js.map +1 -1
- package/dist/commands/projects.d.ts.map +1 -1
- package/dist/commands/projects.js +60 -35
- package/dist/commands/projects.js.map +1 -1
- package/dist/commands/roles.js +1 -1
- package/dist/commands/roles.js.map +1 -1
- package/dist/commands/run.d.ts +48 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +264 -132
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/scan.js +1 -1
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/subscriptions.js +1 -1
- package/dist/commands/subscriptions.js.map +1 -1
- package/dist/commands/tm.js +2 -2
- package/dist/commands/tm.js.map +1 -1
- package/dist/commands/translate.d.ts.map +1 -1
- package/dist/commands/translate.js +8 -1
- package/dist/commands/translate.js.map +1 -1
- package/dist/commands/users.js +1 -1
- package/dist/commands/users.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -4
- package/dist/config.js.map +1 -1
- package/dist/global-options.d.ts +6 -2
- package/dist/global-options.d.ts.map +1 -1
- package/dist/global-options.js +9 -3
- package/dist/global-options.js.map +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +11 -3
- package/dist/oauth.js.map +1 -1
- package/dist/program.d.ts +22 -0
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +26 -10
- package/dist/program.js.map +1 -1
- package/dist/tui/clack.d.ts +60 -0
- package/dist/tui/clack.d.ts.map +1 -0
- package/dist/tui/clack.js +138 -0
- package/dist/tui/clack.js.map +1 -0
- package/dist/tui/help.d.ts.map +1 -1
- package/dist/tui/help.js +14 -5
- package/dist/tui/help.js.map +1 -1
- package/dist/tui/prompt.d.ts.map +1 -1
- package/dist/tui/prompt.js +4 -2
- package/dist/tui/prompt.js.map +1 -1
- package/dist/tui/table.d.ts.map +1 -1
- package/dist/tui/table.js +6 -2
- package/dist/tui/table.js.map +1 -1
- package/dist/utils/validate.d.ts +7 -0
- package/dist/utils/validate.d.ts.map +1 -0
- package/dist/utils/validate.js +16 -0
- package/dist/utils/validate.js.map +1 -0
- package/dist/utils/zip-upload.d.ts +2 -0
- package/dist/utils/zip-upload.d.ts.map +1 -1
- package/dist/utils/zip-upload.js +8 -0
- package/dist/utils/zip-upload.js.map +1 -1
- package/package.json +3 -3
package/dist/commands/run.d.ts
CHANGED
|
@@ -1,3 +1,51 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { AxiosInstance } from 'axios';
|
|
3
|
+
/**
|
|
4
|
+
* Pipeline phases as the worker reports them: each `babel_logs.step` the
|
|
5
|
+
* engine emits through onProgress (packages/engine/src/orchestrator.ts),
|
|
6
|
+
* grouped in run order. The lockfile step runs around the locale step, and
|
|
7
|
+
* format-injection reports under injection.
|
|
8
|
+
*/
|
|
9
|
+
export declare const RUN_PHASES: {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
steps: string[];
|
|
13
|
+
}[];
|
|
14
|
+
export type PhaseState = 'pending' | 'running' | 'done' | 'error';
|
|
15
|
+
export interface PhaseProgress {
|
|
16
|
+
states: PhaseState[];
|
|
17
|
+
/** Index of the phase in progress, or -1 before the first log. */
|
|
18
|
+
current: number;
|
|
19
|
+
/** Latest message logged for the current phase. */
|
|
20
|
+
message?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Where a run is, from its logs. A phase is done once it logs `success` or a
|
|
24
|
+
* later phase has started; the final `complete` step (or a `completed` run)
|
|
25
|
+
* marks every phase done. A phase that logged `error` is the failed one.
|
|
26
|
+
*/
|
|
27
|
+
export declare function computePhaseProgress(logs: Record<string, unknown>[], runStatus?: string): PhaseProgress;
|
|
2
28
|
export declare function registerRunCommands(program: Command): void;
|
|
29
|
+
/**
|
|
30
|
+
* Poll a run until it ends, showing each phase as it finishes.
|
|
31
|
+
*
|
|
32
|
+
* Returns the final status payload of a completed run. A run that ends any
|
|
33
|
+
* other way exits the process:
|
|
34
|
+
* - failed → ExitCode.API_ERROR (6), with the run's error
|
|
35
|
+
* - cancelled → ExitCode.CANCEL (128), the code the CLI already uses for
|
|
36
|
+
* anything the user cancelled, so it never looks like success
|
|
37
|
+
* With --json, the final payload is printed before exiting on failed or
|
|
38
|
+
* cancelled; a completed run's payload is left to the caller.
|
|
39
|
+
*/
|
|
40
|
+
export declare function pollRunStatus(client: AxiosInstance, babelId: string, options?: {
|
|
41
|
+
intervalMs?: number;
|
|
42
|
+
}): Promise<Record<string, unknown>>;
|
|
43
|
+
export interface AutoPrOutcome {
|
|
44
|
+
status: 'created' | 'exists' | 'failed';
|
|
45
|
+
url?: string;
|
|
46
|
+
message?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function renderRunStatus(data: Record<string, unknown>, options?: {
|
|
49
|
+
source?: string;
|
|
50
|
+
}): void;
|
|
3
51
|
//# sourceMappingURL=run.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,OAAc,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAa7C;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,EAStE,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;AAElE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAKD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa,CAkCvG;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+K1D;AASD;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACpC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA8ElC;AAqBD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAuDD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,IAAI,CAgFtG"}
|
package/dist/commands/run.js
CHANGED
|
@@ -1,25 +1,81 @@
|
|
|
1
|
-
import * as clack from '
|
|
1
|
+
import * as clack from '../tui/clack.js';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { success as successColor, error as errorColor, warning as warningColor, statusBadge, sym } from '../tui/theme.js';
|
|
4
5
|
import { table, keyValue } from '../tui/table.js';
|
|
5
|
-
import { section, dim,
|
|
6
|
-
import {
|
|
6
|
+
import { section, dim, success, warning } from '../tui/display.js';
|
|
7
|
+
import { delay, renderRunTitle } from '../tui/task-list.js';
|
|
7
8
|
import { config } from '../config.js';
|
|
8
9
|
import { getClient, handleApiError } from '../client.js';
|
|
9
10
|
import { isJsonMode, printJson } from '../formatters.js';
|
|
10
11
|
import { isNonInteractive } from '../global-options.js';
|
|
11
12
|
import { renderCommandHelp } from '../tui/help.js';
|
|
12
13
|
import { ExitCode } from '../exit-codes.js';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'
|
|
14
|
+
import { requireUuid } from '../utils/validate.js';
|
|
15
|
+
/**
|
|
16
|
+
* Pipeline phases as the worker reports them: each `babel_logs.step` the
|
|
17
|
+
* engine emits through onProgress (packages/engine/src/orchestrator.ts),
|
|
18
|
+
* grouped in run order. The lockfile step runs around the locale step, and
|
|
19
|
+
* format-injection reports under injection.
|
|
20
|
+
*/
|
|
21
|
+
export const RUN_PHASES = [
|
|
22
|
+
{ id: 'validation', title: 'Workspace validation', steps: ['validation'] },
|
|
23
|
+
{ id: 'extraction', title: 'String extraction', steps: ['extraction'] },
|
|
24
|
+
{ id: 'keygen', title: 'Key generation', steps: ['keygen'] },
|
|
25
|
+
{ id: 'locale', title: 'Base locale and lockfile', steps: ['locale', 'lockfile'] },
|
|
26
|
+
{ id: 'translation', title: 'Translation', steps: ['translation'] },
|
|
27
|
+
{ id: 'injection', title: 'Code injection', steps: ['injection', 'format-injection'] },
|
|
28
|
+
{ id: 'finalization', title: 'Finalization', steps: ['finalization'] },
|
|
29
|
+
{ id: 'summary', title: 'Summary', steps: ['summary'] },
|
|
22
30
|
];
|
|
31
|
+
/** Run statuses after which a run never changes again. */
|
|
32
|
+
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
33
|
+
/**
|
|
34
|
+
* Where a run is, from its logs. A phase is done once it logs `success` or a
|
|
35
|
+
* later phase has started; the final `complete` step (or a `completed` run)
|
|
36
|
+
* marks every phase done. A phase that logged `error` is the failed one.
|
|
37
|
+
*/
|
|
38
|
+
export function computePhaseProgress(logs, runStatus) {
|
|
39
|
+
const states = RUN_PHASES.map(() => 'pending');
|
|
40
|
+
const succeeded = new Set();
|
|
41
|
+
let reached = -1;
|
|
42
|
+
let errorAt = -1;
|
|
43
|
+
let message;
|
|
44
|
+
let complete = runStatus === 'completed';
|
|
45
|
+
for (const log of logs) {
|
|
46
|
+
const step = String(log.step ?? '').toLowerCase();
|
|
47
|
+
const status = String(log.status ?? '').toLowerCase();
|
|
48
|
+
if (step === 'complete') {
|
|
49
|
+
if (status === 'success')
|
|
50
|
+
complete = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const idx = RUN_PHASES.findIndex((p) => p.steps.includes(step));
|
|
54
|
+
if (idx === -1)
|
|
55
|
+
continue;
|
|
56
|
+
if (idx >= reached) {
|
|
57
|
+
reached = idx;
|
|
58
|
+
if (typeof log.message === 'string')
|
|
59
|
+
message = log.message;
|
|
60
|
+
}
|
|
61
|
+
if (status === 'success')
|
|
62
|
+
succeeded.add(idx);
|
|
63
|
+
if (status === 'error')
|
|
64
|
+
errorAt = idx;
|
|
65
|
+
}
|
|
66
|
+
if (complete) {
|
|
67
|
+
return { states: states.map(() => 'done'), current: RUN_PHASES.length - 1, message };
|
|
68
|
+
}
|
|
69
|
+
for (let i = 0; i < states.length; i++) {
|
|
70
|
+
if (i === errorAt)
|
|
71
|
+
states[i] = 'error';
|
|
72
|
+
else if (i < reached || succeeded.has(i))
|
|
73
|
+
states[i] = 'done';
|
|
74
|
+
else if (i === reached)
|
|
75
|
+
states[i] = 'running';
|
|
76
|
+
}
|
|
77
|
+
return { states, current: reached, message };
|
|
78
|
+
}
|
|
23
79
|
export function registerRunCommands(program) {
|
|
24
80
|
const run = program
|
|
25
81
|
.command('run')
|
|
@@ -27,13 +83,13 @@ export function registerRunCommands(program) {
|
|
|
27
83
|
run.helpInformation = () => renderCommandHelp('run', 'Engine commands — start, preview, and monitor babel runs', [
|
|
28
84
|
{
|
|
29
85
|
name: 'start',
|
|
30
|
-
desc:
|
|
86
|
+
desc: `Start a full Babelize engine run (exits 0 completed, ${ExitCode.API_ERROR} failed, ${ExitCode.CANCEL} cancelled)`,
|
|
31
87
|
options: [
|
|
32
88
|
{ name: '-p, --project <id>', desc: 'Project ID (defaults to .babelize/config.json)' },
|
|
33
89
|
{ name: '-l, --languages <codes>', desc: 'Comma-separated target language codes (e.g. es,fr,de,ja)' },
|
|
34
90
|
{ name: '--branch <branch>', desc: 'Git branch to run on' },
|
|
35
91
|
{ name: '--no-poll', desc: "Return immediately (don't wait for completion)" },
|
|
36
|
-
{ name: '--no-pr', desc: 'Skip auto-creating a GitHub PR after completion' },
|
|
92
|
+
{ name: '--no-pr', desc: 'Skip auto-creating a GitHub PR after completion (never attempted for ZIP projects)' },
|
|
37
93
|
],
|
|
38
94
|
},
|
|
39
95
|
{
|
|
@@ -64,7 +120,7 @@ export function registerRunCommands(program) {
|
|
|
64
120
|
console.error('Error: --project is required in non-interactive mode.');
|
|
65
121
|
process.exit(ExitCode.USAGE);
|
|
66
122
|
}
|
|
67
|
-
const result = await clack.text({ message: 'Project ID:' });
|
|
123
|
+
const result = await clack.text({ message: 'Project ID:', flag: '--project' });
|
|
68
124
|
if (clack.isCancel(result))
|
|
69
125
|
process.exit(ExitCode.CANCEL);
|
|
70
126
|
projectId = result;
|
|
@@ -74,49 +130,69 @@ export function registerRunCommands(program) {
|
|
|
74
130
|
console.error('Error: --languages is required in non-interactive mode.');
|
|
75
131
|
process.exit(ExitCode.USAGE);
|
|
76
132
|
}
|
|
77
|
-
const result = await clack.text({ message: 'Target languages (comma-sep, e.g. es,fr,de):' });
|
|
133
|
+
const result = await clack.text({ message: 'Target languages (comma-sep, e.g. es,fr,de):', flag: '--languages' });
|
|
78
134
|
if (clack.isCancel(result))
|
|
79
135
|
process.exit(ExitCode.CANCEL);
|
|
80
136
|
languages = result;
|
|
81
137
|
}
|
|
82
138
|
const langs = languages.split(',').map((l) => l.trim()).filter(Boolean);
|
|
139
|
+
const client = getClient();
|
|
83
140
|
const s = clack.spinner();
|
|
84
141
|
s.start('Starting engine run…');
|
|
142
|
+
let babelId;
|
|
143
|
+
let startData;
|
|
85
144
|
try {
|
|
86
|
-
const client = getClient();
|
|
87
145
|
const res = await client.post('/engine/babel', {
|
|
88
146
|
project_id: projectId,
|
|
89
147
|
target_locales: langs,
|
|
90
148
|
...(opts.branch ? { branch: opts.branch } : {}),
|
|
91
149
|
}); // codeql[js/file-access-to-http] — intentional CLI request to own API
|
|
92
|
-
|
|
150
|
+
startData = res.data;
|
|
151
|
+
babelId = res.data?.babel_id ?? res.data?.run_id ?? res.data?.id;
|
|
93
152
|
s.stop(successColor(`Engine run started — Job ID: ${chalk.bold(babelId)}`));
|
|
94
|
-
if (!opts.poll) {
|
|
95
|
-
dim('Use `babelize run status ' + babelId + '` to check progress.');
|
|
96
|
-
if (isJsonMode())
|
|
97
|
-
printJson(res.data);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const lastData = await pollRunStatus(client, babelId);
|
|
101
|
-
printCompletionBanner(lastData);
|
|
102
|
-
if (lastData?.status === 'completed' && opts.pr !== false) {
|
|
103
|
-
await autoRaisePR(client, projectId, langs, lastData);
|
|
104
|
-
}
|
|
105
153
|
}
|
|
106
154
|
catch (err) {
|
|
107
155
|
s.stop('Failed to start engine run.');
|
|
108
156
|
handleApiError(err);
|
|
109
157
|
}
|
|
158
|
+
if (!opts.poll) {
|
|
159
|
+
if (isJsonMode())
|
|
160
|
+
printJson(startData);
|
|
161
|
+
else
|
|
162
|
+
dim('Use `babelize run status ' + babelId + '` to check progress.');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const lastData = await pollRunStatus(client, babelId);
|
|
166
|
+
// ZIP projects have no repository to open a PR against; their results
|
|
167
|
+
// are downloaded instead. The local config already knows the source of
|
|
168
|
+
// its own project, otherwise ask the API.
|
|
169
|
+
const source = localProject?.projectId === projectId && localProject?.source
|
|
170
|
+
? localProject.source
|
|
171
|
+
: await fetchProjectSource(client, projectId);
|
|
172
|
+
let autoPr;
|
|
173
|
+
if (isJsonMode()) {
|
|
174
|
+
if (opts.pr !== false && source !== 'zip') {
|
|
175
|
+
autoPr = await autoRaisePR(client, projectId, langs, lastData, true);
|
|
176
|
+
}
|
|
177
|
+
printJson(autoPr ? { ...lastData, auto_pr: autoPr } : lastData);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
renderRunStatus(lastData, { source });
|
|
181
|
+
printCompletionBanner(lastData);
|
|
182
|
+
if (opts.pr !== false && source !== 'zip') {
|
|
183
|
+
await autoRaisePR(client, projectId, langs, lastData, false);
|
|
184
|
+
}
|
|
110
185
|
});
|
|
111
186
|
run
|
|
112
187
|
.command('status <babelId>')
|
|
113
188
|
.description('Get status of a running or completed babel job')
|
|
114
189
|
.option('--watch', 'Keep polling until job completes')
|
|
115
190
|
.action(async (babelId, opts) => {
|
|
191
|
+
requireUuid(babelId, 'babel ID');
|
|
116
192
|
const client = getClient();
|
|
193
|
+
let data;
|
|
117
194
|
if (opts.watch) {
|
|
118
|
-
|
|
119
|
-
printCompletionBanner(lastData);
|
|
195
|
+
data = await pollRunStatus(client, babelId);
|
|
120
196
|
}
|
|
121
197
|
else {
|
|
122
198
|
const s = clack.spinner();
|
|
@@ -124,18 +200,21 @@ export function registerRunCommands(program) {
|
|
|
124
200
|
try {
|
|
125
201
|
const res = await client.get(`/engine/status/${babelId}`);
|
|
126
202
|
s.stop();
|
|
127
|
-
|
|
128
|
-
if (isJsonMode()) {
|
|
129
|
-
printJson(data);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
renderRunStatus(data);
|
|
203
|
+
data = res.data;
|
|
133
204
|
}
|
|
134
205
|
catch (err) {
|
|
135
206
|
s.stop('Failed to fetch status.');
|
|
136
207
|
handleApiError(err);
|
|
137
208
|
}
|
|
138
209
|
}
|
|
210
|
+
if (isJsonMode()) {
|
|
211
|
+
printJson(data);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const source = await fetchProjectSource(client, runOf(data).project_id);
|
|
215
|
+
renderRunStatus(data, { source });
|
|
216
|
+
if (opts.watch)
|
|
217
|
+
printCompletionBanner(data);
|
|
139
218
|
});
|
|
140
219
|
run
|
|
141
220
|
.command('analyze')
|
|
@@ -148,7 +227,7 @@ export function registerRunCommands(program) {
|
|
|
148
227
|
console.error('Error: --project is required in non-interactive mode.');
|
|
149
228
|
process.exit(ExitCode.USAGE);
|
|
150
229
|
}
|
|
151
|
-
const result = await clack.text({ message: 'Project ID:' });
|
|
230
|
+
const result = await clack.text({ message: 'Project ID:', flag: '--project' });
|
|
152
231
|
if (clack.isCancel(result))
|
|
153
232
|
process.exit(ExitCode.CANCEL);
|
|
154
233
|
projectId = result;
|
|
@@ -171,88 +250,126 @@ export function registerRunCommands(program) {
|
|
|
171
250
|
}
|
|
172
251
|
});
|
|
173
252
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
task.output = phaseLog.message;
|
|
202
|
-
}
|
|
203
|
-
retryCount = 0;
|
|
204
|
-
}
|
|
205
|
-
catch (err) {
|
|
206
|
-
const pollErr = err;
|
|
207
|
-
if (pollErr.message?.includes('failed') || pollErr.message?.includes('Pipeline')) {
|
|
208
|
-
throw err;
|
|
209
|
-
}
|
|
210
|
-
retryCount++;
|
|
211
|
-
if (retryCount >= MAX_RETRIES) {
|
|
212
|
-
throw new Error(`Timed out waiting for pipeline to complete after ${MAX_RETRIES} retries`, { cause: err });
|
|
213
|
-
}
|
|
214
|
-
task.output = `Transient error, retrying (${retryCount}/${MAX_RETRIES})…`;
|
|
215
|
-
}
|
|
216
|
-
await delay(2500);
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
})));
|
|
220
|
-
try {
|
|
253
|
+
const POLL_INTERVAL_MS = 2500;
|
|
254
|
+
const MAX_CONSECUTIVE_FAILURES = 60;
|
|
255
|
+
function runOf(data) {
|
|
256
|
+
return (data?.run ?? data ?? {});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Poll a run until it ends, showing each phase as it finishes.
|
|
260
|
+
*
|
|
261
|
+
* Returns the final status payload of a completed run. A run that ends any
|
|
262
|
+
* other way exits the process:
|
|
263
|
+
* - failed → ExitCode.API_ERROR (6), with the run's error
|
|
264
|
+
* - cancelled → ExitCode.CANCEL (128), the code the CLI already uses for
|
|
265
|
+
* anything the user cancelled, so it never looks like success
|
|
266
|
+
* With --json, the final payload is printed before exiting on failed or
|
|
267
|
+
* cancelled; a completed run's payload is left to the caller.
|
|
268
|
+
*/
|
|
269
|
+
export async function pollRunStatus(client, babelId, options = {}) {
|
|
270
|
+
const intervalMs = options.intervalMs ?? POLL_INTERVAL_MS;
|
|
271
|
+
const json = isJsonMode();
|
|
272
|
+
const s = clack.spinner();
|
|
273
|
+
let announced = 0;
|
|
274
|
+
let failures = 0;
|
|
275
|
+
let spinning = false;
|
|
276
|
+
const startSpinner = (msg) => { s.start(msg); spinning = true; };
|
|
277
|
+
const stopSpinner = (msg) => { if (spinning)
|
|
278
|
+
s.stop(msg); spinning = false; };
|
|
279
|
+
if (!json)
|
|
221
280
|
console.log(`\n ${renderRunTitle(babelId)}\n`);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
281
|
+
startSpinner('Waiting for the run to start…');
|
|
282
|
+
while (true) {
|
|
283
|
+
let data;
|
|
284
|
+
try {
|
|
285
|
+
const res = await client.get(`/engine/status/${babelId}`);
|
|
286
|
+
data = (res.data ?? {});
|
|
287
|
+
failures = 0;
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
const status = axios.isAxiosError(err) ? err.response?.status : undefined;
|
|
291
|
+
failures++;
|
|
292
|
+
// A 4xx (other than rate limiting) will not fix itself: report it now.
|
|
293
|
+
if ((status && status >= 400 && status < 500 && status !== 429) || failures >= MAX_CONSECUTIVE_FAILURES) {
|
|
294
|
+
stopSpinner();
|
|
295
|
+
handleApiError(err);
|
|
230
296
|
}
|
|
297
|
+
s.message(`Connection problem, retrying (${failures}/${MAX_CONSECUTIVE_FAILURES})…`);
|
|
298
|
+
await delay(intervalMs);
|
|
299
|
+
continue;
|
|
231
300
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
301
|
+
const run = runOf(data);
|
|
302
|
+
const status = String(run.status ?? '');
|
|
303
|
+
const logs = (Array.isArray(run.logs) ? run.logs : []);
|
|
304
|
+
const progress = computePhaseProgress(logs, status);
|
|
305
|
+
// Announce each phase once, in order, as it finishes.
|
|
306
|
+
while (announced < RUN_PHASES.length && progress.states[announced] === 'done') {
|
|
307
|
+
if (!spinning)
|
|
308
|
+
startSpinner(RUN_PHASES[announced].title);
|
|
309
|
+
stopSpinner(successColor(`${sym.check} ${RUN_PHASES[announced].title}`));
|
|
310
|
+
announced++;
|
|
311
|
+
}
|
|
312
|
+
if (status === 'completed') {
|
|
313
|
+
stopSpinner();
|
|
314
|
+
return data;
|
|
315
|
+
}
|
|
316
|
+
if (status === 'failed') {
|
|
317
|
+
const failedLog = [...logs].reverse().find((l) => String(l.status) === 'error');
|
|
318
|
+
const reason = run.error ?? failedLog?.message ?? 'unknown error';
|
|
319
|
+
if (!spinning)
|
|
320
|
+
startSpinner(RUN_PHASES[announced]?.title ?? 'Run');
|
|
321
|
+
stopSpinner(errorColor(`${sym.cross} Run failed: ${reason}`));
|
|
322
|
+
if (json)
|
|
323
|
+
printJson(data);
|
|
324
|
+
process.exit(ExitCode.API_ERROR);
|
|
325
|
+
}
|
|
326
|
+
if (status === 'cancelled') {
|
|
327
|
+
if (!spinning)
|
|
328
|
+
startSpinner(RUN_PHASES[announced]?.title ?? 'Run');
|
|
329
|
+
stopSpinner(warningColor(`${sym.warn} Run cancelled. Nothing from this run was applied.`));
|
|
330
|
+
if (json)
|
|
331
|
+
printJson(data);
|
|
332
|
+
process.exit(ExitCode.CANCEL);
|
|
333
|
+
}
|
|
334
|
+
if (TERMINAL_STATUSES.has(status))
|
|
335
|
+
return data;
|
|
336
|
+
if (!spinning)
|
|
337
|
+
startSpinner(RUN_PHASES[announced]?.title ?? 'Working…');
|
|
338
|
+
if (progress.current >= 0) {
|
|
339
|
+
const title = RUN_PHASES[progress.current].title;
|
|
340
|
+
s.message(progress.message ? `${title}: ${progress.message}` : title);
|
|
237
341
|
}
|
|
238
342
|
else {
|
|
239
|
-
|
|
343
|
+
s.message(status === 'queued' ? 'Queued, waiting for a worker…' : 'Waiting for the run to start…');
|
|
240
344
|
}
|
|
241
|
-
|
|
345
|
+
await delay(intervalMs);
|
|
242
346
|
}
|
|
243
347
|
}
|
|
244
348
|
function printCompletionBanner(lastData) {
|
|
245
|
-
|
|
246
|
-
if (status === 'completed') {
|
|
349
|
+
if (runOf(lastData).status === 'completed') {
|
|
247
350
|
console.log('');
|
|
248
351
|
success('All done! Translation run completed successfully.');
|
|
249
352
|
}
|
|
250
353
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
354
|
+
/** The project's source (`github` | `zip`), or undefined when it cannot be read. */
|
|
355
|
+
async function fetchProjectSource(client, projectId) {
|
|
356
|
+
if (!projectId)
|
|
357
|
+
return undefined;
|
|
358
|
+
try {
|
|
359
|
+
const res = await client.get(`/projects/${projectId}`);
|
|
360
|
+
const project = (res?.data?.project ?? res?.data);
|
|
361
|
+
return typeof project?.source === 'string' ? project.source : undefined;
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return undefined;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function autoRaisePR(client, projectId, targetLocales, lastData, quiet) {
|
|
368
|
+
const run = runOf(lastData);
|
|
369
|
+
if (run.pr_url) {
|
|
370
|
+
if (!quiet)
|
|
371
|
+
success(`PR already created: ${chalk.cyan.underline(run.pr_url)}`);
|
|
372
|
+
return { status: 'exists', url: run.pr_url };
|
|
256
373
|
}
|
|
257
374
|
const s = clack.spinner();
|
|
258
375
|
s.start('Auto-creating GitHub PR with translations…');
|
|
@@ -262,30 +379,45 @@ async function autoRaisePR(client, projectId, targetLocales, lastData) {
|
|
|
262
379
|
});
|
|
263
380
|
s.stop();
|
|
264
381
|
const prUrl = res.data?.pr_url ?? res.data?.url;
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
382
|
+
if (!quiet) {
|
|
383
|
+
if (prUrl)
|
|
384
|
+
success(`PR created: ${chalk.cyan.underline(prUrl)}`);
|
|
385
|
+
else
|
|
386
|
+
success('PR created successfully. Check your GitHub repository.');
|
|
270
387
|
}
|
|
388
|
+
return { status: 'created', url: prUrl };
|
|
271
389
|
}
|
|
272
390
|
catch (err) {
|
|
273
391
|
s.stop();
|
|
274
392
|
const apiErr = err;
|
|
275
|
-
if (apiErr?.response?.status === 409) {
|
|
276
|
-
|
|
393
|
+
if (apiErr?.response?.status === 409 && apiErr.response.data?.error_code === 'pr_open') {
|
|
394
|
+
const message = `PR #${apiErr.response.data.pr_number ?? '?'} is still open. Run \`babelize pr update ${projectId}\` to add this run to it (or \`babelize pr raise ${projectId} --new\` for a new PR).`;
|
|
395
|
+
if (!quiet)
|
|
396
|
+
dim(message);
|
|
397
|
+
return { status: 'exists', message };
|
|
277
398
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
399
|
+
if (apiErr?.response?.status === 409) {
|
|
400
|
+
const message = apiErr.response.data?.error ?? 'PR already exists for this project.';
|
|
401
|
+
if (!quiet)
|
|
402
|
+
dim(message);
|
|
403
|
+
return { status: 'exists', message };
|
|
281
404
|
}
|
|
282
|
-
|
|
283
|
-
|
|
405
|
+
if (apiErr?.response?.data?.requires_app_install) {
|
|
406
|
+
const message = 'GitHub App not installed. Install it from the project page to enable auto-PR.';
|
|
407
|
+
if (!quiet) {
|
|
408
|
+
warning(message);
|
|
409
|
+
dim('Run `babelize pr raise ' + projectId + '` after installing.');
|
|
410
|
+
}
|
|
411
|
+
return { status: 'failed', message };
|
|
284
412
|
}
|
|
413
|
+
const message = 'Could not auto-create PR. Run `babelize pr raise ' + projectId + '` to try manually.';
|
|
414
|
+
if (!quiet)
|
|
415
|
+
dim(message);
|
|
416
|
+
return { status: 'failed', message };
|
|
285
417
|
}
|
|
286
418
|
}
|
|
287
|
-
function renderRunStatus(data) {
|
|
288
|
-
const run = (data
|
|
419
|
+
export function renderRunStatus(data, options = {}) {
|
|
420
|
+
const run = runOf(data);
|
|
289
421
|
keyValue({
|
|
290
422
|
id: run.id,
|
|
291
423
|
status: statusBadge(run.status),
|
|
@@ -320,13 +452,8 @@ function renderRunStatus(data) {
|
|
|
320
452
|
processing_time: stats.processing_time_ms ? `${stats.processing_time_ms}ms` : null,
|
|
321
453
|
}, 'Stats');
|
|
322
454
|
}
|
|
323
|
-
// Local artifacts
|
|
324
455
|
const projectId = run.project_id;
|
|
325
|
-
|
|
326
|
-
section('Local Artifacts');
|
|
327
|
-
dim(`Staging: .babelize/${projectId}/staging/`);
|
|
328
|
-
}
|
|
329
|
-
// Warnings (capped)
|
|
456
|
+
// Pipeline log
|
|
330
457
|
const logs = run.logs;
|
|
331
458
|
if (logs?.length) {
|
|
332
459
|
section('Pipeline Log');
|
|
@@ -336,7 +463,7 @@ function renderRunStatus(data) {
|
|
|
336
463
|
{ key: 'message', header: 'Message', width: 60 },
|
|
337
464
|
]);
|
|
338
465
|
}
|
|
339
|
-
// Warnings from activity payload
|
|
466
|
+
// Warnings from activity payload (capped)
|
|
340
467
|
const warnings = data.warnings;
|
|
341
468
|
if (warnings?.length) {
|
|
342
469
|
const DUPE_CAP = 10;
|
|
@@ -347,16 +474,21 @@ function renderRunStatus(data) {
|
|
|
347
474
|
if (shown.length > 0) {
|
|
348
475
|
section('Warnings');
|
|
349
476
|
for (const w of shown) {
|
|
350
|
-
console.log(` ${
|
|
477
|
+
console.log(` ${warningColor(sym.warn)} ${w.message ?? ''}`);
|
|
351
478
|
}
|
|
352
479
|
if (remaining > 0) {
|
|
353
480
|
dim(` … and ${remaining} more duplicate-key warnings. Use --json for full list.`);
|
|
354
481
|
}
|
|
355
482
|
}
|
|
356
483
|
}
|
|
357
|
-
// PR
|
|
484
|
+
// Next step: ZIP projects download their results; GitHub projects open a PR.
|
|
358
485
|
if (run.status === 'completed' && !run.pr_url && projectId) {
|
|
359
|
-
|
|
486
|
+
if (options.source === 'zip') {
|
|
487
|
+
dim('Run `babelize projects download ' + projectId + '` to download the translated files.');
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
dim('Run `babelize pr raise ' + projectId + '` to open a GitHub PR with the translations.');
|
|
491
|
+
}
|
|
360
492
|
}
|
|
361
493
|
}
|
|
362
494
|
//# sourceMappingURL=run.js.map
|