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