@magland/mochi 0.3.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/LICENSE +201 -0
- package/README.md +108 -0
- package/dist/ansi.js +174 -0
- package/dist/api/admin.js +416 -0
- package/dist/api/auth.js +166 -0
- package/dist/api/backup.js +598 -0
- package/dist/api/ci.js +336 -0
- package/dist/api/contents.js +339 -0
- package/dist/api/issues.js +165 -0
- package/dist/api/pulls.js +244 -0
- package/dist/api/releases.js +83 -0
- package/dist/api/repos.js +156 -0
- package/dist/api/write.js +518 -0
- package/dist/api.js +326 -0
- package/dist/assets.js +29 -0
- package/dist/atom.js +32 -0
- package/dist/atomic.js +171 -0
- package/dist/avatar.js +81 -0
- package/dist/browse.js +630 -0
- package/dist/build-info.json +4 -0
- package/dist/ci/actionref.js +86 -0
- package/dist/ci/api.js +829 -0
- package/dist/ci/artifacts.js +201 -0
- package/dist/ci/dispatch.js +30 -0
- package/dist/ci/engine.js +1321 -0
- package/dist/ci/expr.js +526 -0
- package/dist/ci/manual.js +199 -0
- package/dist/ci/present.js +82 -0
- package/dist/ci/protocol.js +6 -0
- package/dist/ci/runners.js +256 -0
- package/dist/ci/runs.js +208 -0
- package/dist/ci/trigger.js +28 -0
- package/dist/ci/views.js +441 -0
- package/dist/ci/wake.js +194 -0
- package/dist/ci/web.js +617 -0
- package/dist/ci/workflow.js +436 -0
- package/dist/cli/admin-cmd.js +324 -0
- package/dist/cli/api-cmd.js +128 -0
- package/dist/cli/backup-cmd.js +1500 -0
- package/dist/cli/exit.js +69 -0
- package/dist/cli/input.js +64 -0
- package/dist/cli/issue-cmd.js +243 -0
- package/dist/cli/output.js +93 -0
- package/dist/cli/parse.js +317 -0
- package/dist/cli/pr-cmd.js +289 -0
- package/dist/cli/release-cmd.js +171 -0
- package/dist/cli/repo-cmd.js +763 -0
- package/dist/cli/repo.js +101 -0
- package/dist/cli/run-cmd.js +438 -0
- package/dist/cli/target.js +54 -0
- package/dist/cli-api.js +84 -0
- package/dist/compare.js +111 -0
- package/dist/config.js +212 -0
- package/dist/credentials.js +235 -0
- package/dist/deploy-cli.js +859 -0
- package/dist/deploy-runner-cli.js +592 -0
- package/dist/diff.js +171 -0
- package/dist/discussion.js +253 -0
- package/dist/egress.js +559 -0
- package/dist/filecache.js +68 -0
- package/dist/find.js +162 -0
- package/dist/forms.js +737 -0
- package/dist/git.js +547 -0
- package/dist/githttp.js +428 -0
- package/dist/html.js +87 -0
- package/dist/icons.js +101 -0
- package/dist/import-cli.js +316 -0
- package/dist/index.js +752 -0
- package/dist/issues.js +308 -0
- package/dist/issueweb.js +447 -0
- package/dist/job-cli.js +197 -0
- package/dist/jobtoken.js +96 -0
- package/dist/languages.js +383 -0
- package/dist/layout.js +100 -0
- package/dist/lfs.js +438 -0
- package/dist/lfsstore.js +425 -0
- package/dist/limit.js +259 -0
- package/dist/logo.js +61 -0
- package/dist/markdown.js +382 -0
- package/dist/migrate.js +334 -0
- package/dist/multipart.js +90 -0
- package/dist/ops.js +869 -0
- package/dist/pagescript.js +465 -0
- package/dist/perms.js +370 -0
- package/dist/pointer.js +55 -0
- package/dist/profile.js +106 -0
- package/dist/pulls.js +320 -0
- package/dist/pullweb.js +461 -0
- package/dist/redirects.js +455 -0
- package/dist/releases.js +435 -0
- package/dist/render.js +233 -0
- package/dist/runner/actions.js +448 -0
- package/dist/runner/client.js +428 -0
- package/dist/runner/context.js +247 -0
- package/dist/runner/docker.js +197 -0
- package/dist/runner/externals.js +175 -0
- package/dist/runner/job.js +290 -0
- package/dist/runner/manual-run.js +272 -0
- package/dist/runner/overrides.js +554 -0
- package/dist/runner/steps.js +571 -0
- package/dist/runner/wake.js +84 -0
- package/dist/runner-cli.js +405 -0
- package/dist/scan.js +231 -0
- package/dist/server.js +424 -0
- package/dist/session.js +267 -0
- package/dist/site.js +259 -0
- package/dist/siteshost.js +94 -0
- package/dist/source.js +90 -0
- package/dist/style.js +1295 -0
- package/dist/themes.js +369 -0
- package/dist/vault.js +442 -0
- package/dist/version.js +88 -0
- package/dist/views.js +1007 -0
- package/dist/web.js +182 -0
- package/dist/webops.js +1402 -0
- package/package.json +71 -0
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.repoCommands = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const cli_api_1 = require("../cli-api");
|
|
6
|
+
const exit_1 = require("./exit");
|
|
7
|
+
const input_1 = require("./input");
|
|
8
|
+
const output_1 = require("./output");
|
|
9
|
+
const repo_1 = require("./repo");
|
|
10
|
+
const target_1 = require("./target");
|
|
11
|
+
// Reading a repository from the command line: what is in it, what one file says,
|
|
12
|
+
// what changed, and where something appears. Between them these are what let a
|
|
13
|
+
// caller work on a repository without cloning it.
|
|
14
|
+
const COMMON = [repo_1.REPO_OPTION, ...target_1.TARGET_OPTIONS];
|
|
15
|
+
const REF_OPTION = {
|
|
16
|
+
name: 'ref',
|
|
17
|
+
type: 'string',
|
|
18
|
+
value: '<r>',
|
|
19
|
+
summary: 'Branch, tag, or commit; the default branch otherwise',
|
|
20
|
+
};
|
|
21
|
+
// Every destructive command takes --yes and refuses without it, rather than
|
|
22
|
+
// prompting. A prompt is no use to a caller that is not a person, and a command
|
|
23
|
+
// that prompts is a command that hangs in a container.
|
|
24
|
+
const YES_OPTION = {
|
|
25
|
+
name: 'yes',
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
summary: 'Required: confirm that this cannot be undone',
|
|
28
|
+
};
|
|
29
|
+
function requireYes(inv, what) {
|
|
30
|
+
if (!inv.bool('yes'))
|
|
31
|
+
throw new exit_1.CliError(`${what} cannot be undone. Pass --yes to go ahead.`, exit_1.EXIT_USAGE);
|
|
32
|
+
}
|
|
33
|
+
function refQuery(ref, extra = {}) {
|
|
34
|
+
const q = new URLSearchParams(extra);
|
|
35
|
+
if (ref !== null)
|
|
36
|
+
q.set('ref', ref);
|
|
37
|
+
const s = q.toString();
|
|
38
|
+
return s ? `?${s}` : '';
|
|
39
|
+
}
|
|
40
|
+
exports.repoCommands = [
|
|
41
|
+
{
|
|
42
|
+
path: ['repo', 'list'],
|
|
43
|
+
summary: 'List the repositories in the vault',
|
|
44
|
+
args: [{ name: 'collection' }],
|
|
45
|
+
options: [output_1.JSON_OPTION, ...target_1.TARGET_OPTIONS],
|
|
46
|
+
async run(inv) {
|
|
47
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
48
|
+
const data = await (0, cli_api_1.api)(target, 'GET', '/api/repos');
|
|
49
|
+
const only = inv.args[0] ?? null;
|
|
50
|
+
const repos = (data.repos ?? []).filter((r) => only === null || r.collection === only);
|
|
51
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
52
|
+
if (json.enabled) {
|
|
53
|
+
(0, output_1.printJson)({ repos: (0, output_1.pickFields)(repos, json.fields) });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (repos.length === 0) {
|
|
57
|
+
console.log(only ? `No repositories in ${only}` : `No repositories on ${target.host}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
(0, output_1.printTable)(repos.map((r) => [
|
|
61
|
+
`${r.collection}/${r.name}${r.private ? ' (private)' : ''}`,
|
|
62
|
+
String(r.description ?? ''),
|
|
63
|
+
]));
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
path: ['repo', 'view'],
|
|
68
|
+
summary: 'Show one repository: its default branch, counts, and whether it has a site',
|
|
69
|
+
args: [{ name: 'repo' }],
|
|
70
|
+
options: [output_1.JSON_OPTION, ...COMMON],
|
|
71
|
+
async run(inv) {
|
|
72
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
73
|
+
const ref = await (0, repo_1.resolveRepo)(inv, target, inv.args[0] ?? null);
|
|
74
|
+
const data = await (0, cli_api_1.api)(target, 'GET', (0, repo_1.repoPath)(ref));
|
|
75
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
76
|
+
if (json.enabled) {
|
|
77
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log(`${data.collection}/${data.name}`);
|
|
81
|
+
if (data.description)
|
|
82
|
+
console.log(String(data.description));
|
|
83
|
+
if (data.forkedFrom) {
|
|
84
|
+
const from = data.forkedFrom;
|
|
85
|
+
console.log(`forked from ${from.collection}/${from.repo}`);
|
|
86
|
+
}
|
|
87
|
+
(0, output_1.printTable)([
|
|
88
|
+
['visibility', data.private ? 'private' : 'public'],
|
|
89
|
+
['default branch', String(data.defaultBranch ?? '(none)')],
|
|
90
|
+
['last updated', (0, output_1.shortDate)(data.updated)],
|
|
91
|
+
['branches', String(data.branches)],
|
|
92
|
+
['tags', String(data.tags)],
|
|
93
|
+
['releases', String(data.releases)],
|
|
94
|
+
['open issues', String(data.openIssues)],
|
|
95
|
+
['open pull requests', String(data.openPulls)],
|
|
96
|
+
['site', data.hasSite ? 'yes' : 'no'],
|
|
97
|
+
]);
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
path: ['repo', 'create'],
|
|
102
|
+
summary: 'Create an empty repository, or one with a README in it',
|
|
103
|
+
args: [{ name: 'collection/name', required: true }],
|
|
104
|
+
options: [
|
|
105
|
+
{ name: 'description', type: 'string', value: '<d>', summary: 'One-line description' },
|
|
106
|
+
{ name: 'readme', type: 'boolean', summary: 'Add a first commit with a README.md' },
|
|
107
|
+
{ name: 'private', type: 'boolean', summary: 'Visible only to collaborators, owners, and site admins' },
|
|
108
|
+
output_1.JSON_OPTION,
|
|
109
|
+
...target_1.TARGET_OPTIONS,
|
|
110
|
+
],
|
|
111
|
+
async run(inv) {
|
|
112
|
+
const parts = inv.args[0].split('/');
|
|
113
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
114
|
+
throw new exit_1.CliError('name the repository as <collection>/<name>', exit_1.EXIT_USAGE);
|
|
115
|
+
}
|
|
116
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
117
|
+
const data = await (0, cli_api_1.api)(target, 'POST', '/api/repos', {
|
|
118
|
+
collection: parts[0],
|
|
119
|
+
name: parts[1],
|
|
120
|
+
description: inv.str('description') ?? undefined,
|
|
121
|
+
initReadme: inv.bool('readme'),
|
|
122
|
+
private: inv.bool('private') || undefined,
|
|
123
|
+
});
|
|
124
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
125
|
+
if (json.enabled) {
|
|
126
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
console.log(`${target.host}/${data.collection}/${data.name}`);
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
path: ['repo', 'fork'],
|
|
134
|
+
summary: 'Copy a repository into another collection',
|
|
135
|
+
args: [
|
|
136
|
+
{ name: 'repo', required: true },
|
|
137
|
+
{ name: 'collection[/name]', required: true },
|
|
138
|
+
],
|
|
139
|
+
options: [output_1.JSON_OPTION, ...target_1.TARGET_OPTIONS],
|
|
140
|
+
async run(inv) {
|
|
141
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
142
|
+
const source = await (0, repo_1.resolveRepo)(inv, target, inv.args[0]);
|
|
143
|
+
const parts = inv.args[1].split('/');
|
|
144
|
+
const data = await (0, cli_api_1.api)(target, 'POST', `${(0, repo_1.repoPath)(source)}/fork`, {
|
|
145
|
+
collection: parts[0],
|
|
146
|
+
name: parts[1] ?? undefined,
|
|
147
|
+
});
|
|
148
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
149
|
+
if (json.enabled) {
|
|
150
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
console.log(`${target.host}/${data.collection}/${data.name}`);
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
path: ['repo', 'edit'],
|
|
158
|
+
summary: 'Change a repository description, default branch, or visibility',
|
|
159
|
+
args: [{ name: 'repo' }],
|
|
160
|
+
options: [
|
|
161
|
+
{ name: 'description', type: 'string', value: '<d>', summary: 'New description' },
|
|
162
|
+
{ name: 'default-branch', type: 'string', value: '<b>', summary: 'New default branch' },
|
|
163
|
+
{ name: 'private', type: 'boolean', summary: 'Make the repository private (takes the admin role)' },
|
|
164
|
+
{ name: 'public', type: 'boolean', summary: 'Make the repository public' },
|
|
165
|
+
output_1.JSON_OPTION,
|
|
166
|
+
...COMMON,
|
|
167
|
+
],
|
|
168
|
+
async run(inv) {
|
|
169
|
+
const description = inv.str('description');
|
|
170
|
+
const defaultBranch = inv.str('default-branch');
|
|
171
|
+
if (inv.bool('private') && inv.bool('public')) {
|
|
172
|
+
throw new exit_1.CliError('Pass --private or --public, not both.', exit_1.EXIT_USAGE);
|
|
173
|
+
}
|
|
174
|
+
const priv = inv.bool('private') ? true : inv.bool('public') ? false : undefined;
|
|
175
|
+
if (description === null && defaultBranch === null && priv === undefined) {
|
|
176
|
+
throw new exit_1.CliError('Nothing to change. Pass --description, --default-branch, --private, or --public.', exit_1.EXIT_USAGE);
|
|
177
|
+
}
|
|
178
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
179
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0] ?? null);
|
|
180
|
+
const data = await (0, cli_api_1.api)(target, 'PATCH', (0, repo_1.repoPath)(repo), {
|
|
181
|
+
description: description ?? undefined,
|
|
182
|
+
defaultBranch: defaultBranch ?? undefined,
|
|
183
|
+
private: priv,
|
|
184
|
+
});
|
|
185
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
186
|
+
if (json.enabled) {
|
|
187
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
console.log(`Updated ${data.collection}/${data.name}`);
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
path: ['repo', 'rename'],
|
|
195
|
+
summary: 'Rename a repository, or move it to another collection',
|
|
196
|
+
description: `A repository is a bare repository plus the directories it keeps beside it, for
|
|
197
|
+
its issues, pull requests, releases, site, and run history. All of them move
|
|
198
|
+
together.`,
|
|
199
|
+
args: [
|
|
200
|
+
{ name: 'repo', required: true },
|
|
201
|
+
{ name: 'new-name', required: true },
|
|
202
|
+
],
|
|
203
|
+
options: [
|
|
204
|
+
{ name: 'collection', type: 'string', value: '<c>', summary: 'Move it to this collection as well' },
|
|
205
|
+
output_1.JSON_OPTION,
|
|
206
|
+
...target_1.TARGET_OPTIONS,
|
|
207
|
+
],
|
|
208
|
+
async run(inv) {
|
|
209
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
210
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0]);
|
|
211
|
+
const data = await (0, cli_api_1.api)(target, 'POST', `${(0, repo_1.repoPath)(repo)}/rename`, {
|
|
212
|
+
name: inv.args[1],
|
|
213
|
+
collection: inv.str('collection') ?? undefined,
|
|
214
|
+
});
|
|
215
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
216
|
+
if (json.enabled) {
|
|
217
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// As for a collection: asking for the name it already has is answered as
|
|
221
|
+
// changed:false, and printing "Now undefined/undefined" for it would be
|
|
222
|
+
// a report of something that did not happen.
|
|
223
|
+
if (data.changed === false) {
|
|
224
|
+
console.log(String(data.message ?? 'Nothing changed.'));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
console.log(`Now ${data.collection}/${data.name}`);
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
path: ['repo', 'delete'],
|
|
232
|
+
summary: 'Delete a repository and everything beside it',
|
|
233
|
+
args: [{ name: 'repo', required: true }],
|
|
234
|
+
options: [YES_OPTION, output_1.JSON_OPTION, ...target_1.TARGET_OPTIONS],
|
|
235
|
+
async run(inv) {
|
|
236
|
+
requireYes(inv, 'Deleting a repository');
|
|
237
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
238
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0]);
|
|
239
|
+
const full = `${repo.collection}/${repo.repo}`;
|
|
240
|
+
const data = await (0, cli_api_1.api)(target, 'DELETE', `${(0, repo_1.repoPath)(repo)}?confirm=${encodeURIComponent(full)}`);
|
|
241
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
242
|
+
if (json.enabled) {
|
|
243
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
console.log(`Deleted ${full}`);
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
path: ['repo', 'clone'],
|
|
251
|
+
summary: 'git clone, with the vault URL filled in',
|
|
252
|
+
description: `A thin wrapper over git clone. The URL is otherwise assembled by hand from the
|
|
253
|
+
login, and the credential arrangement is already in place, so this saves the
|
|
254
|
+
assembling and nothing else.`,
|
|
255
|
+
args: [{ name: 'repo', required: true }, { name: 'directory' }],
|
|
256
|
+
options: [...target_1.TARGET_OPTIONS],
|
|
257
|
+
async run(inv) {
|
|
258
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
259
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0]);
|
|
260
|
+
const url = `${target.host}/${repo.collection}/${repo.repo}`;
|
|
261
|
+
const args = ['clone', url, ...(inv.args[1] ? [inv.args[1]] : [])];
|
|
262
|
+
const code = await new Promise((resolve) => {
|
|
263
|
+
const child = (0, child_process_1.spawn)('git', args, { stdio: 'inherit' });
|
|
264
|
+
child.on('error', () => resolve(exit_1.EXIT_FAIL));
|
|
265
|
+
child.on('close', (c) => resolve(c ?? exit_1.EXIT_FAIL));
|
|
266
|
+
});
|
|
267
|
+
if (code !== 0)
|
|
268
|
+
process.exit(code);
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
path: ['branch', 'list'],
|
|
273
|
+
summary: 'List branches',
|
|
274
|
+
options: [output_1.JSON_OPTION, ...COMMON],
|
|
275
|
+
async run(inv) {
|
|
276
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
277
|
+
const ref = await (0, repo_1.resolveRepo)(inv, target);
|
|
278
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(ref)}/branches`);
|
|
279
|
+
const branches = (data.branches ?? []);
|
|
280
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
281
|
+
if (json.enabled) {
|
|
282
|
+
(0, output_1.printJson)({ branches: (0, output_1.pickFields)(branches, json.fields), defaultBranch: data.defaultBranch });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
(0, output_1.printTable)(branches.map((b) => [
|
|
286
|
+
b.name === data.defaultBranch ? `* ${b.name}` : ` ${b.name}`,
|
|
287
|
+
String(b.sha).slice(0, 10),
|
|
288
|
+
(0, output_1.shortDate)(b.date),
|
|
289
|
+
String(b.subject ?? ''),
|
|
290
|
+
]));
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
path: ['branch', 'create'],
|
|
295
|
+
summary: 'Create a branch at a ref',
|
|
296
|
+
args: [{ name: 'name', required: true }, { name: 'from' }],
|
|
297
|
+
options: [output_1.JSON_OPTION, ...COMMON],
|
|
298
|
+
async run(inv) {
|
|
299
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
300
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
301
|
+
const from = inv.args[1] ?? null;
|
|
302
|
+
const data = await (0, cli_api_1.api)(target, 'POST', `${(0, repo_1.repoPath)(repo)}/branches`, {
|
|
303
|
+
name: inv.args[0],
|
|
304
|
+
// No `from` means from the default branch, which is what a caller
|
|
305
|
+
// starting a branch almost always means.
|
|
306
|
+
from: from ?? (await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/branches`)).defaultBranch,
|
|
307
|
+
});
|
|
308
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
309
|
+
if (json.enabled) {
|
|
310
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
console.log(`Created ${data.name} at ${String(data.sha).slice(0, 10)}`);
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
path: ['branch', 'delete'],
|
|
318
|
+
summary: 'Delete a branch',
|
|
319
|
+
args: [{ name: 'name', required: true }],
|
|
320
|
+
options: [YES_OPTION, output_1.JSON_OPTION, ...COMMON],
|
|
321
|
+
async run(inv) {
|
|
322
|
+
requireYes(inv, 'Deleting a branch');
|
|
323
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
324
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
325
|
+
const data = await (0, cli_api_1.api)(target, 'DELETE', `${(0, repo_1.repoPath)(repo)}/branches/${inv.args[0]}`);
|
|
326
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
327
|
+
if (json.enabled) {
|
|
328
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
console.log(`Deleted ${data.deleted}`);
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
path: ['tag', 'list'],
|
|
336
|
+
summary: 'List tags',
|
|
337
|
+
options: [output_1.JSON_OPTION, ...COMMON],
|
|
338
|
+
async run(inv) {
|
|
339
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
340
|
+
const ref = await (0, repo_1.resolveRepo)(inv, target);
|
|
341
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(ref)}/tags`);
|
|
342
|
+
const tags = (data.tags ?? []);
|
|
343
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
344
|
+
if (json.enabled) {
|
|
345
|
+
(0, output_1.printJson)({ tags: (0, output_1.pickFields)(tags, json.fields) });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (tags.length === 0) {
|
|
349
|
+
console.log('No tags');
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
(0, output_1.printTable)(tags.map((t) => [String(t.name), String(t.sha).slice(0, 10), (0, output_1.shortDate)(t.date), String(t.subject ?? '')]));
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
path: ['tag', 'create'],
|
|
357
|
+
summary: 'Create a tag at a ref',
|
|
358
|
+
args: [{ name: 'name', required: true }, { name: 'at' }],
|
|
359
|
+
options: [output_1.JSON_OPTION, ...COMMON],
|
|
360
|
+
async run(inv) {
|
|
361
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
362
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
363
|
+
const at = inv.args[1] ?? null;
|
|
364
|
+
const data = await (0, cli_api_1.api)(target, 'POST', `${(0, repo_1.repoPath)(repo)}/tags`, {
|
|
365
|
+
name: inv.args[0],
|
|
366
|
+
at: at ?? (await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/branches`)).defaultBranch,
|
|
367
|
+
});
|
|
368
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
369
|
+
if (json.enabled) {
|
|
370
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
console.log(`Created ${data.name} at ${String(data.sha).slice(0, 10)}`);
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
path: ['tag', 'delete'],
|
|
378
|
+
summary: 'Delete a tag',
|
|
379
|
+
args: [{ name: 'name', required: true }],
|
|
380
|
+
options: [YES_OPTION, output_1.JSON_OPTION, ...COMMON],
|
|
381
|
+
async run(inv) {
|
|
382
|
+
requireYes(inv, 'Deleting a tag');
|
|
383
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
384
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
385
|
+
const data = await (0, cli_api_1.api)(target, 'DELETE', `${(0, repo_1.repoPath)(repo)}/tags/${inv.args[0]}`);
|
|
386
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
387
|
+
if (json.enabled) {
|
|
388
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
console.log(`Deleted ${data.deleted}`);
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
path: ['file', 'list'],
|
|
396
|
+
summary: 'List a directory, or every path in the tree',
|
|
397
|
+
description: `With no path this lists the repository root. --all lists every path in the tree
|
|
398
|
+
instead, which is the listing a caller looking for a file wants.`,
|
|
399
|
+
args: [{ name: 'path' }],
|
|
400
|
+
options: [
|
|
401
|
+
REF_OPTION,
|
|
402
|
+
{ name: 'all', type: 'boolean', summary: 'Every path in the tree, recursively' },
|
|
403
|
+
{ name: 'commits', type: 'boolean', summary: 'Add the last commit for each entry (one git log each)' },
|
|
404
|
+
{ name: 'limit', type: 'int', value: '<n>', summary: 'With --all, at most this many paths' },
|
|
405
|
+
output_1.JSON_OPTION,
|
|
406
|
+
...COMMON,
|
|
407
|
+
],
|
|
408
|
+
async run(inv) {
|
|
409
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
410
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
411
|
+
const ref = inv.str('ref');
|
|
412
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
413
|
+
if (inv.bool('all')) {
|
|
414
|
+
const limit = inv.int('limit');
|
|
415
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/paths${refQuery(ref, limit !== null ? { limit: String(limit) } : {})}`);
|
|
416
|
+
if (json.enabled) {
|
|
417
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
for (const p of (data.paths ?? []))
|
|
421
|
+
console.log(p);
|
|
422
|
+
if (data.truncated)
|
|
423
|
+
console.error('(the list was capped by the server; ask for fewer paths)');
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const dir = (inv.args[0] ?? '').replace(/^\/+/, '');
|
|
427
|
+
const query = refQuery(ref, inv.bool('commits') ? { commits: '1' } : {});
|
|
428
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/tree/${dir}${query}`);
|
|
429
|
+
const entries = (data.entries ?? []);
|
|
430
|
+
if (json.enabled) {
|
|
431
|
+
(0, output_1.printJson)({ ref: data.ref, path: data.path, entries: (0, output_1.pickFields)(entries, json.fields) });
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
(0, output_1.printTable)(entries.map((e) => {
|
|
435
|
+
const last = e.lastCommit;
|
|
436
|
+
const row = [String(e.type), String(e.name), e.size === null ? '' : String(e.size)];
|
|
437
|
+
return last ? [...row, (0, output_1.shortDate)(last.date), String(last.subject ?? '')] : row;
|
|
438
|
+
}));
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
path: ['file', 'view'],
|
|
443
|
+
summary: 'Print one file',
|
|
444
|
+
description: `Text is printed as text. A binary file is refused rather than written to a
|
|
445
|
+
terminal, unless --raw is given, which streams the bytes exactly as the vault
|
|
446
|
+
serves them. A Git LFS pointer is reported as one rather than handed over as if it
|
|
447
|
+
were the file, since the bytes are elsewhere.`,
|
|
448
|
+
args: [{ name: 'path', required: true }],
|
|
449
|
+
options: [
|
|
450
|
+
REF_OPTION,
|
|
451
|
+
{ name: 'raw', type: 'boolean', summary: 'The bytes, unchanged, whatever they are' },
|
|
452
|
+
output_1.JSON_OPTION,
|
|
453
|
+
...COMMON,
|
|
454
|
+
],
|
|
455
|
+
async run(inv) {
|
|
456
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
457
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
458
|
+
const ref = inv.str('ref');
|
|
459
|
+
const filePath = inv.args[0].replace(/^\/+/, '');
|
|
460
|
+
if (inv.bool('raw')) {
|
|
461
|
+
const r = await (0, cli_api_1.request)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/raw/${filePath}${refQuery(ref)}`);
|
|
462
|
+
if (!r.ok) {
|
|
463
|
+
process.stderr.write(r.body.endsWith('\n') ? r.body : r.body + '\n');
|
|
464
|
+
process.exit((0, exit_1.exitCodeForStatus)(r.status));
|
|
465
|
+
}
|
|
466
|
+
process.stdout.write(r.body);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/contents/${filePath}${refQuery(ref)}`);
|
|
470
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
471
|
+
if (json.enabled) {
|
|
472
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (data.encoding === 'lfs-pointer') {
|
|
476
|
+
const lfs = data.lfs;
|
|
477
|
+
console.error(`${filePath} is a Git LFS pointer (oid ${lfs.oid}, ${lfs.size} bytes).`);
|
|
478
|
+
console.error('Fetch it with a clone that has git-lfs, or with --raw.');
|
|
479
|
+
process.exit(exit_1.EXIT_USAGE);
|
|
480
|
+
}
|
|
481
|
+
if (data.encoding === 'base64') {
|
|
482
|
+
console.error(`${filePath} is binary (${data.size} bytes). Pass --raw to write the bytes out.`);
|
|
483
|
+
process.exit(exit_1.EXIT_USAGE);
|
|
484
|
+
}
|
|
485
|
+
process.stdout.write(String(data.content ?? ''));
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
path: ['file', 'write'],
|
|
490
|
+
summary: 'Create or replace a file, committing the change',
|
|
491
|
+
description: `The content comes from --body-file, or from --body, or from stdin when neither is
|
|
492
|
+
given, so a generated file can be piped straight in.
|
|
493
|
+
|
|
494
|
+
--expected-sha is the commit the caller last saw. Given one, a branch that has
|
|
495
|
+
moved since is a conflict (exit 5) rather than a silent overwrite, which is what a
|
|
496
|
+
caller that reads, thinks, and then writes wants. 'mochi file view <path>
|
|
497
|
+
--json=commit' is where that value comes from.`,
|
|
498
|
+
args: [{ name: 'path', required: true }],
|
|
499
|
+
options: [
|
|
500
|
+
{ name: 'branch', type: 'string', value: '<b>', summary: 'Branch to commit on; the default branch otherwise' },
|
|
501
|
+
{ name: 'new-branch', type: 'string', value: '<b>', summary: 'Create this branch and commit there instead' },
|
|
502
|
+
{ name: 'message', type: 'string', value: '<m>', summary: 'Commit message' },
|
|
503
|
+
{ name: 'body', type: 'string', value: '<t>', summary: 'The file content' },
|
|
504
|
+
{ name: 'body-file', type: 'string', value: '<f>', summary: "Read the content from a file, or '-' for stdin" },
|
|
505
|
+
{ name: 'expected-sha', type: 'string', value: '<s>', summary: 'The commit the caller last saw at the branch tip' },
|
|
506
|
+
output_1.JSON_OPTION,
|
|
507
|
+
...COMMON,
|
|
508
|
+
],
|
|
509
|
+
async run(inv) {
|
|
510
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
511
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
512
|
+
const inline = inv.str('body');
|
|
513
|
+
const file = inv.str('body-file');
|
|
514
|
+
if (inline !== null && file !== null) {
|
|
515
|
+
throw new exit_1.CliError('Pass either --body or --body-file, not both.', exit_1.EXIT_USAGE);
|
|
516
|
+
}
|
|
517
|
+
const content = inline !== null ? inline : await (0, input_1.readFileArg)(file ?? '-');
|
|
518
|
+
const data = await (0, cli_api_1.api)(target, 'PUT', `${(0, repo_1.repoPath)(repo)}/contents/${inv.args[0].replace(/^\/+/, '')}`, {
|
|
519
|
+
branch: inv.str('branch') ?? (await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/branches`)).defaultBranch ?? 'main',
|
|
520
|
+
newBranch: inv.str('new-branch') ?? undefined,
|
|
521
|
+
message: inv.str('message') ?? undefined,
|
|
522
|
+
expectedSha: inv.str('expected-sha') ?? undefined,
|
|
523
|
+
content,
|
|
524
|
+
});
|
|
525
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
526
|
+
if (json.enabled) {
|
|
527
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
console.log(`${data.created ? 'Created' : 'Updated'} ${data.path} on ${data.branch} as ${String(data.sha).slice(0, 10)}`);
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
path: ['file', 'delete'],
|
|
535
|
+
summary: 'Delete a file, committing the change',
|
|
536
|
+
args: [{ name: 'path', required: true }],
|
|
537
|
+
options: [
|
|
538
|
+
{ name: 'branch', type: 'string', value: '<b>', summary: 'Branch to commit on; the default branch otherwise' },
|
|
539
|
+
{ name: 'message', type: 'string', value: '<m>', summary: 'Commit message' },
|
|
540
|
+
{ name: 'expected-sha', type: 'string', value: '<s>', summary: 'The commit the caller last saw at the branch tip' },
|
|
541
|
+
YES_OPTION,
|
|
542
|
+
output_1.JSON_OPTION,
|
|
543
|
+
...COMMON,
|
|
544
|
+
],
|
|
545
|
+
async run(inv) {
|
|
546
|
+
requireYes(inv, 'Deleting a file');
|
|
547
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
548
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
549
|
+
const data = await (0, cli_api_1.api)(target, 'DELETE', `${(0, repo_1.repoPath)(repo)}/contents/${inv.args[0].replace(/^\/+/, '')}`, {
|
|
550
|
+
branch: inv.str('branch') ?? (await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/branches`)).defaultBranch ?? 'main',
|
|
551
|
+
message: inv.str('message') ?? undefined,
|
|
552
|
+
expectedSha: inv.str('expected-sha') ?? undefined,
|
|
553
|
+
});
|
|
554
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
555
|
+
if (json.enabled) {
|
|
556
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
console.log(`Deleted ${data.path} on ${data.branch} as ${String(data.sha).slice(0, 10)}`);
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
path: ['commit', 'list'],
|
|
564
|
+
summary: 'List commits',
|
|
565
|
+
options: [
|
|
566
|
+
REF_OPTION,
|
|
567
|
+
{ name: 'path', type: 'string', value: '<p>', summary: 'Only commits touching this path' },
|
|
568
|
+
{ name: 'limit', type: 'int', value: '<n>', summary: 'At most this many' },
|
|
569
|
+
output_1.JSON_OPTION,
|
|
570
|
+
...COMMON,
|
|
571
|
+
],
|
|
572
|
+
async run(inv) {
|
|
573
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
574
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
575
|
+
const extra = {};
|
|
576
|
+
const filePath = inv.str('path');
|
|
577
|
+
if (filePath !== null)
|
|
578
|
+
extra.path = filePath;
|
|
579
|
+
const limit = inv.int('limit');
|
|
580
|
+
if (limit !== null)
|
|
581
|
+
extra.limit = String(limit);
|
|
582
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/commits${refQuery(inv.str('ref'), extra)}`);
|
|
583
|
+
const commits = (data.commits ?? []);
|
|
584
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
585
|
+
if (json.enabled) {
|
|
586
|
+
(0, output_1.printJson)({ ref: data.ref, commits: (0, output_1.pickFields)(commits, json.fields) });
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
(0, output_1.printTable)(commits.map((c) => [String(c.sha).slice(0, 10), (0, output_1.shortDate)(c.date), String(c.author), String(c.subject)]));
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
path: ['commit', 'view'],
|
|
594
|
+
summary: 'Show one commit',
|
|
595
|
+
args: [{ name: 'sha', required: true }],
|
|
596
|
+
options: [
|
|
597
|
+
{ name: 'patch', type: 'boolean', summary: 'Print the patch instead of the summary' },
|
|
598
|
+
output_1.JSON_OPTION,
|
|
599
|
+
...COMMON,
|
|
600
|
+
],
|
|
601
|
+
async run(inv) {
|
|
602
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
603
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
604
|
+
const sha = inv.args[0];
|
|
605
|
+
if (inv.bool('patch')) {
|
|
606
|
+
const r = await (0, cli_api_1.request)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/commits/${encodeURIComponent(sha)}/patch`);
|
|
607
|
+
if (!r.ok) {
|
|
608
|
+
process.stderr.write(r.body.endsWith('\n') ? r.body : r.body + '\n');
|
|
609
|
+
process.exit((0, exit_1.exitCodeForStatus)(r.status));
|
|
610
|
+
}
|
|
611
|
+
process.stdout.write(r.body);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/commits/${encodeURIComponent(sha)}`);
|
|
615
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
616
|
+
if (json.enabled) {
|
|
617
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
console.log(`commit ${data.sha}`);
|
|
621
|
+
console.log(`author ${data.author} <${data.email ?? ''}>`);
|
|
622
|
+
console.log(`date ${data.date}`);
|
|
623
|
+
if (data.parents?.length)
|
|
624
|
+
console.log(`parents ${data.parents.join(' ')}`);
|
|
625
|
+
console.log('');
|
|
626
|
+
console.log(String(data.message ?? '').trimEnd());
|
|
627
|
+
},
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
path: ['diff'],
|
|
631
|
+
summary: 'Compare two refs, as <base>...<head>',
|
|
632
|
+
args: [{ name: 'range', required: true }],
|
|
633
|
+
options: [
|
|
634
|
+
{ name: 'stat', type: 'boolean', summary: 'Only the counts and the commit list' },
|
|
635
|
+
output_1.JSON_OPTION,
|
|
636
|
+
...COMMON,
|
|
637
|
+
],
|
|
638
|
+
async run(inv) {
|
|
639
|
+
const range = inv.args[0];
|
|
640
|
+
if (!range.includes('...'))
|
|
641
|
+
throw new exit_1.CliError("ask for a comparison as <base>...<head>", exit_1.EXIT_USAGE);
|
|
642
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
643
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
644
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/compare/${range}`);
|
|
645
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
646
|
+
if (json.enabled) {
|
|
647
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
if (inv.bool('stat')) {
|
|
651
|
+
console.log(`${data.ahead} ahead, ${data.behind} behind`);
|
|
652
|
+
(0, output_1.printTable)((data.commits ?? []).map((c) => [String(c.sha).slice(0, 10), String(c.subject)]));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
process.stdout.write(String(data.patch ?? ''));
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
path: ['search'],
|
|
660
|
+
summary: 'Search a repository for literal text',
|
|
661
|
+
args: [{ name: 'query', required: true }],
|
|
662
|
+
options: [REF_OPTION, output_1.JSON_OPTION, ...COMMON],
|
|
663
|
+
async run(inv) {
|
|
664
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
665
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target);
|
|
666
|
+
const query = refQuery(inv.str('ref'), { q: inv.args[0] });
|
|
667
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/search${query}`);
|
|
668
|
+
const hits = (data.hits ?? []);
|
|
669
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
670
|
+
if (json.enabled) {
|
|
671
|
+
(0, output_1.printJson)({ ref: data.ref, query: data.query, hits: (0, output_1.pickFields)(hits, json.fields), truncated: data.truncated });
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (hits.length === 0) {
|
|
675
|
+
console.log('No matches');
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
for (const h of hits)
|
|
679
|
+
console.log(`${h.path}:${h.line}: ${String(h.text ?? '').trim()}`);
|
|
680
|
+
if (data.truncated)
|
|
681
|
+
console.error('(the server capped the results)');
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
// ---- collaborators ----
|
|
685
|
+
//
|
|
686
|
+
// All three take the admin role on the repository, as the API routes behind
|
|
687
|
+
// them do: who may see or write a repository is decided by the people who
|
|
688
|
+
// administer it.
|
|
689
|
+
{
|
|
690
|
+
path: ['collab', 'list'],
|
|
691
|
+
summary: "Show a repository's collaborators, and the owners standing over them",
|
|
692
|
+
args: [{ name: 'repo' }],
|
|
693
|
+
options: [output_1.JSON_OPTION, ...COMMON],
|
|
694
|
+
async run(inv) {
|
|
695
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
696
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0] ?? null);
|
|
697
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(repo)}/collaborators`);
|
|
698
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
699
|
+
if (json.enabled) {
|
|
700
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
console.log(`${repo.collection}/${repo.repo} is ${data.private ? 'private' : 'public'}`);
|
|
704
|
+
const collaborators = (data.collaborators ?? []);
|
|
705
|
+
if (collaborators.length === 0)
|
|
706
|
+
console.log('No collaborators.');
|
|
707
|
+
else
|
|
708
|
+
(0, output_1.printTable)(collaborators.map((c) => [c.username, c.role]));
|
|
709
|
+
const owners = (data.owners ?? []);
|
|
710
|
+
console.log(`Owners of ${repo.collection} hold the admin role without being listed: ` +
|
|
711
|
+
`${[repo.collection, ...owners].join(', ')} (the first by bearing the collection's name).`);
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
path: ['collab', 'add'],
|
|
716
|
+
summary: 'Give a user a role on a repository',
|
|
717
|
+
description: `read may see a private repository; write may also push and edit; admin may also
|
|
718
|
+
change its settings, visibility, and collaborators. Adding a user who already
|
|
719
|
+
has a role replaces it.`,
|
|
720
|
+
args: [
|
|
721
|
+
{ name: 'repo', required: true },
|
|
722
|
+
{ name: 'username', required: true },
|
|
723
|
+
],
|
|
724
|
+
options: [
|
|
725
|
+
{ name: 'role', type: 'string', value: '<r>', summary: 'read, write (the default), or admin' },
|
|
726
|
+
output_1.JSON_OPTION,
|
|
727
|
+
...target_1.TARGET_OPTIONS,
|
|
728
|
+
],
|
|
729
|
+
async run(inv) {
|
|
730
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
731
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0]);
|
|
732
|
+
const data = await (0, cli_api_1.api)(target, 'PUT', `${(0, repo_1.repoPath)(repo)}/collaborators/${encodeURIComponent(inv.args[1])}`, {
|
|
733
|
+
role: inv.str('role') ?? 'write',
|
|
734
|
+
});
|
|
735
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
736
|
+
if (json.enabled) {
|
|
737
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
console.log(`${data.username} now has the ${data.role} role on ${repo.collection}/${repo.repo}`);
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
path: ['collab', 'remove'],
|
|
745
|
+
summary: "Remove a user's role on a repository",
|
|
746
|
+
args: [
|
|
747
|
+
{ name: 'repo', required: true },
|
|
748
|
+
{ name: 'username', required: true },
|
|
749
|
+
],
|
|
750
|
+
options: [output_1.JSON_OPTION, ...target_1.TARGET_OPTIONS],
|
|
751
|
+
async run(inv) {
|
|
752
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
753
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0]);
|
|
754
|
+
const data = await (0, cli_api_1.api)(target, 'DELETE', `${(0, repo_1.repoPath)(repo)}/collaborators/${encodeURIComponent(inv.args[1])}`);
|
|
755
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
756
|
+
if (json.enabled) {
|
|
757
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
console.log(`Removed ${data.removed} from ${repo.collection}/${repo.repo}`);
|
|
761
|
+
},
|
|
762
|
+
},
|
|
763
|
+
];
|