@vocoder/cli 0.14.0 → 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,218 +551,498 @@ async function searchMultiSelectLocales(options, message, initialValues) {
|
|
|
833
551
|
if (result === null) return null;
|
|
834
552
|
const picks = result;
|
|
835
553
|
if (picks.length === 0) {
|
|
554
|
+
p.log.warn(
|
|
555
|
+
"At least one target language is required. Please select at least one."
|
|
556
|
+
);
|
|
557
|
+
return searchMultiSelectLocales(options, message, initialValues);
|
|
558
|
+
}
|
|
559
|
+
return picks;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/utils/project-create.ts
|
|
563
|
+
function buildLocaleOptions(locales) {
|
|
564
|
+
return locales.map((l) => ({
|
|
565
|
+
bcp47: l.code,
|
|
566
|
+
label: `${l.name} \u2014 ${l.code}`
|
|
567
|
+
}));
|
|
568
|
+
}
|
|
569
|
+
function buildLanguageOptions(locales) {
|
|
570
|
+
const byFamily = /* @__PURE__ */ new Map();
|
|
571
|
+
for (const l of locales) {
|
|
572
|
+
const family = l.code.split("-")[0].toLowerCase();
|
|
573
|
+
const opt = { bcp47: l.code, label: `${l.name} \u2014 ${l.code}` };
|
|
574
|
+
const existing = byFamily.get(family);
|
|
575
|
+
if (!existing || l.code.length < existing.bcp47.length) {
|
|
576
|
+
byFamily.set(family, opt);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return Array.from(byFamily.values());
|
|
580
|
+
}
|
|
581
|
+
async function runProjectCreate(params) {
|
|
582
|
+
const { api, userToken, organizationId, repoCanonical } = params;
|
|
583
|
+
const projectName = (params.defaultName ?? "my-project").trim();
|
|
584
|
+
p2.log.success(`Project: ${chalk2.bold(projectName)}`);
|
|
585
|
+
let sourceLocales;
|
|
586
|
+
try {
|
|
587
|
+
({ sourceLocales } = await api.listLocales(userToken));
|
|
588
|
+
} catch {
|
|
589
|
+
p2.log.error(
|
|
590
|
+
"Failed to fetch supported locales. Check your connection and try again."
|
|
591
|
+
);
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
const languageOptions = buildLanguageOptions(sourceLocales);
|
|
595
|
+
const appDir = params.defaultAppDir ?? "";
|
|
596
|
+
if (appDir) {
|
|
597
|
+
p2.log.success(`App directory: ${chalk2.bold(appDir)}`);
|
|
598
|
+
}
|
|
599
|
+
const sourceLocale = await searchSelectLocale(
|
|
600
|
+
languageOptions,
|
|
601
|
+
"Source language (the language your code is written in)",
|
|
602
|
+
params.defaultSourceLocale ?? "en"
|
|
603
|
+
);
|
|
604
|
+
if (sourceLocale === null) return null;
|
|
605
|
+
let compatibleTargets;
|
|
606
|
+
try {
|
|
607
|
+
compatibleTargets = await api.listCompatibleLocales(userToken, sourceLocale);
|
|
608
|
+
} catch {
|
|
609
|
+
p2.log.error(
|
|
610
|
+
"Failed to fetch compatible target locales. Check your connection and try again."
|
|
611
|
+
);
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
const localeOptions = buildLocaleOptions(compatibleTargets);
|
|
615
|
+
const targetOptions = localeOptions.filter(
|
|
616
|
+
(opt) => opt.bcp47 !== sourceLocale
|
|
617
|
+
);
|
|
618
|
+
const targetLocales = await searchMultiSelectLocales(
|
|
619
|
+
targetOptions,
|
|
620
|
+
"Target languages (languages to translate into)"
|
|
621
|
+
);
|
|
622
|
+
if (targetLocales === null) return null;
|
|
623
|
+
if (targetLocales.length === 0) {
|
|
836
624
|
p2.log.warn(
|
|
837
|
-
"
|
|
625
|
+
"No target languages selected \u2014 you can add them later from the dashboard."
|
|
838
626
|
);
|
|
839
|
-
return searchMultiSelectLocales(options, message, initialValues);
|
|
840
627
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
628
|
+
const detected = detectGitBranches();
|
|
629
|
+
const initialBranches = params.defaultBranches?.length ? params.defaultBranches : [detected.defaultBranch];
|
|
630
|
+
let pushBranches = [];
|
|
631
|
+
{
|
|
632
|
+
let initial = initialBranches;
|
|
633
|
+
while (pushBranches.length === 0) {
|
|
634
|
+
const result = await filterableBranchSelect({
|
|
635
|
+
message: "Which branches should trigger translations?",
|
|
636
|
+
branches: detected.branches,
|
|
637
|
+
defaultBranch: detected.defaultBranch,
|
|
638
|
+
initialValues: initial
|
|
639
|
+
});
|
|
640
|
+
if (result === null) return null;
|
|
641
|
+
if (result.length === 0) {
|
|
642
|
+
p2.log.warn(
|
|
643
|
+
"At least one branch is required. Please select at least one."
|
|
644
|
+
);
|
|
645
|
+
initial = [detected.defaultBranch];
|
|
646
|
+
} else {
|
|
647
|
+
pushBranches = result;
|
|
648
|
+
}
|
|
859
649
|
}
|
|
860
650
|
}
|
|
861
|
-
|
|
651
|
+
const targetBranches = pushBranches;
|
|
652
|
+
try {
|
|
653
|
+
const result = await api.createProject(userToken, {
|
|
654
|
+
organizationId,
|
|
655
|
+
name: projectName,
|
|
656
|
+
sourceLocale,
|
|
657
|
+
targetLocales,
|
|
658
|
+
targetBranches,
|
|
659
|
+
appDirs: appDir ? [appDir] : [],
|
|
660
|
+
repoCanonical
|
|
661
|
+
});
|
|
662
|
+
p2.log.success(`Project ${chalk2.bold(result.projectName)} created!`);
|
|
663
|
+
return result;
|
|
664
|
+
} catch (error) {
|
|
665
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
666
|
+
p2.log.error(`Failed to create project: ${message}`);
|
|
667
|
+
return null;
|
|
668
|
+
}
|
|
862
669
|
}
|
|
863
|
-
async function
|
|
864
|
-
const { api, userToken,
|
|
865
|
-
const
|
|
866
|
-
p3.log.success(`Project: ${chalk3.bold(projectName)}`);
|
|
670
|
+
async function runAppCreate(params) {
|
|
671
|
+
const { api, userToken, projectId, projectName, repoCanonical } = params;
|
|
672
|
+
const existingScopes = new Set(params.existingApps.map((a) => a.appDir));
|
|
867
673
|
let sourceLocales;
|
|
868
674
|
try {
|
|
869
675
|
({ sourceLocales } = await api.listLocales(userToken));
|
|
870
676
|
} catch {
|
|
871
|
-
|
|
677
|
+
p2.log.error(
|
|
872
678
|
"Failed to fetch supported locales. Check your connection and try again."
|
|
873
679
|
);
|
|
874
680
|
return null;
|
|
875
681
|
}
|
|
876
682
|
const languageOptions = buildLanguageOptions(sourceLocales);
|
|
877
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
|
+
}
|
|
878
688
|
if (appDir) {
|
|
879
|
-
|
|
689
|
+
p2.log.success(`App directory: ${chalk2.bold(appDir)}`);
|
|
880
690
|
}
|
|
881
691
|
const sourceLocale = await searchSelectLocale(
|
|
882
692
|
languageOptions,
|
|
883
|
-
"Source language
|
|
884
|
-
|
|
693
|
+
"Source language",
|
|
694
|
+
"en"
|
|
885
695
|
);
|
|
886
696
|
if (sourceLocale === null) return null;
|
|
887
697
|
let compatibleTargets;
|
|
888
698
|
try {
|
|
889
|
-
compatibleTargets = await api.listCompatibleLocales(userToken, sourceLocale);
|
|
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
|
+
});
|
|
883
|
+
}
|
|
884
|
+
async function runGitHubInstallFlow(params) {
|
|
885
|
+
let server = null;
|
|
886
|
+
try {
|
|
887
|
+
server = await startCallbackServer();
|
|
890
888
|
} catch {
|
|
891
|
-
p3.log.error(
|
|
892
|
-
"Failed to fetch compatible target locales. Check your connection and try again."
|
|
893
|
-
);
|
|
894
|
-
return null;
|
|
895
889
|
}
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
"Target languages (languages to translate into)"
|
|
890
|
+
const { installUrl } = await params.api.startCliGitHubInstall(
|
|
891
|
+
params.userToken,
|
|
892
|
+
{
|
|
893
|
+
organizationId: params.organizationId,
|
|
894
|
+
callbackPort: server?.port
|
|
895
|
+
}
|
|
903
896
|
);
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
)
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
message: "Which branches should trigger translations?",
|
|
918
|
-
branches: detected.branches,
|
|
919
|
-
defaultBranch: detected.defaultBranch,
|
|
920
|
-
initialValues: initial
|
|
921
|
-
});
|
|
922
|
-
if (result === null) return null;
|
|
923
|
-
if (result.length === 0) {
|
|
924
|
-
p3.log.warn(
|
|
925
|
-
"At least one branch is required. Please select at least one."
|
|
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."
|
|
926
910
|
);
|
|
927
|
-
initial = [detected.defaultBranch];
|
|
928
|
-
} else {
|
|
929
|
-
pushBranches = result;
|
|
930
911
|
}
|
|
931
912
|
}
|
|
932
913
|
}
|
|
933
|
-
const
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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
|
`);
|
|
@@ -2171,6 +2170,9 @@ function matchBranchPattern(branch, pattern) {
|
|
|
2171
2170
|
import * as p6 from "@clack/prompts";
|
|
2172
2171
|
import { config as loadEnv2 } from "dotenv";
|
|
2173
2172
|
loadEnv2();
|
|
2173
|
+
function extractShortCodeFromApiKey(apiKey) {
|
|
2174
|
+
return apiKey.slice(4, 14);
|
|
2175
|
+
}
|
|
2174
2176
|
function validateLocalConfig(config) {
|
|
2175
2177
|
if (!config.apiKey || config.apiKey.length === 0) {
|
|
2176
2178
|
throw new Error("VOCODER_API_KEY is required. Set it in your .env file.");
|
|
@@ -2682,7 +2684,7 @@ async function sync(options = {}) {
|
|
|
2682
2684
|
`Deduped ${extractedStrings.length} extracted entries into ${stringEntries.length} unique source strings`
|
|
2683
2685
|
);
|
|
2684
2686
|
}
|
|
2685
|
-
const fingerprint = computeFingerprint(
|
|
2687
|
+
const fingerprint = computeFingerprint(extractShortCodeFromApiKey(localConfig.apiKey), sourceStrings);
|
|
2686
2688
|
if (!options.force) {
|
|
2687
2689
|
const cacheFile = getCacheFilePath(projectRoot, fingerprint);
|
|
2688
2690
|
if (existsSync3(cacheFile)) {
|