Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/command/publish/account.ts
3562 views
1
/*
2
* account.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { info } from "../../deno_ral/log.ts";
8
import { Checkbox, prompt, Select, SelectOption } from "cliffy/prompt/mod.ts";
9
10
import {
11
accountTokenText,
12
findProvider,
13
publishProviders,
14
} from "../../publish/provider.ts";
15
import {
16
AccountToken,
17
AccountTokenType,
18
PublishProvider,
19
} from "../../publish/provider-types.ts";
20
import { PublishOptions, PublishRecord } from "../../publish/types.ts";
21
22
export type AccountPrompt = "always" | "never" | "multiple";
23
24
export async function resolveAccount(
25
provider: PublishProvider,
26
prompt: AccountPrompt,
27
options: PublishOptions,
28
hintAccount?: AccountToken,
29
target?: PublishRecord,
30
) {
31
// if the options provide a token then reflect that
32
if (options.token) {
33
// validate server
34
if (provider.requiresServer && !options.server) {
35
throw new Error(
36
`You must provide the --server argument along with --token for ${provider.description}`,
37
);
38
}
39
40
return {
41
type: AccountTokenType.Authorized,
42
name: provider.name,
43
server: options.server ? options.server : null,
44
token: options.token,
45
};
46
}
47
48
// see what tyep of token we are going to use
49
let token: AccountToken | undefined;
50
51
// build list of account options
52
let accounts = (await provider.accountTokens()).filter((account) => {
53
if (account.server && target?.url) {
54
return target.url.startsWith(account.server);
55
} else {
56
return true;
57
}
58
});
59
60
// if we aren't prompting then we need to have one at the ready
61
if (prompt === "never") {
62
return accounts.length === 1 ? accounts[0] : undefined;
63
} else if (prompt === "multiple" && accounts.length === 1) {
64
return accounts[0];
65
} else if (
66
accounts.length === 1 && accounts[0].type === AccountTokenType.Anonymous
67
) {
68
return accounts[0];
69
} else {
70
// prompt for account to publish with
71
if (accounts.length > 0) {
72
// order the hint account first
73
if (hintAccount) {
74
const hintIdx = accounts.findIndex((account) =>
75
account.token === hintAccount.token
76
);
77
if (hintIdx !== -1) {
78
const newAccounts = [accounts[hintIdx]];
79
if (hintIdx > 0) {
80
newAccounts.push(...accounts.slice(0, hintIdx));
81
}
82
if (hintIdx < (accounts.length - 1)) {
83
newAccounts.push(...accounts.slice(hintIdx + 1));
84
}
85
86
accounts = newAccounts;
87
}
88
}
89
90
token = await accountPrompt(provider, accounts);
91
}
92
93
// if we don't have a token yet we need to authorize
94
if (!token) {
95
token = await provider.authorizeToken(options, target);
96
}
97
98
return token;
99
}
100
}
101
102
export async function accountPrompt(
103
_provider: PublishProvider,
104
accounts: AccountToken[],
105
): Promise<AccountToken | undefined> {
106
const options: SelectOption<string>[] = accounts
107
.filter((account) => account.type !== AccountTokenType.Anonymous).map((
108
account,
109
) => ({
110
name: accountTokenText(account),
111
value: account.token,
112
}));
113
const kAuthorize = "authorize";
114
const accountDescriptor = _provider.accountDescriptor || "account";
115
options.push({
116
name: `Use another ${accountDescriptor}...`,
117
value: kAuthorize,
118
});
119
120
const result = await prompt([{
121
indent: "",
122
name: "token",
123
message: `Publish with ${accountDescriptor}:`,
124
options,
125
type: Select,
126
}]);
127
if (result.token !== kAuthorize) {
128
return accounts.find((account) => account.token === result.token);
129
}
130
}
131
132
interface ProviderAccountToken extends AccountToken {
133
provider: string;
134
}
135
136
export async function manageAccounts() {
137
// build a list of all authorized accounts
138
const accounts: ProviderAccountToken[] = [];
139
for (const provider of publishProviders()) {
140
for (const account of await provider.accountTokens()) {
141
if (account.type === AccountTokenType.Authorized) {
142
accounts.push({ provider: provider.name, ...account });
143
}
144
}
145
}
146
147
// if we don't have any then exit
148
if (accounts.length === 0) {
149
info("No publishing accounts currently authorized.");
150
throw new Error();
151
}
152
153
// create a checked list from which accounts can be removed
154
const keepAccounts = await prompt([{
155
name: "accounts",
156
message: "Manage Publishing Accounts",
157
type: Checkbox,
158
indent: "",
159
options: accounts.map((account) => ({
160
name: `${findProvider(account.provider)?.description}: ${account.name}${
161
account.server ? " (" + account.server + ")" : ""
162
}`,
163
value: JSON.stringify(account),
164
checked: true,
165
})),
166
hint:
167
`Use the arrow keys and spacebar to specify accounts you would like to remove.\n` +
168
` Press Enter to confirm the list of accounts you wish to remain available.`,
169
}]);
170
171
// figure out which accounts we should be removing
172
const removeAccounts: ProviderAccountToken[] = [];
173
for (const account of accounts) {
174
if (
175
!keepAccounts.accounts?.find((keepAccountJson: string) => {
176
const keepAccount = JSON.parse(
177
keepAccountJson,
178
) as ProviderAccountToken;
179
return account.provider == keepAccount.provider &&
180
account.name == keepAccount.name &&
181
account.server == keepAccount.server;
182
})
183
) {
184
info(
185
`Removing ${
186
findProvider(account.provider)
187
?.description
188
} account ${account.name}`,
189
);
190
removeAccounts.push(account);
191
}
192
}
193
194
// remove them
195
for (const account of removeAccounts) {
196
const provider = findProvider(account.provider);
197
provider?.removeToken(account);
198
}
199
}
200
201