@vocoder/cli 0.1.9 → 0.1.11
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 +210 -74
- package/dist/bin.mjs.map +1 -1
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -305,7 +305,10 @@ async function selectGitHubInstallation(installations, canInstallNew) {
|
|
|
305
305
|
].filter(Boolean).join(" \xB7 ") || void 0
|
|
306
306
|
}));
|
|
307
307
|
if (canInstallNew) {
|
|
308
|
-
options.push({
|
|
308
|
+
options.push({
|
|
309
|
+
value: "install_new",
|
|
310
|
+
label: `Install on a new account ${chalk.dim("(creates a new personal workspace)")}`
|
|
311
|
+
});
|
|
309
312
|
}
|
|
310
313
|
const selected = await p.select({
|
|
311
314
|
message: "Select a GitHub installation",
|
|
@@ -663,8 +666,10 @@ var VocoderAPI = class {
|
|
|
663
666
|
}
|
|
664
667
|
}
|
|
665
668
|
// ── Workspaces ────────────────────────────────────────────────────────────────
|
|
666
|
-
async listWorkspaces(userToken) {
|
|
667
|
-
const
|
|
669
|
+
async listWorkspaces(userToken, params) {
|
|
670
|
+
const url = new URL(`${this.apiUrl}/api/cli/workspaces`);
|
|
671
|
+
if (params?.repo) url.searchParams.set("repo", params.repo);
|
|
672
|
+
const response = await fetch(url.toString(), {
|
|
668
673
|
headers: { Authorization: `Bearer ${userToken}` }
|
|
669
674
|
});
|
|
670
675
|
const payload = await readPayload(response);
|
|
@@ -677,6 +682,23 @@ var VocoderAPI = class {
|
|
|
677
682
|
}
|
|
678
683
|
return payload;
|
|
679
684
|
}
|
|
685
|
+
async listProjects(userToken, organizationId) {
|
|
686
|
+
const url = new URL(`${this.apiUrl}/api/cli/projects`);
|
|
687
|
+
url.searchParams.set("organizationId", organizationId);
|
|
688
|
+
const response = await fetch(url.toString(), {
|
|
689
|
+
headers: { Authorization: `Bearer ${userToken}` }
|
|
690
|
+
});
|
|
691
|
+
const payload = await readPayload(response);
|
|
692
|
+
if (!response.ok) {
|
|
693
|
+
throw new VocoderAPIError({
|
|
694
|
+
message: extractErrorMessage(payload, `Failed to list projects (${response.status})`),
|
|
695
|
+
status: response.status,
|
|
696
|
+
payload
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const result = payload;
|
|
700
|
+
return result.projects;
|
|
701
|
+
}
|
|
680
702
|
// ── CLI GitHub endpoints ──────────────────────────────────────────────────────
|
|
681
703
|
async startCliGitHubInstall(userToken, params) {
|
|
682
704
|
const response = await fetch(`${this.apiUrl}/api/cli/github/install/start`, {
|
|
@@ -1930,105 +1952,219 @@ async function init(options = {}) {
|
|
|
1930
1952
|
selectedWorkspaceName = claimResult.organizationName;
|
|
1931
1953
|
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
1932
1954
|
} else {
|
|
1933
|
-
const workspaceData = await api.listWorkspaces(userToken
|
|
1934
|
-
|
|
1935
|
-
|
|
1955
|
+
const workspaceData = await api.listWorkspaces(userToken, {
|
|
1956
|
+
repo: identity?.repoCanonical
|
|
1957
|
+
});
|
|
1958
|
+
const repoCanonical = identity?.repoCanonical ?? null;
|
|
1959
|
+
const covering = repoCanonical ? workspaceData.workspaces.filter((w) => w.coversRepo === true) : [];
|
|
1960
|
+
const connected = workspaceData.workspaces.filter((w) => w.hasGitHubConnection);
|
|
1961
|
+
if (repoCanonical && covering.length === 1) {
|
|
1962
|
+
const ws = covering[0];
|
|
1936
1963
|
selectedWorkspaceId = ws.id;
|
|
1937
1964
|
selectedWorkspaceName = ws.name;
|
|
1938
1965
|
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
1939
|
-
} else {
|
|
1940
|
-
const
|
|
1941
|
-
|
|
1966
|
+
} else if (repoCanonical && covering.length > 1) {
|
|
1967
|
+
const choice = await p5.select({
|
|
1968
|
+
message: "Select workspace for this repo",
|
|
1969
|
+
options: covering.map((w) => ({
|
|
1970
|
+
value: w.id,
|
|
1971
|
+
label: `${w.name} ${chalk6.dim(`(${w.projectCount} project${w.projectCount !== 1 ? "s" : ""})`)}`
|
|
1972
|
+
}))
|
|
1973
|
+
});
|
|
1974
|
+
if (p5.isCancel(choice)) {
|
|
1975
|
+
p5.cancel("Setup cancelled.");
|
|
1976
|
+
return 1;
|
|
1977
|
+
}
|
|
1978
|
+
const ws = covering.find((w) => w.id === choice);
|
|
1979
|
+
selectedWorkspaceId = ws.id;
|
|
1980
|
+
selectedWorkspaceName = ws.name;
|
|
1981
|
+
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
1982
|
+
} else if (repoCanonical && covering.length === 0 && connected.length > 0) {
|
|
1983
|
+
const shortRepo = repoCanonical.split(":")[1] ?? repoCanonical;
|
|
1984
|
+
p5.log.warn(
|
|
1985
|
+
`${chalk6.bold(shortRepo)} isn't accessible from your Vocoder installation.
|
|
1986
|
+
Grant access to this repository or install on the account that owns it.`
|
|
1987
|
+
);
|
|
1988
|
+
const fixOptions = [];
|
|
1989
|
+
for (const ws of connected) {
|
|
1990
|
+
if (ws.installationConfigureUrl) {
|
|
1991
|
+
fixOptions.push({
|
|
1992
|
+
value: `grant:${ws.id}`,
|
|
1993
|
+
label: `Configure ${chalk6.bold(ws.connectionLabel ?? ws.name)}'s GitHub App installation`
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
fixOptions.push({
|
|
1998
|
+
value: "install_new",
|
|
1999
|
+
label: `Install on a different GitHub account ${chalk6.dim("(creates a new personal workspace)")}`
|
|
2000
|
+
});
|
|
2001
|
+
fixOptions.push({ value: "cancel", label: "Cancel" });
|
|
2002
|
+
const fix = await p5.select({
|
|
2003
|
+
message: "How would you like to fix this?",
|
|
2004
|
+
options: fixOptions
|
|
2005
|
+
});
|
|
2006
|
+
if (p5.isCancel(fix) || fix === "cancel") {
|
|
1942
2007
|
p5.cancel("Setup cancelled.");
|
|
1943
2008
|
return 1;
|
|
1944
2009
|
}
|
|
1945
|
-
if (
|
|
1946
|
-
|
|
1947
|
-
|
|
2010
|
+
if (fix.startsWith("grant:")) {
|
|
2011
|
+
const ws = connected.find((w) => `grant:${w.id}` === fix);
|
|
2012
|
+
await tryOpenBrowser2(ws.installationConfigureUrl);
|
|
2013
|
+
p5.cancel(
|
|
2014
|
+
`Grant access to ${chalk6.bold(shortRepo)} in your browser,
|
|
2015
|
+
then re-run ${chalk6.bold("vocoder init")}.`
|
|
2016
|
+
);
|
|
2017
|
+
return 1;
|
|
2018
|
+
}
|
|
2019
|
+
const connectResult = await runGitHubInstallFlow({ api, userToken, yes: options.yes });
|
|
2020
|
+
if (!connectResult) {
|
|
2021
|
+
p5.log.error("GitHub App installation did not complete. Run `vocoder init` again.");
|
|
2022
|
+
return 1;
|
|
2023
|
+
}
|
|
2024
|
+
selectedWorkspaceId = connectResult.organizationId;
|
|
2025
|
+
selectedWorkspaceName = connectResult.organizationName;
|
|
2026
|
+
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
2027
|
+
} else {
|
|
2028
|
+
if (workspaceData.workspaces.length === 1 && !workspaceData.canCreateWorkspace) {
|
|
2029
|
+
const ws = workspaceData.workspaces[0];
|
|
2030
|
+
selectedWorkspaceId = ws.id;
|
|
2031
|
+
selectedWorkspaceName = ws.name;
|
|
1948
2032
|
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
1949
2033
|
} else {
|
|
1950
|
-
const
|
|
1951
|
-
|
|
1952
|
-
options: [
|
|
1953
|
-
{ value: "install", label: "Install the Vocoder GitHub App" },
|
|
1954
|
-
{ value: "link", label: "Link an existing installation" }
|
|
1955
|
-
]
|
|
1956
|
-
});
|
|
1957
|
-
if (p5.isCancel(connectChoice)) {
|
|
2034
|
+
const workspaceResult = await selectWorkspace(workspaceData);
|
|
2035
|
+
if (workspaceResult.action === "cancelled") {
|
|
1958
2036
|
p5.cancel("Setup cancelled.");
|
|
1959
2037
|
return 1;
|
|
1960
2038
|
}
|
|
1961
|
-
if (
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
userToken,
|
|
1965
|
-
yes: options.yes
|
|
1966
|
-
});
|
|
1967
|
-
if (!connectResult) {
|
|
1968
|
-
p5.log.error("GitHub App installation did not complete. Run `vocoder init` again.");
|
|
1969
|
-
return 1;
|
|
1970
|
-
}
|
|
1971
|
-
selectedWorkspaceId = connectResult.organizationId;
|
|
1972
|
-
selectedWorkspaceName = connectResult.organizationName;
|
|
2039
|
+
if (workspaceResult.action === "use") {
|
|
2040
|
+
selectedWorkspaceId = workspaceResult.workspace.id;
|
|
2041
|
+
selectedWorkspaceName = workspaceResult.workspace.name;
|
|
1973
2042
|
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
1974
2043
|
} else {
|
|
1975
|
-
const
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2044
|
+
const connectChoice = await p5.select({
|
|
2045
|
+
message: "Connect your new workspace to GitHub",
|
|
2046
|
+
options: [
|
|
2047
|
+
{ value: "install", label: "Install the Vocoder GitHub App" },
|
|
2048
|
+
{ value: "link", label: "Link an existing installation" }
|
|
2049
|
+
]
|
|
1979
2050
|
});
|
|
1980
|
-
if (
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
const connectResult = await runGitHubInstallFlow({
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
}
|
|
1990
|
-
if (!connectResult) return 1;
|
|
2051
|
+
if (p5.isCancel(connectChoice)) {
|
|
2052
|
+
p5.cancel("Setup cancelled.");
|
|
2053
|
+
return 1;
|
|
2054
|
+
}
|
|
2055
|
+
if (connectChoice === "install") {
|
|
2056
|
+
const connectResult = await runGitHubInstallFlow({ api, userToken, yes: options.yes });
|
|
2057
|
+
if (!connectResult) {
|
|
2058
|
+
p5.log.error("GitHub App installation did not complete. Run `vocoder init` again.");
|
|
2059
|
+
return 1;
|
|
2060
|
+
}
|
|
1991
2061
|
selectedWorkspaceId = connectResult.organizationId;
|
|
1992
2062
|
selectedWorkspaceName = connectResult.organizationName;
|
|
2063
|
+
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
1993
2064
|
} else {
|
|
1994
|
-
const
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
})),
|
|
2002
|
-
true
|
|
2003
|
-
);
|
|
2004
|
-
if (selectedInstallationId === null) {
|
|
2005
|
-
p5.cancel("Setup cancelled.");
|
|
2006
|
-
return 1;
|
|
2007
|
-
}
|
|
2008
|
-
if (selectedInstallationId === "install_new") {
|
|
2009
|
-
const connectResult = await runGitHubInstallFlow({
|
|
2010
|
-
api,
|
|
2011
|
-
userToken,
|
|
2012
|
-
yes: options.yes
|
|
2013
|
-
});
|
|
2065
|
+
const installations = await runGitHubDiscoveryFlow({ api, userToken, yes: options.yes });
|
|
2066
|
+
if (!installations) return 1;
|
|
2067
|
+
if (installations.length === 0) {
|
|
2068
|
+
p5.log.warn("No GitHub installations found. Install the Vocoder GitHub App first.");
|
|
2069
|
+
const installNow = await p5.confirm({ message: "Open GitHub to install the App?" });
|
|
2070
|
+
if (p5.isCancel(installNow) || !installNow) return 1;
|
|
2071
|
+
const connectResult = await runGitHubInstallFlow({ api, userToken, yes: options.yes });
|
|
2014
2072
|
if (!connectResult) return 1;
|
|
2015
2073
|
selectedWorkspaceId = connectResult.organizationId;
|
|
2016
2074
|
selectedWorkspaceName = connectResult.organizationName;
|
|
2017
2075
|
} else {
|
|
2018
|
-
const
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2076
|
+
const selectedInstallationId = await selectGitHubInstallation(
|
|
2077
|
+
installations.map((inst) => ({
|
|
2078
|
+
installationId: inst.installationId,
|
|
2079
|
+
accountLogin: inst.accountLogin,
|
|
2080
|
+
accountType: inst.accountType,
|
|
2081
|
+
isSuspended: inst.isSuspended,
|
|
2082
|
+
conflictLabel: inst.conflictLabel
|
|
2083
|
+
})),
|
|
2084
|
+
true
|
|
2085
|
+
);
|
|
2086
|
+
if (selectedInstallationId === null) {
|
|
2087
|
+
p5.cancel("Setup cancelled.");
|
|
2088
|
+
return 1;
|
|
2089
|
+
}
|
|
2090
|
+
if (selectedInstallationId === "install_new") {
|
|
2091
|
+
const connectResult = await runGitHubInstallFlow({ api, userToken, yes: options.yes });
|
|
2092
|
+
if (!connectResult) return 1;
|
|
2093
|
+
selectedWorkspaceId = connectResult.organizationId;
|
|
2094
|
+
selectedWorkspaceName = connectResult.organizationName;
|
|
2095
|
+
} else {
|
|
2096
|
+
const claimResult = await api.claimCliGitHubInstallation(userToken, {
|
|
2097
|
+
installationId: String(selectedInstallationId),
|
|
2098
|
+
organizationId: null
|
|
2099
|
+
});
|
|
2100
|
+
selectedWorkspaceId = claimResult.organizationId;
|
|
2101
|
+
selectedWorkspaceName = claimResult.organizationName;
|
|
2102
|
+
}
|
|
2024
2103
|
}
|
|
2104
|
+
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
2025
2105
|
}
|
|
2026
|
-
p5.log.success(`Workspace: ${chalk6.bold(selectedWorkspaceName)}`);
|
|
2027
2106
|
}
|
|
2028
2107
|
}
|
|
2029
2108
|
}
|
|
2030
2109
|
}
|
|
2031
2110
|
}
|
|
2111
|
+
try {
|
|
2112
|
+
const wsCheck = await api.listWorkspaces(userToken);
|
|
2113
|
+
const ws = wsCheck.workspaces.find((w) => w.id === selectedWorkspaceId);
|
|
2114
|
+
if (ws && ws.maxProjects !== -1 && ws.projectCount >= ws.maxProjects) {
|
|
2115
|
+
p5.log.warn(
|
|
2116
|
+
`Project limit reached \u2014 ${ws.projectCount}/${ws.maxProjects} on your ${chalk6.bold(ws.planId)} plan.`
|
|
2117
|
+
);
|
|
2118
|
+
const limitAction = await p5.select({
|
|
2119
|
+
message: "What would you like to do?",
|
|
2120
|
+
options: [
|
|
2121
|
+
{ value: "upgrade", label: "Upgrade plan" },
|
|
2122
|
+
{ value: "existing", label: "Use an existing project in this workspace" },
|
|
2123
|
+
{ value: "cancel", label: "Cancel" }
|
|
2124
|
+
]
|
|
2125
|
+
});
|
|
2126
|
+
if (p5.isCancel(limitAction) || limitAction === "cancel") {
|
|
2127
|
+
p5.cancel("Setup cancelled.");
|
|
2128
|
+
return 1;
|
|
2129
|
+
}
|
|
2130
|
+
if (limitAction === "upgrade") {
|
|
2131
|
+
await tryOpenBrowser2(`${apiUrl}/dashboard/billing`);
|
|
2132
|
+
p5.cancel("Upgrade your plan in the browser, then re-run `vocoder init`.");
|
|
2133
|
+
return 1;
|
|
2134
|
+
}
|
|
2135
|
+
const existingProjects = await api.listProjects(userToken, selectedWorkspaceId);
|
|
2136
|
+
if (existingProjects.length === 0) {
|
|
2137
|
+
p5.log.error("No projects found in this workspace.");
|
|
2138
|
+
return 1;
|
|
2139
|
+
}
|
|
2140
|
+
const chosenId = await p5.select({
|
|
2141
|
+
message: "Select a project",
|
|
2142
|
+
options: existingProjects.map((proj) => ({
|
|
2143
|
+
value: proj.id,
|
|
2144
|
+
label: proj.name,
|
|
2145
|
+
hint: `${proj.sourceLocale} \u2192 ${proj.targetLocales.join(", ")}`
|
|
2146
|
+
}))
|
|
2147
|
+
});
|
|
2148
|
+
if (p5.isCancel(chosenId)) {
|
|
2149
|
+
p5.cancel("Setup cancelled.");
|
|
2150
|
+
return 1;
|
|
2151
|
+
}
|
|
2152
|
+
const chosen = existingProjects.find((proj) => proj.id === chosenId);
|
|
2153
|
+
runScaffold({
|
|
2154
|
+
projectName: chosen.name,
|
|
2155
|
+
organizationName: selectedWorkspaceName,
|
|
2156
|
+
sourceLocale: chosen.sourceLocale,
|
|
2157
|
+
translationTriggers: chosen.translationTriggers
|
|
2158
|
+
});
|
|
2159
|
+
p5.log.info(
|
|
2160
|
+
`Get your project API key at:
|
|
2161
|
+
${apiUrl}/dashboard/projects/${chosen.id}/settings`
|
|
2162
|
+
);
|
|
2163
|
+
p5.outro("You're all set.");
|
|
2164
|
+
return 0;
|
|
2165
|
+
}
|
|
2166
|
+
} catch {
|
|
2167
|
+
}
|
|
2032
2168
|
const projectResult = await runProjectCreate({
|
|
2033
2169
|
api,
|
|
2034
2170
|
userToken,
|