@vocoder/cli 0.13.4 → 0.14.1
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/dist/bin.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
readAuthData,
|
|
14
14
|
verifyStoredAuth,
|
|
15
15
|
writeAuthData
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-LLEMSC3X.mjs";
|
|
17
17
|
|
|
18
18
|
// src/bin.ts
|
|
19
19
|
import { Command } from "commander";
|
|
@@ -64,311 +64,29 @@ export default defineConfig({
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// src/utils/github-connect.ts
|
|
68
|
-
import { spawn } from "child_process";
|
|
69
|
-
import * as p from "@clack/prompts";
|
|
70
|
-
import chalk from "chalk";
|
|
71
|
-
|
|
72
|
-
// src/utils/local-server.ts
|
|
73
|
-
import { createServer } from "http";
|
|
74
|
-
import { URL as URL2 } from "url";
|
|
75
|
-
function startCallbackServer() {
|
|
76
|
-
return new Promise((resolve2, reject) => {
|
|
77
|
-
let settled = false;
|
|
78
|
-
let callbackResolve = null;
|
|
79
|
-
let callbackReject = null;
|
|
80
|
-
const callbackPromise = new Promise((res, rej) => {
|
|
81
|
-
callbackResolve = res;
|
|
82
|
-
callbackReject = rej;
|
|
83
|
-
});
|
|
84
|
-
const server = createServer((req, res) => {
|
|
85
|
-
if (!req.url) {
|
|
86
|
-
res.writeHead(400);
|
|
87
|
-
res.end();
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
let pathname;
|
|
91
|
-
let params;
|
|
92
|
-
try {
|
|
93
|
-
const parsed = new URL2(req.url, "http://localhost");
|
|
94
|
-
pathname = parsed.pathname;
|
|
95
|
-
params = Object.fromEntries(parsed.searchParams.entries());
|
|
96
|
-
} catch {
|
|
97
|
-
res.writeHead(400);
|
|
98
|
-
res.end("Bad request");
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (pathname !== "/callback") {
|
|
102
|
-
res.writeHead(404);
|
|
103
|
-
res.end("Not found");
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
107
|
-
res.end(
|
|
108
|
-
'<!DOCTYPE html><html><head><title>Authenticated</title></head><body style="font-family:sans-serif;text-align:center;padding:3rem;"><h2>Authenticated</h2><p>Return to your terminal to continue. You can close this tab.</p></body></html>'
|
|
109
|
-
);
|
|
110
|
-
if (callbackResolve) {
|
|
111
|
-
callbackResolve(params);
|
|
112
|
-
callbackResolve = null;
|
|
113
|
-
}
|
|
114
|
-
setImmediate(() => server.close());
|
|
115
|
-
});
|
|
116
|
-
server.on("error", (err) => {
|
|
117
|
-
if (!settled) {
|
|
118
|
-
settled = true;
|
|
119
|
-
if (callbackReject) callbackReject(err);
|
|
120
|
-
reject(err);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
server.listen(0, "127.0.0.1", () => {
|
|
124
|
-
if (settled) return;
|
|
125
|
-
settled = true;
|
|
126
|
-
const port = server.address().port;
|
|
127
|
-
resolve2({
|
|
128
|
-
port,
|
|
129
|
-
waitForCallback: () => callbackPromise,
|
|
130
|
-
close: () => server.close()
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// src/utils/github-connect.ts
|
|
137
|
-
async function tryOpenBrowser(url) {
|
|
138
|
-
if (!process.stdout.isTTY || process.env.CI === "true") {
|
|
139
|
-
return false;
|
|
140
|
-
}
|
|
141
|
-
const platform = process.platform;
|
|
142
|
-
let command;
|
|
143
|
-
let args;
|
|
144
|
-
if (platform === "darwin") {
|
|
145
|
-
command = "open";
|
|
146
|
-
args = [url];
|
|
147
|
-
} else if (platform === "win32") {
|
|
148
|
-
command = "rundll32";
|
|
149
|
-
args = ["url.dll,FileProtocolHandler", url];
|
|
150
|
-
} else {
|
|
151
|
-
command = "xdg-open";
|
|
152
|
-
args = [url];
|
|
153
|
-
}
|
|
154
|
-
return new Promise((resolve2) => {
|
|
155
|
-
try {
|
|
156
|
-
const child = spawn(command, args, {
|
|
157
|
-
detached: true,
|
|
158
|
-
stdio: "ignore",
|
|
159
|
-
windowsHide: true
|
|
160
|
-
});
|
|
161
|
-
let settled = false;
|
|
162
|
-
child.once("spawn", () => {
|
|
163
|
-
if (settled) return;
|
|
164
|
-
settled = true;
|
|
165
|
-
child.unref();
|
|
166
|
-
resolve2(true);
|
|
167
|
-
});
|
|
168
|
-
child.once("error", () => {
|
|
169
|
-
if (settled) return;
|
|
170
|
-
settled = true;
|
|
171
|
-
resolve2(false);
|
|
172
|
-
});
|
|
173
|
-
setTimeout(() => {
|
|
174
|
-
if (settled) return;
|
|
175
|
-
settled = true;
|
|
176
|
-
resolve2(false);
|
|
177
|
-
}, 300);
|
|
178
|
-
} catch {
|
|
179
|
-
resolve2(false);
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
async function runGitHubInstallFlow(params) {
|
|
184
|
-
let server = null;
|
|
185
|
-
try {
|
|
186
|
-
server = await startCallbackServer();
|
|
187
|
-
} catch {
|
|
188
|
-
}
|
|
189
|
-
const { installUrl } = await params.api.startCliGitHubInstall(
|
|
190
|
-
params.userToken,
|
|
191
|
-
{
|
|
192
|
-
organizationId: params.organizationId,
|
|
193
|
-
callbackPort: server?.port
|
|
194
|
-
}
|
|
195
|
-
);
|
|
196
|
-
p.log.info("Opening GitHub to install the Vocoder App...");
|
|
197
|
-
p.note(installUrl, "Install URL");
|
|
198
|
-
if (process.stdin.isTTY && process.stdout.isTTY && process.env.CI !== "true") {
|
|
199
|
-
const shouldOpen = params.yes ? true : await p.confirm({ message: "Open in your browser?" });
|
|
200
|
-
if (p.isCancel(shouldOpen)) {
|
|
201
|
-
server?.close();
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
if (shouldOpen) {
|
|
205
|
-
const opened = await tryOpenBrowser(installUrl);
|
|
206
|
-
if (!opened) {
|
|
207
|
-
p.log.info(
|
|
208
|
-
"Could not open a browser automatically. Use the URL above."
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
const connectSpinner = p.spinner();
|
|
214
|
-
connectSpinner.start("Waiting for GitHub App installation...");
|
|
215
|
-
if (server) {
|
|
216
|
-
try {
|
|
217
|
-
const params_timeout = 15 * 60 * 1e3;
|
|
218
|
-
const callbackParams = await Promise.race([
|
|
219
|
-
server.waitForCallback(),
|
|
220
|
-
new Promise(
|
|
221
|
-
(resolve2) => setTimeout(() => resolve2(null), params_timeout)
|
|
222
|
-
)
|
|
223
|
-
]);
|
|
224
|
-
server.close();
|
|
225
|
-
if (!callbackParams) {
|
|
226
|
-
connectSpinner.stop("GitHub App installation timed out");
|
|
227
|
-
p.log.error(
|
|
228
|
-
"The installation flow timed out. Run `vocoder init` again."
|
|
229
|
-
);
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
if (callbackParams.error) {
|
|
233
|
-
connectSpinner.stop("GitHub App installation failed");
|
|
234
|
-
p.log.error(callbackParams.error);
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
const { organizationId, connectionLabel, workspace_created } = callbackParams;
|
|
238
|
-
if (!organizationId || !connectionLabel) {
|
|
239
|
-
connectSpinner.stop("GitHub App installation incomplete");
|
|
240
|
-
p.log.error("Missing organization or connection data from callback.");
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
connectSpinner.stop(
|
|
244
|
-
`Connected to GitHub as ${chalk.bold(connectionLabel)}`
|
|
245
|
-
);
|
|
246
|
-
const orgName = workspace_created ? connectionLabel : organizationId;
|
|
247
|
-
return {
|
|
248
|
-
organizationId,
|
|
249
|
-
organizationName: orgName,
|
|
250
|
-
connectionLabel
|
|
251
|
-
};
|
|
252
|
-
} catch {
|
|
253
|
-
server.close();
|
|
254
|
-
connectSpinner.stop("GitHub App installation failed");
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
connectSpinner.stop("Could not detect GitHub App installation automatically");
|
|
259
|
-
p.log.warn(
|
|
260
|
-
"Complete the installation in your browser, then run `vocoder init` again."
|
|
261
|
-
);
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
async function runGitHubDiscoveryFlow(params) {
|
|
265
|
-
let server = null;
|
|
266
|
-
try {
|
|
267
|
-
server = await startCallbackServer();
|
|
268
|
-
} catch {
|
|
269
|
-
}
|
|
270
|
-
const { oauthUrl } = await params.api.startCliGitHubOAuth(params.userToken, {
|
|
271
|
-
organizationId: params.organizationId,
|
|
272
|
-
callbackPort: server?.port
|
|
273
|
-
});
|
|
274
|
-
p.log.info("Opening GitHub to authorize your account...");
|
|
275
|
-
p.note("Complete authorization in your browser.");
|
|
276
|
-
if (process.stdin.isTTY && process.stdout.isTTY && process.env.CI !== "true") {
|
|
277
|
-
const shouldOpen = params.yes ? true : await p.confirm({ message: "Open in your browser?" });
|
|
278
|
-
if (p.isCancel(shouldOpen)) {
|
|
279
|
-
server?.close();
|
|
280
|
-
return null;
|
|
281
|
-
}
|
|
282
|
-
if (shouldOpen) {
|
|
283
|
-
const opened = await tryOpenBrowser(oauthUrl);
|
|
284
|
-
if (!opened) {
|
|
285
|
-
p.log.info(`Could not open browser automatically. Visit: ${oauthUrl}`);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
const oauthSpinner = p.spinner();
|
|
290
|
-
oauthSpinner.start("Waiting for GitHub authorization...");
|
|
291
|
-
if (server) {
|
|
292
|
-
try {
|
|
293
|
-
const timeoutMs = 10 * 60 * 1e3;
|
|
294
|
-
const callbackParams = await Promise.race([
|
|
295
|
-
server.waitForCallback(),
|
|
296
|
-
new Promise(
|
|
297
|
-
(resolve2) => setTimeout(() => resolve2(null), timeoutMs)
|
|
298
|
-
)
|
|
299
|
-
]);
|
|
300
|
-
server.close();
|
|
301
|
-
if (!callbackParams) {
|
|
302
|
-
oauthSpinner.stop("GitHub authorization timed out");
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
if (callbackParams.error) {
|
|
306
|
-
oauthSpinner.stop("GitHub authorization failed");
|
|
307
|
-
p.log.error(callbackParams.error);
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
310
|
-
} catch {
|
|
311
|
-
server.close();
|
|
312
|
-
oauthSpinner.stop("GitHub authorization failed");
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
oauthSpinner.stop("GitHub account authorized");
|
|
317
|
-
const discoveryResult = await params.api.getCliGitHubDiscovery(
|
|
318
|
-
params.userToken
|
|
319
|
-
);
|
|
320
|
-
return discoveryResult.installations;
|
|
321
|
-
}
|
|
322
|
-
async function selectGitHubInstallation(installations, canInstallNew) {
|
|
323
|
-
const options = installations.map((inst) => ({
|
|
324
|
-
value: String(inst.installationId),
|
|
325
|
-
label: inst.accountLogin,
|
|
326
|
-
hint: [
|
|
327
|
-
inst.accountType === "Organization" ? "organization" : "personal",
|
|
328
|
-
inst.conflictLabel ? `connected to ${inst.conflictLabel}` : "",
|
|
329
|
-
inst.isSuspended ? "suspended" : ""
|
|
330
|
-
].filter(Boolean).join(" \xB7 ") || void 0
|
|
331
|
-
}));
|
|
332
|
-
if (canInstallNew) {
|
|
333
|
-
options.push({
|
|
334
|
-
value: "install_new",
|
|
335
|
-
label: `Install on a new account ${chalk.dim("(creates a new personal workspace)")}`
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
const selected = await p.select({
|
|
339
|
-
message: "Select a GitHub installation",
|
|
340
|
-
options
|
|
341
|
-
});
|
|
342
|
-
if (p.isCancel(selected)) return null;
|
|
343
|
-
if (selected === "install_new") return "install_new";
|
|
344
|
-
return Number(selected);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// src/utils/project-create.ts
|
|
348
|
-
import * as p3 from "@clack/prompts";
|
|
349
|
-
import chalk3 from "chalk";
|
|
350
|
-
|
|
351
|
-
// src/utils/branch-select.ts
|
|
352
|
-
import { execSync } from "child_process";
|
|
353
|
-
import { isCancel as isCancel2, Prompt } from "@clack/core";
|
|
354
|
-
|
|
355
67
|
// src/utils/theme.ts
|
|
356
|
-
import
|
|
68
|
+
import chalk from "chalk";
|
|
357
69
|
var ORANGE = "#FC5206";
|
|
358
70
|
var PINK = "#D51977";
|
|
359
71
|
var BLUE = "#2450A9";
|
|
360
72
|
var noColor = process.env.NO_COLOR === "1" || process.env.FORCE_COLOR === "0";
|
|
361
|
-
var hex = (color) => (s) => noColor ? s :
|
|
362
|
-
var dim = (s) => noColor ? s :
|
|
363
|
-
var bld = (s) => noColor ? s :
|
|
364
|
-
var grn = (s) => noColor ? s :
|
|
365
|
-
var ylw = (s) => noColor ? s :
|
|
366
|
-
var red = (s) => noColor ? s :
|
|
73
|
+
var hex = (color) => (s) => noColor ? s : chalk.hex(color)(s);
|
|
74
|
+
var dim = (s) => noColor ? s : chalk.dim(s);
|
|
75
|
+
var bld = (s) => noColor ? s : chalk.bold(s);
|
|
76
|
+
var grn = (s) => noColor ? s : chalk.green(s);
|
|
77
|
+
var ylw = (s) => noColor ? s : chalk.yellow(s);
|
|
78
|
+
var red = (s) => noColor ? s : chalk.red(s);
|
|
367
79
|
var highlight = hex(PINK);
|
|
368
80
|
var info = hex(BLUE);
|
|
369
81
|
var active = hex(ORANGE);
|
|
370
82
|
|
|
83
|
+
// src/utils/project-create.ts
|
|
84
|
+
import * as p2 from "@clack/prompts";
|
|
85
|
+
import chalk2 from "chalk";
|
|
86
|
+
|
|
371
87
|
// src/utils/branch-select.ts
|
|
88
|
+
import { execSync } from "child_process";
|
|
89
|
+
import { isCancel, Prompt } from "@clack/core";
|
|
372
90
|
var S_BAR = "\u2502";
|
|
373
91
|
var S_BAR_END = "\u2514";
|
|
374
92
|
var S_ACTIVE = "\u25C6";
|
|
@@ -630,13 +348,13 @@ ${symbol(this.state)} ${message}
|
|
|
630
348
|
}
|
|
631
349
|
});
|
|
632
350
|
const result = await prompt.prompt();
|
|
633
|
-
if (
|
|
351
|
+
if (isCancel(result)) return null;
|
|
634
352
|
return result;
|
|
635
353
|
}
|
|
636
354
|
|
|
637
355
|
// src/utils/locale-search.ts
|
|
638
|
-
import { isCancel as
|
|
639
|
-
import * as
|
|
356
|
+
import { isCancel as isCancel2, Prompt as Prompt2 } from "@clack/core";
|
|
357
|
+
import * as p from "@clack/prompts";
|
|
640
358
|
var S_BAR2 = "\u2502";
|
|
641
359
|
var S_BAR_END2 = "\u2514";
|
|
642
360
|
var S_ACTIVE2 = "\u25C6";
|
|
@@ -811,7 +529,7 @@ ${symbol2(this.state)} ${message}
|
|
|
811
529
|
}
|
|
812
530
|
});
|
|
813
531
|
const result = await prompt.prompt();
|
|
814
|
-
if (
|
|
532
|
+
if (isCancel2(result)) return null;
|
|
815
533
|
return result;
|
|
816
534
|
}
|
|
817
535
|
async function searchSelectLocale(options, message, initialValue) {
|
|
@@ -833,7 +551,7 @@ async function searchMultiSelectLocales(options, message, initialValues) {
|
|
|
833
551
|
if (result === null) return null;
|
|
834
552
|
const picks = result;
|
|
835
553
|
if (picks.length === 0) {
|
|
836
|
-
|
|
554
|
+
p.log.warn(
|
|
837
555
|
"At least one target language is required. Please select at least one."
|
|
838
556
|
);
|
|
839
557
|
return searchMultiSelectLocales(options, message, initialValues);
|
|
@@ -863,12 +581,12 @@ function buildLanguageOptions(locales) {
|
|
|
863
581
|
async function runProjectCreate(params) {
|
|
864
582
|
const { api, userToken, organizationId, repoCanonical } = params;
|
|
865
583
|
const projectName = (params.defaultName ?? "my-project").trim();
|
|
866
|
-
|
|
584
|
+
p2.log.success(`Project: ${chalk2.bold(projectName)}`);
|
|
867
585
|
let sourceLocales;
|
|
868
586
|
try {
|
|
869
587
|
({ sourceLocales } = await api.listLocales(userToken));
|
|
870
588
|
} catch {
|
|
871
|
-
|
|
589
|
+
p2.log.error(
|
|
872
590
|
"Failed to fetch supported locales. Check your connection and try again."
|
|
873
591
|
);
|
|
874
592
|
return null;
|
|
@@ -876,7 +594,7 @@ async function runProjectCreate(params) {
|
|
|
876
594
|
const languageOptions = buildLanguageOptions(sourceLocales);
|
|
877
595
|
const appDir = params.defaultAppDir ?? "";
|
|
878
596
|
if (appDir) {
|
|
879
|
-
|
|
597
|
+
p2.log.success(`App directory: ${chalk2.bold(appDir)}`);
|
|
880
598
|
}
|
|
881
599
|
const sourceLocale = await searchSelectLocale(
|
|
882
600
|
languageOptions,
|
|
@@ -888,7 +606,7 @@ async function runProjectCreate(params) {
|
|
|
888
606
|
try {
|
|
889
607
|
compatibleTargets = await api.listCompatibleLocales(userToken, sourceLocale);
|
|
890
608
|
} catch {
|
|
891
|
-
|
|
609
|
+
p2.log.error(
|
|
892
610
|
"Failed to fetch compatible target locales. Check your connection and try again."
|
|
893
611
|
);
|
|
894
612
|
return null;
|
|
@@ -903,7 +621,7 @@ async function runProjectCreate(params) {
|
|
|
903
621
|
);
|
|
904
622
|
if (targetLocales === null) return null;
|
|
905
623
|
if (targetLocales.length === 0) {
|
|
906
|
-
|
|
624
|
+
p2.log.warn(
|
|
907
625
|
"No target languages selected \u2014 you can add them later from the dashboard."
|
|
908
626
|
);
|
|
909
627
|
}
|
|
@@ -921,7 +639,7 @@ async function runProjectCreate(params) {
|
|
|
921
639
|
});
|
|
922
640
|
if (result === null) return null;
|
|
923
641
|
if (result.length === 0) {
|
|
924
|
-
|
|
642
|
+
p2.log.warn(
|
|
925
643
|
"At least one branch is required. Please select at least one."
|
|
926
644
|
);
|
|
927
645
|
initial = [detected.defaultBranch];
|
|
@@ -941,110 +659,390 @@ async function runProjectCreate(params) {
|
|
|
941
659
|
appDirs: appDir ? [appDir] : [],
|
|
942
660
|
repoCanonical
|
|
943
661
|
});
|
|
944
|
-
|
|
662
|
+
p2.log.success(`Project ${chalk2.bold(result.projectName)} created!`);
|
|
945
663
|
return result;
|
|
946
664
|
} catch (error) {
|
|
947
665
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
948
|
-
|
|
666
|
+
p2.log.error(`Failed to create project: ${message}`);
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
async function runAppCreate(params) {
|
|
671
|
+
const { api, userToken, projectId, projectName, repoCanonical } = params;
|
|
672
|
+
const existingScopes = new Set(params.existingApps.map((a) => a.appDir));
|
|
673
|
+
let sourceLocales;
|
|
674
|
+
try {
|
|
675
|
+
({ sourceLocales } = await api.listLocales(userToken));
|
|
676
|
+
} catch {
|
|
677
|
+
p2.log.error(
|
|
678
|
+
"Failed to fetch supported locales. Check your connection and try again."
|
|
679
|
+
);
|
|
949
680
|
return null;
|
|
950
681
|
}
|
|
682
|
+
const languageOptions = buildLanguageOptions(sourceLocales);
|
|
683
|
+
const appDir = params.defaultAppDir ?? "";
|
|
684
|
+
if (existingScopes.has(appDir)) {
|
|
685
|
+
p2.log.error(`App directory "${appDir}" is already configured for this project.`);
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
if (appDir) {
|
|
689
|
+
p2.log.success(`App directory: ${chalk2.bold(appDir)}`);
|
|
690
|
+
}
|
|
691
|
+
const sourceLocale = await searchSelectLocale(
|
|
692
|
+
languageOptions,
|
|
693
|
+
"Source language",
|
|
694
|
+
"en"
|
|
695
|
+
);
|
|
696
|
+
if (sourceLocale === null) return null;
|
|
697
|
+
let compatibleTargets;
|
|
698
|
+
try {
|
|
699
|
+
compatibleTargets = await api.listCompatibleLocales(userToken, sourceLocale);
|
|
700
|
+
} catch {
|
|
701
|
+
p2.log.error(
|
|
702
|
+
"Failed to fetch compatible target locales. Check your connection and try again."
|
|
703
|
+
);
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
const targetOptions = buildLocaleOptions(compatibleTargets).filter(
|
|
707
|
+
(opt) => opt.bcp47 !== sourceLocale
|
|
708
|
+
);
|
|
709
|
+
const targetLocales = await searchMultiSelectLocales(
|
|
710
|
+
targetOptions,
|
|
711
|
+
"Target languages"
|
|
712
|
+
);
|
|
713
|
+
if (targetLocales === null) return null;
|
|
714
|
+
if (targetLocales.length === 0) {
|
|
715
|
+
p2.log.warn(
|
|
716
|
+
"No target languages selected \u2014 you can add them later from the dashboard."
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
const detectedApp = detectGitBranches();
|
|
720
|
+
let appPushBranches = [];
|
|
721
|
+
{
|
|
722
|
+
let initial = [detectedApp.defaultBranch];
|
|
723
|
+
while (appPushBranches.length === 0) {
|
|
724
|
+
const result = await filterableBranchSelect({
|
|
725
|
+
message: "Which branches should trigger translations?",
|
|
726
|
+
branches: detectedApp.branches,
|
|
727
|
+
defaultBranch: detectedApp.defaultBranch,
|
|
728
|
+
initialValues: initial
|
|
729
|
+
});
|
|
730
|
+
if (result === null) return null;
|
|
731
|
+
if (result.length === 0) {
|
|
732
|
+
p2.log.warn("At least one branch is required.");
|
|
733
|
+
initial = [detectedApp.defaultBranch];
|
|
734
|
+
} else {
|
|
735
|
+
appPushBranches = result;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const targetBranches = appPushBranches;
|
|
740
|
+
try {
|
|
741
|
+
const result = await api.createProject(userToken, {
|
|
742
|
+
projectId,
|
|
743
|
+
appDir,
|
|
744
|
+
sourceLocale,
|
|
745
|
+
targetLocales,
|
|
746
|
+
targetBranches,
|
|
747
|
+
repoCanonical: repoCanonical ?? ""
|
|
748
|
+
});
|
|
749
|
+
p2.log.success(
|
|
750
|
+
`App ${chalk2.bold(appDir)} added to ${chalk2.bold(projectName)}!`
|
|
751
|
+
);
|
|
752
|
+
return {
|
|
753
|
+
projectId: result.projectId,
|
|
754
|
+
projectName: result.projectName,
|
|
755
|
+
apiKey: result.apiKey,
|
|
756
|
+
appDir: result.appDir,
|
|
757
|
+
sourceLocale,
|
|
758
|
+
targetLocales,
|
|
759
|
+
targetBranches
|
|
760
|
+
};
|
|
761
|
+
} catch (error) {
|
|
762
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
763
|
+
p2.log.error(`Failed to add app: ${message}`);
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/utils/github-connect.ts
|
|
769
|
+
import { spawn } from "child_process";
|
|
770
|
+
import * as p3 from "@clack/prompts";
|
|
771
|
+
import chalk3 from "chalk";
|
|
772
|
+
|
|
773
|
+
// src/utils/local-server.ts
|
|
774
|
+
import { createServer } from "http";
|
|
775
|
+
import { URL as URL2 } from "url";
|
|
776
|
+
function startCallbackServer() {
|
|
777
|
+
return new Promise((resolve2, reject) => {
|
|
778
|
+
let settled = false;
|
|
779
|
+
let callbackResolve = null;
|
|
780
|
+
let callbackReject = null;
|
|
781
|
+
const callbackPromise = new Promise((res, rej) => {
|
|
782
|
+
callbackResolve = res;
|
|
783
|
+
callbackReject = rej;
|
|
784
|
+
});
|
|
785
|
+
const server = createServer((req, res) => {
|
|
786
|
+
if (!req.url) {
|
|
787
|
+
res.writeHead(400);
|
|
788
|
+
res.end();
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
let pathname;
|
|
792
|
+
let params;
|
|
793
|
+
try {
|
|
794
|
+
const parsed = new URL2(req.url, "http://localhost");
|
|
795
|
+
pathname = parsed.pathname;
|
|
796
|
+
params = Object.fromEntries(parsed.searchParams.entries());
|
|
797
|
+
} catch {
|
|
798
|
+
res.writeHead(400);
|
|
799
|
+
res.end("Bad request");
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (pathname !== "/callback") {
|
|
803
|
+
res.writeHead(404);
|
|
804
|
+
res.end("Not found");
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
808
|
+
res.end(
|
|
809
|
+
'<!DOCTYPE html><html><head><title>Authenticated</title></head><body style="font-family:sans-serif;text-align:center;padding:3rem;"><h2>Authenticated</h2><p>Return to your terminal to continue. You can close this tab.</p></body></html>'
|
|
810
|
+
);
|
|
811
|
+
if (callbackResolve) {
|
|
812
|
+
callbackResolve(params);
|
|
813
|
+
callbackResolve = null;
|
|
814
|
+
}
|
|
815
|
+
setImmediate(() => server.close());
|
|
816
|
+
});
|
|
817
|
+
server.on("error", (err) => {
|
|
818
|
+
if (!settled) {
|
|
819
|
+
settled = true;
|
|
820
|
+
if (callbackReject) callbackReject(err);
|
|
821
|
+
reject(err);
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
server.listen(0, "127.0.0.1", () => {
|
|
825
|
+
if (settled) return;
|
|
826
|
+
settled = true;
|
|
827
|
+
const port = server.address().port;
|
|
828
|
+
resolve2({
|
|
829
|
+
port,
|
|
830
|
+
waitForCallback: () => callbackPromise,
|
|
831
|
+
close: () => server.close()
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// src/utils/github-connect.ts
|
|
838
|
+
async function tryOpenBrowser(url) {
|
|
839
|
+
if (!process.stdout.isTTY || process.env.CI === "true") {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
const platform = process.platform;
|
|
843
|
+
let command;
|
|
844
|
+
let args;
|
|
845
|
+
if (platform === "darwin") {
|
|
846
|
+
command = "open";
|
|
847
|
+
args = [url];
|
|
848
|
+
} else if (platform === "win32") {
|
|
849
|
+
command = "rundll32";
|
|
850
|
+
args = ["url.dll,FileProtocolHandler", url];
|
|
851
|
+
} else {
|
|
852
|
+
command = "xdg-open";
|
|
853
|
+
args = [url];
|
|
854
|
+
}
|
|
855
|
+
return new Promise((resolve2) => {
|
|
856
|
+
try {
|
|
857
|
+
const child = spawn(command, args, {
|
|
858
|
+
detached: true,
|
|
859
|
+
stdio: "ignore",
|
|
860
|
+
windowsHide: true
|
|
861
|
+
});
|
|
862
|
+
let settled = false;
|
|
863
|
+
child.once("spawn", () => {
|
|
864
|
+
if (settled) return;
|
|
865
|
+
settled = true;
|
|
866
|
+
child.unref();
|
|
867
|
+
resolve2(true);
|
|
868
|
+
});
|
|
869
|
+
child.once("error", () => {
|
|
870
|
+
if (settled) return;
|
|
871
|
+
settled = true;
|
|
872
|
+
resolve2(false);
|
|
873
|
+
});
|
|
874
|
+
setTimeout(() => {
|
|
875
|
+
if (settled) return;
|
|
876
|
+
settled = true;
|
|
877
|
+
resolve2(false);
|
|
878
|
+
}, 300);
|
|
879
|
+
} catch {
|
|
880
|
+
resolve2(false);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
951
883
|
}
|
|
952
|
-
async function
|
|
953
|
-
|
|
954
|
-
const existingScopes = new Set(params.existingApps.map((a) => a.appDir));
|
|
955
|
-
let sourceLocales;
|
|
884
|
+
async function runGitHubInstallFlow(params) {
|
|
885
|
+
let server = null;
|
|
956
886
|
try {
|
|
957
|
-
|
|
887
|
+
server = await startCallbackServer();
|
|
958
888
|
} catch {
|
|
959
|
-
p3.log.error(
|
|
960
|
-
"Failed to fetch supported locales. Check your connection and try again."
|
|
961
|
-
);
|
|
962
|
-
return null;
|
|
963
889
|
}
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
890
|
+
const { installUrl } = await params.api.startCliGitHubInstall(
|
|
891
|
+
params.userToken,
|
|
892
|
+
{
|
|
893
|
+
organizationId: params.organizationId,
|
|
894
|
+
callbackPort: server?.port
|
|
895
|
+
}
|
|
896
|
+
);
|
|
897
|
+
p3.log.info("Opening GitHub to install the Vocoder App...");
|
|
898
|
+
p3.note(installUrl, "Install URL");
|
|
899
|
+
if (process.stdin.isTTY && process.stdout.isTTY && process.env.CI !== "true") {
|
|
900
|
+
const shouldOpen = params.yes ? true : await p3.confirm({ message: "Open in your browser?" });
|
|
901
|
+
if (p3.isCancel(shouldOpen)) {
|
|
902
|
+
server?.close();
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
if (shouldOpen) {
|
|
906
|
+
const opened = await tryOpenBrowser(installUrl);
|
|
907
|
+
if (!opened) {
|
|
908
|
+
p3.log.info(
|
|
909
|
+
"Could not open a browser automatically. Use the URL above."
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
969
913
|
}
|
|
970
|
-
|
|
971
|
-
|
|
914
|
+
const connectSpinner = p3.spinner();
|
|
915
|
+
connectSpinner.start("Waiting for GitHub App installation...");
|
|
916
|
+
if (server) {
|
|
917
|
+
try {
|
|
918
|
+
const params_timeout = 15 * 60 * 1e3;
|
|
919
|
+
const callbackParams = await Promise.race([
|
|
920
|
+
server.waitForCallback(),
|
|
921
|
+
new Promise(
|
|
922
|
+
(resolve2) => setTimeout(() => resolve2(null), params_timeout)
|
|
923
|
+
)
|
|
924
|
+
]);
|
|
925
|
+
server.close();
|
|
926
|
+
if (!callbackParams) {
|
|
927
|
+
connectSpinner.stop("GitHub App installation timed out");
|
|
928
|
+
p3.log.error(
|
|
929
|
+
"The installation flow timed out. Run `vocoder init` again."
|
|
930
|
+
);
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
if (callbackParams.error) {
|
|
934
|
+
connectSpinner.stop("GitHub App installation failed");
|
|
935
|
+
p3.log.error(callbackParams.error);
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
const { organizationId, connectionLabel, workspace_created } = callbackParams;
|
|
939
|
+
if (!organizationId || !connectionLabel) {
|
|
940
|
+
connectSpinner.stop("GitHub App installation incomplete");
|
|
941
|
+
p3.log.error("Missing organization or connection data from callback.");
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
944
|
+
connectSpinner.stop(
|
|
945
|
+
`Connected to GitHub as ${chalk3.bold(connectionLabel)}`
|
|
946
|
+
);
|
|
947
|
+
const orgName = workspace_created ? connectionLabel : organizationId;
|
|
948
|
+
return {
|
|
949
|
+
organizationId,
|
|
950
|
+
organizationName: orgName,
|
|
951
|
+
connectionLabel
|
|
952
|
+
};
|
|
953
|
+
} catch {
|
|
954
|
+
server.close();
|
|
955
|
+
connectSpinner.stop("GitHub App installation failed");
|
|
956
|
+
return null;
|
|
957
|
+
}
|
|
972
958
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
"
|
|
976
|
-
"en"
|
|
959
|
+
connectSpinner.stop("Could not detect GitHub App installation automatically");
|
|
960
|
+
p3.log.warn(
|
|
961
|
+
"Complete the installation in your browser, then run `vocoder init` again."
|
|
977
962
|
);
|
|
978
|
-
|
|
979
|
-
|
|
963
|
+
return null;
|
|
964
|
+
}
|
|
965
|
+
async function runGitHubDiscoveryFlow(params) {
|
|
966
|
+
let server = null;
|
|
980
967
|
try {
|
|
981
|
-
|
|
968
|
+
server = await startCallbackServer();
|
|
982
969
|
} catch {
|
|
983
|
-
p3.log.error(
|
|
984
|
-
"Failed to fetch compatible target locales. Check your connection and try again."
|
|
985
|
-
);
|
|
986
|
-
return null;
|
|
987
970
|
}
|
|
988
|
-
const
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
)
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
971
|
+
const { oauthUrl } = await params.api.startCliGitHubOAuth(params.userToken, {
|
|
972
|
+
organizationId: params.organizationId,
|
|
973
|
+
callbackPort: server?.port
|
|
974
|
+
});
|
|
975
|
+
p3.log.info("Opening GitHub to authorize your account...");
|
|
976
|
+
p3.note("Complete authorization in your browser.");
|
|
977
|
+
if (process.stdin.isTTY && process.stdout.isTTY && process.env.CI !== "true") {
|
|
978
|
+
const shouldOpen = params.yes ? true : await p3.confirm({ message: "Open in your browser?" });
|
|
979
|
+
if (p3.isCancel(shouldOpen)) {
|
|
980
|
+
server?.close();
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
if (shouldOpen) {
|
|
984
|
+
const opened = await tryOpenBrowser(oauthUrl);
|
|
985
|
+
if (!opened) {
|
|
986
|
+
p3.log.info(`Could not open browser automatically. Visit: ${oauthUrl}`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
1000
989
|
}
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
{
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
|
|
990
|
+
const oauthSpinner = p3.spinner();
|
|
991
|
+
oauthSpinner.start("Waiting for GitHub authorization...");
|
|
992
|
+
if (server) {
|
|
993
|
+
try {
|
|
994
|
+
const timeoutMs = 10 * 60 * 1e3;
|
|
995
|
+
const callbackParams = await Promise.race([
|
|
996
|
+
server.waitForCallback(),
|
|
997
|
+
new Promise(
|
|
998
|
+
(resolve2) => setTimeout(() => resolve2(null), timeoutMs)
|
|
999
|
+
)
|
|
1000
|
+
]);
|
|
1001
|
+
server.close();
|
|
1002
|
+
if (!callbackParams) {
|
|
1003
|
+
oauthSpinner.stop("GitHub authorization timed out");
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
if (callbackParams.error) {
|
|
1007
|
+
oauthSpinner.stop("GitHub authorization failed");
|
|
1008
|
+
p3.log.error(callbackParams.error);
|
|
1009
|
+
return null;
|
|
1018
1010
|
}
|
|
1011
|
+
} catch {
|
|
1012
|
+
server.close();
|
|
1013
|
+
oauthSpinner.stop("GitHub authorization failed");
|
|
1014
|
+
return null;
|
|
1019
1015
|
}
|
|
1020
1016
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1017
|
+
oauthSpinner.stop("GitHub account authorized");
|
|
1018
|
+
const discoveryResult = await params.api.getCliGitHubDiscovery(
|
|
1019
|
+
params.userToken
|
|
1020
|
+
);
|
|
1021
|
+
return discoveryResult.installations;
|
|
1022
|
+
}
|
|
1023
|
+
async function selectGitHubInstallation(installations, canInstallNew) {
|
|
1024
|
+
const options = installations.map((inst) => ({
|
|
1025
|
+
value: String(inst.installationId),
|
|
1026
|
+
label: inst.accountLogin,
|
|
1027
|
+
hint: [
|
|
1028
|
+
inst.accountType === "Organization" ? "organization" : "personal",
|
|
1029
|
+
inst.conflictLabel ? `connected to ${inst.conflictLabel}` : "",
|
|
1030
|
+
inst.isSuspended ? "suspended" : ""
|
|
1031
|
+
].filter(Boolean).join(" \xB7 ") || void 0
|
|
1032
|
+
}));
|
|
1033
|
+
if (canInstallNew) {
|
|
1034
|
+
options.push({
|
|
1035
|
+
value: "install_new",
|
|
1036
|
+
label: `Install on a new account ${chalk3.dim("(creates a new personal workspace)")}`
|
|
1030
1037
|
});
|
|
1031
|
-
p3.log.success(
|
|
1032
|
-
`App ${chalk3.bold(appDir)} added to ${chalk3.bold(projectName)}!`
|
|
1033
|
-
);
|
|
1034
|
-
return {
|
|
1035
|
-
projectId: result.projectId,
|
|
1036
|
-
projectName: result.projectName,
|
|
1037
|
-
apiKey: result.apiKey,
|
|
1038
|
-
appDir: result.appDir,
|
|
1039
|
-
sourceLocale,
|
|
1040
|
-
targetLocales,
|
|
1041
|
-
targetBranches
|
|
1042
|
-
};
|
|
1043
|
-
} catch (error) {
|
|
1044
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1045
|
-
p3.log.error(`Failed to add app: ${message}`);
|
|
1046
|
-
return null;
|
|
1047
1038
|
}
|
|
1039
|
+
const selected = await p3.select({
|
|
1040
|
+
message: "Select a GitHub installation",
|
|
1041
|
+
options
|
|
1042
|
+
});
|
|
1043
|
+
if (p3.isCancel(selected)) return null;
|
|
1044
|
+
if (selected === "install_new") return "install_new";
|
|
1045
|
+
return Number(selected);
|
|
1048
1046
|
}
|
|
1049
1047
|
|
|
1050
1048
|
// src/commands/init.ts
|
|
@@ -1418,6 +1416,7 @@ async function runAuthFlow(api, options, reauth = false, repoCanonical) {
|
|
|
1418
1416
|
const session = await api.startCliAuthSession(server?.port, repoCanonical);
|
|
1419
1417
|
const browserUrl = reauth ? session.verificationUrl : session.installUrl ?? session.verificationUrl;
|
|
1420
1418
|
const expiresAt = new Date(session.expiresAt).getTime();
|
|
1419
|
+
p5.log.info(browserUrl);
|
|
1421
1420
|
if (options.ci) {
|
|
1422
1421
|
process.stdout.write(`VOCODER_AUTH_URL: ${browserUrl}
|
|
1423
1422
|
`);
|
|
@@ -1494,46 +1493,71 @@ async function runAuthFlow(api, options, reauth = false, repoCanonical) {
|
|
|
1494
1493
|
let rawToken = null;
|
|
1495
1494
|
let callbackOrganizationId;
|
|
1496
1495
|
let callbackDiscoveryReady = false;
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
]);
|
|
1507
|
-
if (params && typeof params.token === "string") {
|
|
1508
|
-
rawToken = params.token;
|
|
1509
|
-
if (typeof params.organizationId === "string" && params.organizationId) {
|
|
1510
|
-
callbackOrganizationId = params.organizationId;
|
|
1511
|
-
}
|
|
1512
|
-
if (params.discovery_ready === "1") {
|
|
1513
|
-
callbackDiscoveryReady = true;
|
|
1496
|
+
const deadline = Math.min(expiresAt, Date.now() + 10 * 60 * 1e3);
|
|
1497
|
+
let stopPolling = false;
|
|
1498
|
+
const serverCallback = server ? server.waitForCallback().catch(() => null) : Promise.resolve(null);
|
|
1499
|
+
const sessionPoll = (async () => {
|
|
1500
|
+
while (!stopPolling && Date.now() < expiresAt) {
|
|
1501
|
+
try {
|
|
1502
|
+
const result = await api.pollCliAuthSession(session.sessionId);
|
|
1503
|
+
if (result.status === "complete" || result.status === "failed") {
|
|
1504
|
+
return result;
|
|
1514
1505
|
}
|
|
1506
|
+
} catch {
|
|
1515
1507
|
}
|
|
1516
|
-
|
|
1517
|
-
} finally {
|
|
1518
|
-
server.close();
|
|
1508
|
+
if (!stopPolling) await sleep(2e3);
|
|
1519
1509
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1510
|
+
return null;
|
|
1511
|
+
})();
|
|
1512
|
+
const winner = await new Promise((resolve2) => {
|
|
1513
|
+
let done = false;
|
|
1514
|
+
serverCallback.then((params) => {
|
|
1515
|
+
if (done || params === null || typeof params.token !== "string") return;
|
|
1516
|
+
done = true;
|
|
1517
|
+
resolve2({ kind: "server", params });
|
|
1518
|
+
}).catch(() => {
|
|
1519
|
+
});
|
|
1520
|
+
sessionPoll.then((result) => {
|
|
1521
|
+
if (done || result === null) return;
|
|
1522
|
+
if (result.status === "complete" || result.status === "failed") {
|
|
1523
|
+
done = true;
|
|
1524
|
+
resolve2({
|
|
1525
|
+
kind: "poll",
|
|
1526
|
+
result
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
}).catch(() => {
|
|
1530
|
+
});
|
|
1531
|
+
setTimeout(
|
|
1532
|
+
() => {
|
|
1533
|
+
if (!done) {
|
|
1534
|
+
done = true;
|
|
1535
|
+
resolve2(null);
|
|
1528
1536
|
}
|
|
1529
|
-
|
|
1537
|
+
},
|
|
1538
|
+
Math.max(0, deadline - Date.now())
|
|
1539
|
+
);
|
|
1540
|
+
});
|
|
1541
|
+
stopPolling = true;
|
|
1542
|
+
server?.close();
|
|
1543
|
+
if (winner !== null) {
|
|
1544
|
+
if (winner.kind === "server") {
|
|
1545
|
+
rawToken = winner.params.token;
|
|
1546
|
+
if (typeof winner.params.organizationId === "string" && winner.params.organizationId) {
|
|
1547
|
+
callbackOrganizationId = winner.params.organizationId;
|
|
1530
1548
|
}
|
|
1531
|
-
if (
|
|
1532
|
-
|
|
1533
|
-
p5.log.error(result.reason);
|
|
1534
|
-
return null;
|
|
1549
|
+
if (winner.params.discovery_ready === "1") {
|
|
1550
|
+
callbackDiscoveryReady = true;
|
|
1535
1551
|
}
|
|
1536
|
-
|
|
1552
|
+
} else if (winner.result.status === "complete") {
|
|
1553
|
+
rawToken = winner.result.token;
|
|
1554
|
+
if (winner.result.organizationId) {
|
|
1555
|
+
callbackOrganizationId = winner.result.organizationId;
|
|
1556
|
+
}
|
|
1557
|
+
} else {
|
|
1558
|
+
authSpinner.stop();
|
|
1559
|
+
p5.log.error(winner.result.reason);
|
|
1560
|
+
return null;
|
|
1537
1561
|
}
|
|
1538
1562
|
}
|
|
1539
1563
|
if (!rawToken) {
|
|
@@ -2146,6 +2170,9 @@ function matchBranchPattern(branch, pattern) {
|
|
|
2146
2170
|
import * as p6 from "@clack/prompts";
|
|
2147
2171
|
import { config as loadEnv2 } from "dotenv";
|
|
2148
2172
|
loadEnv2();
|
|
2173
|
+
function extractShortCodeFromApiKey(apiKey) {
|
|
2174
|
+
return apiKey.slice(4, 14);
|
|
2175
|
+
}
|
|
2149
2176
|
function validateLocalConfig(config) {
|
|
2150
2177
|
if (!config.apiKey || config.apiKey.length === 0) {
|
|
2151
2178
|
throw new Error("VOCODER_API_KEY is required. Set it in your .env file.");
|
|
@@ -2657,7 +2684,7 @@ async function sync(options = {}) {
|
|
|
2657
2684
|
`Deduped ${extractedStrings.length} extracted entries into ${stringEntries.length} unique source strings`
|
|
2658
2685
|
);
|
|
2659
2686
|
}
|
|
2660
|
-
const fingerprint = computeFingerprint(
|
|
2687
|
+
const fingerprint = computeFingerprint(extractShortCodeFromApiKey(localConfig.apiKey), sourceStrings);
|
|
2661
2688
|
if (!options.force) {
|
|
2662
2689
|
const cacheFile = getCacheFilePath(projectRoot, fingerprint);
|
|
2663
2690
|
if (existsSync3(cacheFile)) {
|