@magland/mochi 0.3.1 → 0.4.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/README.md +7 -6
- package/dist/agefile.js +47 -0
- package/dist/agescript.js +343 -0
- package/dist/api/admin.js +66 -28
- package/dist/api/auth.js +3 -2
- package/dist/api/backup.js +227 -192
- package/dist/api/contents.js +46 -16
- package/dist/api/repos.js +60 -4
- package/dist/api/write.js +138 -8
- package/dist/api.js +34 -1
- package/dist/browse.js +171 -33
- package/dist/build-info.json +2 -2
- package/dist/ci/api.js +138 -4
- package/dist/ci/artifacts.js +82 -2
- package/dist/ci/engine.js +30 -17
- package/dist/ci/expr.js +14 -0
- package/dist/ci/runners.js +60 -1
- package/dist/ci/runs.js +21 -3
- package/dist/ci/trigger.js +6 -4
- package/dist/ci/views.js +24 -3
- package/dist/ci/web.js +48 -0
- package/dist/cli/admin-cmd.js +40 -3
- package/dist/cli/backup-cmd.js +166 -120
- package/dist/cli/gitrun.js +50 -0
- package/dist/cli/pr-cmd.js +155 -0
- package/dist/cli/repo-cmd.js +134 -6
- package/dist/cli/sync-cmd.js +128 -0
- package/dist/cli/target.js +7 -3
- package/dist/config.js +10 -0
- package/dist/credentials.js +9 -8
- package/dist/deploy-cli.js +214 -109
- package/dist/deploy-runner-cli.js +125 -49
- package/dist/domains.js +259 -0
- package/dist/egress.js +23 -6
- package/dist/forms.js +703 -32
- package/dist/git.js +124 -13
- package/dist/githttp.js +80 -23
- package/dist/githubauth.js +185 -0
- package/dist/icons.js +14 -0
- package/dist/import-cli.js +40 -25
- package/dist/index.js +110 -4
- package/dist/issueweb.js +10 -6
- package/dist/lfs.js +9 -2
- package/dist/limit.js +2 -2
- package/dist/logincodes.js +31 -0
- package/dist/maintenance.js +297 -0
- package/dist/markdown.js +193 -3
- package/dist/mdedit.js +29 -0
- package/dist/migrate.js +48 -0
- package/dist/naming.js +25 -0
- package/dist/onetime.js +112 -0
- package/dist/ops.js +293 -16
- package/dist/pagescript.js +355 -24
- package/dist/perms.js +1 -0
- package/dist/profile.js +2 -0
- package/dist/pulls.js +21 -1
- package/dist/pullweb.js +25 -6
- package/dist/releases.js +10 -1
- package/dist/runner/actions.js +12 -1
- package/dist/runner/client.js +40 -2
- package/dist/runner/docker.js +40 -28
- package/dist/runner/download.js +77 -0
- package/dist/runner/externals.js +12 -1
- package/dist/runner/job.js +26 -11
- package/dist/runner/steps.js +42 -7
- package/dist/runner-cli.js +89 -0
- package/dist/scan.js +58 -2
- package/dist/server.js +107 -26
- package/dist/session.js +82 -40
- package/dist/site.js +110 -27
- package/dist/sitepublish.js +177 -0
- package/dist/sitesettings.js +420 -0
- package/dist/siteshost.js +116 -28
- package/dist/source.js +20 -0
- package/dist/style.js +162 -3
- package/dist/topics.js +165 -0
- package/dist/vault.js +399 -6
- package/dist/vendor-age.js +15 -0
- package/dist/views.js +242 -57
- package/dist/web.js +11 -1
- package/dist/webauthn.js +347 -0
- package/dist/webops.js +1263 -33
- package/package.json +4 -4
package/dist/cli/pr-cmd.js
CHANGED
|
@@ -1,7 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.prCommands = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
4
40
|
const cli_api_1 = require("../cli-api");
|
|
41
|
+
const source_1 = require("../source");
|
|
42
|
+
const gitrun_1 = require("./gitrun");
|
|
5
43
|
const exit_1 = require("./exit");
|
|
6
44
|
const input_1 = require("./input");
|
|
7
45
|
const output_1 = require("./output");
|
|
@@ -266,6 +304,123 @@ commit. That is what this reports, and it is worth knowing that is what it means
|
|
|
266
304
|
(0, output_1.printTable)(matching.map((r) => [`#${r.number}`, String(r.status), String(r.conclusion ?? ''), String(r.workflowName ?? r.workflow ?? '')]));
|
|
267
305
|
},
|
|
268
306
|
},
|
|
307
|
+
{
|
|
308
|
+
path: ['pr', 'export'],
|
|
309
|
+
summary: 'Send a pull request on to the GitHub repository this one was forked from',
|
|
310
|
+
description: `The repository must have a GitHub upstream recorded (mochi fork records one;
|
|
311
|
+
mochi repo edit --upstream sets one). The pull request's head branch is pushed
|
|
312
|
+
to a fork under your GitHub account, made with gh if it does not exist yet,
|
|
313
|
+
and a pull request with the same title and body is opened against the
|
|
314
|
+
upstream. Running it again force-pushes the branch and finds the pull request
|
|
315
|
+
already open rather than opening a second one.
|
|
316
|
+
|
|
317
|
+
GitHub is only ever touched from this machine, through gh's own credentials:
|
|
318
|
+
the vault holds no GitHub token, so run gh auth login once first.`,
|
|
319
|
+
args: [{ name: 'number', required: true }],
|
|
320
|
+
options: [
|
|
321
|
+
{ name: 'base', type: 'string', value: '<b>', summary: 'Base branch on GitHub (default: the pull request base)' },
|
|
322
|
+
{ name: 'fork', type: 'string', value: '<o/r>', summary: 'Your GitHub fork (default: <your login>/<upstream name>)' },
|
|
323
|
+
...COMMON,
|
|
324
|
+
],
|
|
325
|
+
async run(inv) {
|
|
326
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
327
|
+
const ref = await (0, repo_1.resolveRepo)(inv, target);
|
|
328
|
+
const n = pullRef(inv);
|
|
329
|
+
const pull = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(ref)}/pulls/${n}`);
|
|
330
|
+
if (pull.state !== 'open') {
|
|
331
|
+
throw new exit_1.CliError(`Pull request #${n} is ${pull.state}; only an open one can be exported.`, exit_1.EXIT_USAGE);
|
|
332
|
+
}
|
|
333
|
+
const head = String(pull.head ?? '');
|
|
334
|
+
const repoData = await (0, cli_api_1.api)(target, 'GET', (0, repo_1.repoPath)(ref));
|
|
335
|
+
const upstream = (0, source_1.parseUpstream)(String(repoData.upstream ?? ''));
|
|
336
|
+
if (!upstream?.github) {
|
|
337
|
+
throw new exit_1.CliError(`${ref.collection}/${ref.repo} has no GitHub upstream recorded, so there is nowhere to send this.\n` +
|
|
338
|
+
`Fork with mochi fork, or record one:\n` +
|
|
339
|
+
` mochi repo edit ${ref.collection}/${ref.repo} --upstream https://github.com/owner/repo`, exit_1.EXIT_USAGE);
|
|
340
|
+
}
|
|
341
|
+
const gh = upstream.github;
|
|
342
|
+
const login = (await (0, gitrun_1.capture)('gh', ['api', 'user', '--jq', '.login']))?.trim();
|
|
343
|
+
if (!login) {
|
|
344
|
+
throw new exit_1.CliError('GitHub is reached through gh, which is not installed or not logged in. Run: gh auth login', exit_1.EXIT_FAIL);
|
|
345
|
+
}
|
|
346
|
+
const forkSpec = inv.str('fork') ?? `${login}/${gh.repo}`;
|
|
347
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(forkSpec)) {
|
|
348
|
+
throw new exit_1.CliError(`--fork should be <owner>/<repo>, not '${forkSpec}'`, exit_1.EXIT_USAGE);
|
|
349
|
+
}
|
|
350
|
+
// The head branch has to live on GitHub for GitHub to accept it, so a
|
|
351
|
+
// fork under your account is the unavoidable middle step. It is a
|
|
352
|
+
// publishing mirror: it holds the branches you have exported and nothing
|
|
353
|
+
// else, and gh creates it the first time.
|
|
354
|
+
if ((await (0, gitrun_1.capture)('gh', ['repo', 'view', forkSpec, '--json', 'name'])) === null) {
|
|
355
|
+
if (inv.str('fork') !== null) {
|
|
356
|
+
throw new exit_1.CliError(`${forkSpec} was not found on GitHub.`, exit_1.EXIT_FAIL);
|
|
357
|
+
}
|
|
358
|
+
console.log(`Forking ${gh.owner}/${gh.repo} on GitHub`);
|
|
359
|
+
if ((await (0, gitrun_1.run)('gh', ['repo', 'fork', `${gh.owner}/${gh.repo}`, '--clone=false'])) !== 0) {
|
|
360
|
+
throw new exit_1.CliError(`Could not fork ${gh.owner}/${gh.repo} on GitHub.`, exit_1.EXIT_FAIL);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const vaultUrl = `${target.host}/${encodeURIComponent(ref.collection)}/${encodeURIComponent(ref.repo)}`;
|
|
364
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mochi-export-'));
|
|
365
|
+
try {
|
|
366
|
+
console.log(`Fetching ${ref.collection}/${ref.repo} @ ${head}`);
|
|
367
|
+
const env = (0, gitrun_1.mochiCredentialEnv)('mochi', target.token);
|
|
368
|
+
if ((await (0, gitrun_1.run)('git', [...(0, gitrun_1.mochiCredentialArgs)(), 'clone', '--bare', '--quiet', '--single-branch', '--branch', head, vaultUrl, tmp], env)) !== 0) {
|
|
369
|
+
throw new exit_1.CliError(`Could not fetch branch ${head} from ${vaultUrl}.`, exit_1.EXIT_FAIL);
|
|
370
|
+
}
|
|
371
|
+
// Forced, deliberately: the branch on the fork exists only as this
|
|
372
|
+
// pull request's published copy, and a re-export after a rebase in
|
|
373
|
+
// the vault must win.
|
|
374
|
+
console.log(`Pushing ${head} to ${forkSpec}`);
|
|
375
|
+
if ((await (0, gitrun_1.run)('git', ['-C', tmp, ...(0, gitrun_1.ghCredentialArgs)(), 'push', '--quiet', '--force', `https://github.com/${forkSpec}.git`, `refs/heads/${head}:refs/heads/${head}`])) !== 0) {
|
|
376
|
+
throw new exit_1.CliError(`Could not push ${head} to ${forkSpec}.`, exit_1.EXIT_FAIL);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
finally {
|
|
380
|
+
try {
|
|
381
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// A leftover temporary directory is not worth failing the export over.
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const ghHead = `${forkSpec.split('/')[0]}:${head}`;
|
|
388
|
+
const findExisting = async () => {
|
|
389
|
+
const out = await (0, gitrun_1.capture)('gh', ['pr', 'list', '--repo', `${gh.owner}/${gh.repo}`, '--head', ghHead, '--state', 'open', '--json', 'url']);
|
|
390
|
+
if (out === null)
|
|
391
|
+
return null;
|
|
392
|
+
try {
|
|
393
|
+
const arr = JSON.parse(out);
|
|
394
|
+
return arr.length ? String(arr[0].url ?? '') || null : null;
|
|
395
|
+
}
|
|
396
|
+
catch {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
const existing = await findExisting();
|
|
401
|
+
if (existing) {
|
|
402
|
+
console.log(`Updated the branch behind the pull request already open: ${existing}`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const base = inv.str('base') ?? String(pull.base ?? '');
|
|
406
|
+
const body = `${String(pull.body ?? '').trim()}\n\n---\n\nExported from ${vaultUrl}/pulls/${n}\n`;
|
|
407
|
+
const bodyFile = path.join(os.tmpdir(), `mochi-export-body-${process.pid}.md`);
|
|
408
|
+
fs.writeFileSync(bodyFile, body);
|
|
409
|
+
try {
|
|
410
|
+
if ((await (0, gitrun_1.run)('gh', ['pr', 'create', '--repo', `${gh.owner}/${gh.repo}`, '--base', base, '--head', ghHead, '--title', String(pull.title ?? `Pull request ${n}`), '--body-file', bodyFile])) !== 0) {
|
|
411
|
+
throw new exit_1.CliError(`gh could not open the pull request on ${gh.owner}/${gh.repo}.`, exit_1.EXIT_FAIL);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
fs.rmSync(bodyFile, { force: true });
|
|
416
|
+
}
|
|
417
|
+
// Close the loop in the vault: the mochi pull request says where it went.
|
|
418
|
+
const url = await findExisting();
|
|
419
|
+
if (url) {
|
|
420
|
+
await (0, cli_api_1.apiTry)(target, 'POST', `${(0, repo_1.repoPath)(ref)}/pulls/${n}/comments`, { body: `Exported to ${url}` });
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
},
|
|
269
424
|
...['close', 'reopen'].map((verb) => ({
|
|
270
425
|
path: ['pr', verb],
|
|
271
426
|
summary: verb === 'close' ? 'Close a pull request without merging it' : 'Reopen a closed pull request',
|
package/dist/cli/repo-cmd.js
CHANGED
|
@@ -30,6 +30,18 @@ function requireYes(inv, what) {
|
|
|
30
30
|
if (!inv.bool('yes'))
|
|
31
31
|
throw new exit_1.CliError(`${what} cannot be undone. Pass --yes to go ahead.`, exit_1.EXIT_USAGE);
|
|
32
32
|
}
|
|
33
|
+
// One line about the site for `repo view`: off, or where it is served, with
|
|
34
|
+
// the source when it is workflow deploys, since that is the half a reader
|
|
35
|
+
// cannot guess.
|
|
36
|
+
function siteSummary(data) {
|
|
37
|
+
const site = data.site;
|
|
38
|
+
if (!site)
|
|
39
|
+
return data.hasSite ? 'yes' : 'no';
|
|
40
|
+
if (!site.enabled)
|
|
41
|
+
return 'off';
|
|
42
|
+
const source = site.source === 'actions' ? ', deployed by workflows' : site.source === 'repository' ? ', published from the repository' : '';
|
|
43
|
+
return `${site.url ?? 'on'}${source}`;
|
|
44
|
+
}
|
|
33
45
|
function refQuery(ref, extra = {}) {
|
|
34
46
|
const q = new URLSearchParams(extra);
|
|
35
47
|
if (ref !== null)
|
|
@@ -42,10 +54,15 @@ exports.repoCommands = [
|
|
|
42
54
|
path: ['repo', 'list'],
|
|
43
55
|
summary: 'List the repositories in the vault',
|
|
44
56
|
args: [{ name: 'collection' }],
|
|
45
|
-
options: [
|
|
57
|
+
options: [
|
|
58
|
+
{ name: 'topic', type: 'string', value: '<t>', summary: 'Only repositories carrying this topic' },
|
|
59
|
+
output_1.JSON_OPTION,
|
|
60
|
+
...target_1.TARGET_OPTIONS,
|
|
61
|
+
],
|
|
46
62
|
async run(inv) {
|
|
47
63
|
const target = await (0, target_1.targetFrom)(inv);
|
|
48
|
-
const
|
|
64
|
+
const topic = inv.str('topic');
|
|
65
|
+
const data = await (0, cli_api_1.api)(target, 'GET', `/api/repos${topic !== null ? `?topic=${encodeURIComponent(topic)}` : ''}`);
|
|
49
66
|
const only = inv.args[0] ?? null;
|
|
50
67
|
const repos = (data.repos ?? []).filter((r) => only === null || r.collection === only);
|
|
51
68
|
const json = (0, output_1.jsonMode)(inv);
|
|
@@ -84,8 +101,12 @@ exports.repoCommands = [
|
|
|
84
101
|
const from = data.forkedFrom;
|
|
85
102
|
console.log(`forked from ${from.collection}/${from.repo}`);
|
|
86
103
|
}
|
|
104
|
+
if (data.upstream)
|
|
105
|
+
console.log(`forked from ${data.upstream}`);
|
|
106
|
+
const topics = (data.topics ?? []);
|
|
87
107
|
(0, output_1.printTable)([
|
|
88
108
|
['visibility', data.private ? 'private' : 'public'],
|
|
109
|
+
...(topics.length ? [['topics', topics.join(', ')]] : []),
|
|
89
110
|
['default branch', String(data.defaultBranch ?? '(none)')],
|
|
90
111
|
['last updated', (0, output_1.shortDate)(data.updated)],
|
|
91
112
|
['branches', String(data.branches)],
|
|
@@ -93,7 +114,7 @@ exports.repoCommands = [
|
|
|
93
114
|
['releases', String(data.releases)],
|
|
94
115
|
['open issues', String(data.openIssues)],
|
|
95
116
|
['open pull requests', String(data.openPulls)],
|
|
96
|
-
['site', data
|
|
117
|
+
['site', siteSummary(data)],
|
|
97
118
|
]);
|
|
98
119
|
},
|
|
99
120
|
},
|
|
@@ -155,32 +176,98 @@ exports.repoCommands = [
|
|
|
155
176
|
},
|
|
156
177
|
{
|
|
157
178
|
path: ['repo', 'edit'],
|
|
158
|
-
summary: 'Change a repository description, default branch, or
|
|
179
|
+
summary: 'Change a repository description, topics, default branch, visibility, or site settings',
|
|
180
|
+
description: `--topic replaces the whole set, which is what the API does; --add-topic and
|
|
181
|
+
--remove-topic keep what is there and change it, which means reading the
|
|
182
|
+
repository first. A topic is lowercase letters, digits, and hyphens.
|
|
183
|
+
|
|
184
|
+
The site settings take the admin role. --enable-site serves the repository's
|
|
185
|
+
<repo>.site directory; --site-source actions additionally lets a workflow's
|
|
186
|
+
deploy-pages step publish it, and --site-source repository has the server
|
|
187
|
+
publish the default branch itself on every push (--site-path picks a
|
|
188
|
+
directory within it, such as docs; '' is the root). --site-label picks the label under the vault's
|
|
189
|
+
sites host ('' goes back to the derived <repo>--<alias>, where the alias stands
|
|
190
|
+
in for the collection's name), and --site-domain attaches a custom domain,
|
|
191
|
+
which takes a site admin ('' detaches it). A label another repository holds, or
|
|
192
|
+
one the vault reserves for its operator, is refused.`,
|
|
159
193
|
args: [{ name: 'repo' }],
|
|
160
194
|
options: [
|
|
161
195
|
{ name: 'description', type: 'string', value: '<d>', summary: 'New description' },
|
|
196
|
+
{ name: 'topic', type: 'string[]', value: '<t>', summary: 'Replace the topics with these' },
|
|
197
|
+
{ name: 'add-topic', type: 'string[]', value: '<t>', summary: 'Keep the existing topics and add these' },
|
|
198
|
+
{ name: 'remove-topic', type: 'string[]', value: '<t>', summary: 'Keep the existing topics apart from these' },
|
|
199
|
+
{ name: 'clear-topics', type: 'boolean', summary: 'Remove every topic' },
|
|
162
200
|
{ name: 'default-branch', type: 'string', value: '<b>', summary: 'New default branch' },
|
|
201
|
+
{ name: 'upstream', type: 'string', value: '<url>', summary: "URL this was forked from; '' clears it" },
|
|
163
202
|
{ name: 'private', type: 'boolean', summary: 'Make the repository private (takes the admin role)' },
|
|
164
203
|
{ name: 'public', type: 'boolean', summary: 'Make the repository public' },
|
|
204
|
+
{ name: 'enable-site', type: 'boolean', summary: 'Serve the repository site (takes the admin role)' },
|
|
205
|
+
{ name: 'disable-site', type: 'boolean', summary: 'Stop serving the site; its files stay on disk' },
|
|
206
|
+
{ name: 'site-source', type: 'string', value: '<s>', summary: "How the site is published: 'copy', 'actions', or 'repository'" },
|
|
207
|
+
{ name: 'site-path', type: 'string', value: '<dir>', summary: "Directory published by a 'repository' site; '' for the root" },
|
|
208
|
+
{ name: 'site-label', type: 'string', value: '<l>', summary: "Label under the sites host; '' for the default" },
|
|
209
|
+
{ name: 'site-domain', type: 'string', value: '<h>', summary: "Custom domain (site admin); '' detaches it" },
|
|
165
210
|
output_1.JSON_OPTION,
|
|
166
211
|
...COMMON,
|
|
167
212
|
],
|
|
168
213
|
async run(inv) {
|
|
169
214
|
const description = inv.str('description');
|
|
170
215
|
const defaultBranch = inv.str('default-branch');
|
|
216
|
+
const upstream = inv.str('upstream');
|
|
171
217
|
if (inv.bool('private') && inv.bool('public')) {
|
|
172
218
|
throw new exit_1.CliError('Pass --private or --public, not both.', exit_1.EXIT_USAGE);
|
|
173
219
|
}
|
|
174
220
|
const priv = inv.bool('private') ? true : inv.bool('public') ? false : undefined;
|
|
175
|
-
if (
|
|
176
|
-
throw new exit_1.CliError('
|
|
221
|
+
if (inv.bool('enable-site') && inv.bool('disable-site')) {
|
|
222
|
+
throw new exit_1.CliError('Pass --enable-site or --disable-site, not both.', exit_1.EXIT_USAGE);
|
|
223
|
+
}
|
|
224
|
+
const siteEnabled = inv.bool('enable-site') ? true : inv.bool('disable-site') ? false : undefined;
|
|
225
|
+
const siteSource = inv.str('site-source');
|
|
226
|
+
if (siteSource !== null && siteSource !== 'copy' && siteSource !== 'actions' && siteSource !== 'repository') {
|
|
227
|
+
throw new exit_1.CliError("--site-source takes 'copy', 'actions', or 'repository'.", exit_1.EXIT_USAGE);
|
|
228
|
+
}
|
|
229
|
+
const sitePath = inv.str('site-path');
|
|
230
|
+
const siteLabel = inv.str('site-label');
|
|
231
|
+
const siteDomain = inv.str('site-domain');
|
|
232
|
+
const set = inv.list('topic');
|
|
233
|
+
const add = inv.list('add-topic');
|
|
234
|
+
const remove = inv.list('remove-topic');
|
|
235
|
+
const clear = inv.bool('clear-topics');
|
|
236
|
+
// --topic and --clear-topics each name the whole set, so they combine
|
|
237
|
+
// with nothing; --add-topic and --remove-topic edit it and may combine.
|
|
238
|
+
if ((set.length !== 0 ? 1 : 0) + (clear ? 1 : 0) + (add.length !== 0 || remove.length !== 0 ? 1 : 0) > 1) {
|
|
239
|
+
throw new exit_1.CliError('Pass --topic or --clear-topics on their own, or --add-topic/--remove-topic together.', exit_1.EXIT_USAGE);
|
|
177
240
|
}
|
|
178
241
|
const target = await (0, target_1.targetFrom)(inv);
|
|
179
242
|
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0] ?? null);
|
|
243
|
+
let topics = clear ? [] : set.length ? set : undefined;
|
|
244
|
+
if (add.length || remove.length) {
|
|
245
|
+
const current = (await (0, cli_api_1.api)(target, 'GET', (0, repo_1.repoPath)(repo))).topics;
|
|
246
|
+
topics = [...new Set([...(current ?? []), ...add])].filter((t) => !remove.includes(t));
|
|
247
|
+
}
|
|
248
|
+
if (description === null &&
|
|
249
|
+
defaultBranch === null &&
|
|
250
|
+
upstream === null &&
|
|
251
|
+
topics === undefined &&
|
|
252
|
+
priv === undefined &&
|
|
253
|
+
siteEnabled === undefined &&
|
|
254
|
+
siteSource === null &&
|
|
255
|
+
sitePath === null &&
|
|
256
|
+
siteLabel === null &&
|
|
257
|
+
siteDomain === null) {
|
|
258
|
+
throw new exit_1.CliError('Nothing to change. Pass --description, --topic, --add-topic, --remove-topic, --clear-topics, --default-branch, --upstream, --private, --public, --enable-site, --disable-site, --site-source, --site-path, --site-label, or --site-domain.', exit_1.EXIT_USAGE);
|
|
259
|
+
}
|
|
180
260
|
const data = await (0, cli_api_1.api)(target, 'PATCH', (0, repo_1.repoPath)(repo), {
|
|
181
261
|
description: description ?? undefined,
|
|
262
|
+
topics,
|
|
182
263
|
defaultBranch: defaultBranch ?? undefined,
|
|
264
|
+
upstream: upstream ?? undefined,
|
|
183
265
|
private: priv,
|
|
266
|
+
siteEnabled,
|
|
267
|
+
siteSource: siteSource ?? undefined,
|
|
268
|
+
sitePath: sitePath ?? undefined,
|
|
269
|
+
siteLabel: siteLabel ?? undefined,
|
|
270
|
+
siteDomain: siteDomain ?? undefined,
|
|
184
271
|
});
|
|
185
272
|
const json = (0, output_1.jsonMode)(inv);
|
|
186
273
|
if (json.enabled) {
|
|
@@ -188,6 +275,11 @@ exports.repoCommands = [
|
|
|
188
275
|
return;
|
|
189
276
|
}
|
|
190
277
|
console.log(`Updated ${data.collection}/${data.name}`);
|
|
278
|
+
const published = data.sitePublish;
|
|
279
|
+
if (published?.error)
|
|
280
|
+
console.error(`The site could not be published from the repository: ${published.error}`);
|
|
281
|
+
else if (published?.files !== undefined)
|
|
282
|
+
console.log(`Site published from the repository: ${published.files} file(s)`);
|
|
191
283
|
},
|
|
192
284
|
},
|
|
193
285
|
{
|
|
@@ -246,6 +338,42 @@ together.`,
|
|
|
246
338
|
console.log(`Deleted ${full}`);
|
|
247
339
|
},
|
|
248
340
|
},
|
|
341
|
+
{
|
|
342
|
+
path: ['repo', 'gc'],
|
|
343
|
+
summary: 'Drop every object no branch or tag can reach',
|
|
344
|
+
description: `Git adds objects and never removes them, so a force push, a deleted branch, or a
|
|
345
|
+
rewritten history leaves what it abandoned in the repository: unreachable from
|
|
346
|
+
any ref, still readable by anyone who knows the hash, and still counted against
|
|
347
|
+
the disk. This drops all of it now.
|
|
348
|
+
|
|
349
|
+
The vault collects each repository on its own every few hours, sparing anything
|
|
350
|
+
unreachable but newer than two days so that a push in flight is never pruned.
|
|
351
|
+
This command is for when that is not soon enough: a file rewritten out of a
|
|
352
|
+
history is unreachable at once, but until it is collected it can still be
|
|
353
|
+
fetched by naming its commit. Objects younger than five minutes are still
|
|
354
|
+
spared, which is the same in-flight push the sweep protects.
|
|
355
|
+
|
|
356
|
+
Takes the admin role on the repository. What it removes cannot be recovered.`,
|
|
357
|
+
args: [{ name: 'repo' }],
|
|
358
|
+
options: [YES_OPTION, output_1.JSON_OPTION, ...target_1.TARGET_OPTIONS],
|
|
359
|
+
async run(inv) {
|
|
360
|
+
requireYes(inv, 'Dropping unreachable objects');
|
|
361
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
362
|
+
const repo = await (0, repo_1.resolveRepo)(inv, target, inv.args[0]);
|
|
363
|
+
const full = `${repo.collection}/${repo.repo}`;
|
|
364
|
+
const data = await (0, cli_api_1.api)(target, 'POST', `${(0, repo_1.repoPath)(repo)}/gc?confirm=${encodeURIComponent(full)}`);
|
|
365
|
+
const json = (0, output_1.jsonMode)(inv);
|
|
366
|
+
if (json.enabled) {
|
|
367
|
+
(0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const removed = Number(data.removed ?? 0);
|
|
371
|
+
const freed = Number(data.kilobytesFreed ?? 0);
|
|
372
|
+
console.log(removed === 0
|
|
373
|
+
? `Collected ${full}; nothing was unreachable.`
|
|
374
|
+
: `Collected ${full}: ${removed} object${removed === 1 ? '' : 's'} dropped, ${freed >= 1024 ? `${(freed / 1024).toFixed(1)} MB` : `${freed} KB`} freed.`);
|
|
375
|
+
},
|
|
376
|
+
},
|
|
249
377
|
{
|
|
250
378
|
path: ['repo', 'clone'],
|
|
251
379
|
summary: 'git clone, with the vault URL filled in',
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.syncCommand = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const cli_api_1 = require("../cli-api");
|
|
41
|
+
const source_1 = require("../source");
|
|
42
|
+
const exit_1 = require("./exit");
|
|
43
|
+
const gitrun_1 = require("./gitrun");
|
|
44
|
+
const repo_1 = require("./repo");
|
|
45
|
+
const target_1 = require("./target");
|
|
46
|
+
// `mochi sync`: bring a fork's branch up to date with the upstream it was
|
|
47
|
+
// forked from. Without this a fork silently rots, and a pull request exported
|
|
48
|
+
// from a stale base arrives already conflicted.
|
|
49
|
+
//
|
|
50
|
+
// Like import and export, it runs on the operator's machine: a bare clone of
|
|
51
|
+
// the vault's branch, a fetch from the upstream URL on top of it (so only the
|
|
52
|
+
// new objects cross the network), and a push of the result back to the vault.
|
|
53
|
+
// The push is not forced, and the vault refuses non-fast-forwards anyway, so a
|
|
54
|
+
// branch that has grown its own commits is reported as diverged rather than
|
|
55
|
+
// overwritten.
|
|
56
|
+
exports.syncCommand = {
|
|
57
|
+
path: ['sync'],
|
|
58
|
+
summary: 'Fast-forward a branch from the upstream the repository was forked from',
|
|
59
|
+
description: `The upstream is the URL \`mochi fork\` recorded (or \`mochi repo edit --upstream\`
|
|
60
|
+
set). The branch defaults to the repository's default branch. The fetch uses
|
|
61
|
+
whatever git credentials this machine already has, the push uses your mochi
|
|
62
|
+
token, and only a fast-forward is ever pushed: a branch that has diverged from
|
|
63
|
+
its upstream is reported and left alone.`,
|
|
64
|
+
args: [{ name: 'repo' }],
|
|
65
|
+
options: [
|
|
66
|
+
{ name: 'branch', type: 'string', value: '<b>', summary: 'Branch to sync (default: the default branch)' },
|
|
67
|
+
repo_1.REPO_OPTION,
|
|
68
|
+
...target_1.TARGET_OPTIONS,
|
|
69
|
+
],
|
|
70
|
+
async run(inv) {
|
|
71
|
+
const target = await (0, target_1.targetFrom)(inv);
|
|
72
|
+
const ref = await (0, repo_1.resolveRepo)(inv, target, inv.args[0] ?? null);
|
|
73
|
+
const data = await (0, cli_api_1.api)(target, 'GET', (0, repo_1.repoPath)(ref));
|
|
74
|
+
const upstream = (0, source_1.parseUpstream)(String(data.upstream ?? ''));
|
|
75
|
+
if (!upstream) {
|
|
76
|
+
throw new exit_1.CliError(`${ref.collection}/${ref.repo} has no upstream recorded. Fork with mochi fork, or record one:\n` +
|
|
77
|
+
` mochi repo edit ${ref.collection}/${ref.repo} --upstream https://github.com/owner/repo`, exit_1.EXIT_USAGE);
|
|
78
|
+
}
|
|
79
|
+
const branch = inv.str('branch') ?? String(data.defaultBranch ?? '');
|
|
80
|
+
if (!branch)
|
|
81
|
+
throw new exit_1.CliError('No branch to sync: the repository is empty. Pass --branch.', exit_1.EXIT_USAGE);
|
|
82
|
+
const vaultUrl = `${target.host}/${encodeURIComponent(ref.collection)}/${encodeURIComponent(ref.repo)}`;
|
|
83
|
+
const env = (0, gitrun_1.mochiCredentialEnv)('mochi', target.token);
|
|
84
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mochi-sync-'));
|
|
85
|
+
try {
|
|
86
|
+
// The vault side first, so the upstream fetch only carries what is new.
|
|
87
|
+
console.log(`Fetching ${ref.collection}/${ref.repo} @ ${branch}`);
|
|
88
|
+
if ((await (0, gitrun_1.run)('git', [...(0, gitrun_1.mochiCredentialArgs)(), 'clone', '--bare', '--quiet', '--single-branch', '--branch', branch, vaultUrl, tmp], env)) !== 0) {
|
|
89
|
+
throw new exit_1.CliError(`Could not fetch branch ${branch} from ${vaultUrl}.`, exit_1.EXIT_FAIL);
|
|
90
|
+
}
|
|
91
|
+
console.log(`Fetching ${upstream.label} @ ${branch}`);
|
|
92
|
+
if ((await (0, gitrun_1.run)('git', ['-C', tmp, 'fetch', '--quiet', upstream.url, branch])) !== 0) {
|
|
93
|
+
throw new exit_1.CliError(`Could not fetch branch ${branch} from ${upstream.url}.`, exit_1.EXIT_FAIL);
|
|
94
|
+
}
|
|
95
|
+
const count = async (range) => {
|
|
96
|
+
const out = await (0, gitrun_1.capture)('git', ['-C', tmp, 'rev-list', '--count', range]);
|
|
97
|
+
if (out === null)
|
|
98
|
+
throw new exit_1.CliError('git rev-list failed in the temporary clone.', exit_1.EXIT_FAIL);
|
|
99
|
+
return parseInt(out.trim(), 10);
|
|
100
|
+
};
|
|
101
|
+
const behind = await count(`refs/heads/${branch}..FETCH_HEAD`);
|
|
102
|
+
const ahead = await count(`FETCH_HEAD..refs/heads/${branch}`);
|
|
103
|
+
if (behind === 0) {
|
|
104
|
+
console.log(ahead === 0
|
|
105
|
+
? `${branch} is up to date with ${upstream.label}.`
|
|
106
|
+
: `${branch} is up to date with ${upstream.label}, and ${ahead} commit${ahead === 1 ? '' : 's'} ahead of it.`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (ahead > 0) {
|
|
110
|
+
throw new exit_1.CliError(`${branch} has diverged from ${upstream.label}: ${ahead} commit${ahead === 1 ? '' : 's'} of its own, ` +
|
|
111
|
+
`${behind} upstream. Nothing was pushed; merge or rebase by hand in a clone.`, exit_1.EXIT_CONFLICT);
|
|
112
|
+
}
|
|
113
|
+
console.log(`Pushing ${behind} commit${behind === 1 ? '' : 's'} to ${vaultUrl}`);
|
|
114
|
+
if ((await (0, gitrun_1.run)('git', ['-C', tmp, ...(0, gitrun_1.mochiCredentialArgs)(), 'push', '--quiet', vaultUrl, `FETCH_HEAD:refs/heads/${branch}`], env)) !== 0) {
|
|
115
|
+
throw new exit_1.CliError(`Could not push ${branch} to ${vaultUrl}.`, exit_1.EXIT_FAIL);
|
|
116
|
+
}
|
|
117
|
+
console.log(`Fast-forwarded ${branch} by ${behind} commit${behind === 1 ? '' : 's'} from ${upstream.label}.`);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
try {
|
|
121
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// A leftover temporary directory is not worth failing the sync over.
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
};
|
package/dist/cli/target.js
CHANGED
|
@@ -3,23 +3,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TARGET_OPTIONS = void 0;
|
|
4
4
|
exports.targetFrom = targetFrom;
|
|
5
5
|
const cli_api_1 = require("../cli-api");
|
|
6
|
+
const naming_1 = require("../naming");
|
|
6
7
|
const exit_1 = require("./exit");
|
|
7
8
|
const input_1 = require("./input");
|
|
8
9
|
// Which vault a command talks to, and with what token. Every command that
|
|
9
10
|
// reaches a vault takes the same three options, declared once here so that they
|
|
10
11
|
// are spelled and documented identically everywhere.
|
|
12
|
+
// The summaries read naming at module load, which is after a sibling
|
|
13
|
+
// application's branding module has run (it is that application's first
|
|
14
|
+
// import), and is simply the mochi defaults everywhere else.
|
|
11
15
|
exports.TARGET_OPTIONS = [
|
|
12
16
|
{
|
|
13
17
|
name: 'host',
|
|
14
18
|
type: 'string',
|
|
15
19
|
value: '<url>',
|
|
16
|
-
summary:
|
|
20
|
+
summary: `${naming_1.naming.rootNoun[0].toUpperCase()}${naming_1.naming.rootNoun.slice(1)} URL, ahead of ${naming_1.naming.envPrefix}_HOST and the last login`,
|
|
17
21
|
},
|
|
18
22
|
{
|
|
19
23
|
name: 'token',
|
|
20
24
|
type: 'string',
|
|
21
25
|
value: '<t>',
|
|
22
|
-
summary:
|
|
26
|
+
summary: `Token, ahead of ${naming_1.naming.envPrefix}_TOKEN and git's credential store`,
|
|
23
27
|
},
|
|
24
28
|
{
|
|
25
29
|
name: 'token-stdin',
|
|
@@ -49,6 +53,6 @@ async function targetFrom(inv, opts = {}) {
|
|
|
49
53
|
if (!token)
|
|
50
54
|
throw new exit_1.CliError('--token-stdin was given but stdin was empty.', exit_1.EXIT_AUTH);
|
|
51
55
|
}
|
|
52
|
-
const host = inv.str('host') ?? process.env.
|
|
56
|
+
const host = inv.str('host') ?? process.env[`${naming_1.naming.envPrefix}_HOST`]?.trim() ?? opts.host ?? null;
|
|
53
57
|
return await (0, cli_api_1.remoteTarget)({ host, token });
|
|
54
58
|
}
|
package/dist/config.js
CHANGED
|
@@ -67,6 +67,7 @@ function configFilePath(root) {
|
|
|
67
67
|
}
|
|
68
68
|
const DEFAULTS = {
|
|
69
69
|
theme: themes_1.DEFAULT_THEME,
|
|
70
|
+
auth: { githubClientId: '' },
|
|
70
71
|
ci: { runs: 100, days: 0, artifactMb: 500 },
|
|
71
72
|
sites: { host: '' },
|
|
72
73
|
network: { trustProxy: false },
|
|
@@ -78,6 +79,7 @@ const DEFAULTS = {
|
|
|
78
79
|
function defaults() {
|
|
79
80
|
return {
|
|
80
81
|
...DEFAULTS,
|
|
82
|
+
auth: { ...DEFAULTS.auth },
|
|
81
83
|
ci: { ...DEFAULTS.ci },
|
|
82
84
|
sites: { ...DEFAULTS.sites },
|
|
83
85
|
network: { ...DEFAULTS.network },
|
|
@@ -96,6 +98,14 @@ function readConfig(file) {
|
|
|
96
98
|
// request: a typo in config.json should not take the vault down.
|
|
97
99
|
if (typeof parsed.theme === 'string' && (0, themes_1.findTheme)(parsed.theme))
|
|
98
100
|
config.theme = parsed.theme;
|
|
101
|
+
// The client id is public and its exact shape is GitHub's business; the
|
|
102
|
+
// check refuses only whitespace and control characters, which could not
|
|
103
|
+
// be one and would otherwise ride into a redirect URL.
|
|
104
|
+
if (typeof parsed.auth === 'object' && parsed.auth !== null) {
|
|
105
|
+
const auth = parsed.auth;
|
|
106
|
+
const id = typeof auth.githubClientId === 'string' ? auth.githubClientId.trim() : '';
|
|
107
|
+
config.auth = { githubClientId: /^[\x21-\x7e]{1,100}$/.test(id) ? id : DEFAULTS.auth.githubClientId };
|
|
108
|
+
}
|
|
99
109
|
// Run retention: a vault's run history is the one part of its state that
|
|
100
110
|
// grows without bound, so it is bounded by default and tunable here.
|
|
101
111
|
if (typeof parsed.ci === 'object' && parsed.ci !== null) {
|
package/dist/credentials.js
CHANGED
|
@@ -49,6 +49,7 @@ const child_process_1 = require("child_process");
|
|
|
49
49
|
const fs = __importStar(require("fs"));
|
|
50
50
|
const os = __importStar(require("os"));
|
|
51
51
|
const path = __importStar(require("path"));
|
|
52
|
+
const naming_1 = require("./naming");
|
|
52
53
|
// The client side of authentication. A token is the password git sends over
|
|
53
54
|
// Basic auth, so the place to keep it is git's own credential store: clone,
|
|
54
55
|
// fetch, push, and git-lfs all read credentials through the same `git
|
|
@@ -171,7 +172,7 @@ async function rejectCredential(target, username) {
|
|
|
171
172
|
// makes `mochi user list` work with no arguments and no environment.
|
|
172
173
|
function loginPath() {
|
|
173
174
|
const base = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), '.config');
|
|
174
|
-
return path.join(base,
|
|
175
|
+
return path.join(base, naming_1.naming.configDirName, 'login.json');
|
|
175
176
|
}
|
|
176
177
|
function loadLogin(file = loginPath()) {
|
|
177
178
|
try {
|
|
@@ -212,13 +213,13 @@ function clearLogin(host, file = loginPath()) {
|
|
|
212
213
|
// configures, and the runner already took this shape with
|
|
213
214
|
// MOCHI_RUNNER_TOKEN.
|
|
214
215
|
async function vaultTarget(args) {
|
|
215
|
-
const envHost = process.env.
|
|
216
|
-
const envToken = process.env.
|
|
216
|
+
const envHost = process.env[`${naming_1.naming.envPrefix}_HOST`]?.trim() || null;
|
|
217
|
+
const envToken = process.env[`${naming_1.naming.envPrefix}_TOKEN`]?.trim() || null;
|
|
217
218
|
const host = (args.host ?? envHost ?? loadLogin()?.host ?? '').replace(/\/+$/, '');
|
|
218
219
|
if (!host) {
|
|
219
|
-
throw new CredentialError(
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
throw new CredentialError(`No ${naming_1.naming.rootNoun}. Log in to one first:\n\n` +
|
|
221
|
+
` ${naming_1.naming.product} login https://${naming_1.naming.rootNoun}.example.com\n\n` +
|
|
222
|
+
`or pass --host <url>, or set ${naming_1.naming.envPrefix}_HOST.`);
|
|
222
223
|
}
|
|
223
224
|
if (args.token)
|
|
224
225
|
return { host, token: args.token };
|
|
@@ -228,8 +229,8 @@ async function vaultTarget(args) {
|
|
|
228
229
|
const stored = await readCredential(target);
|
|
229
230
|
if (!stored) {
|
|
230
231
|
throw new CredentialError(`No stored token for ${target.url}. Log in again:\n\n` +
|
|
231
|
-
`
|
|
232
|
-
|
|
232
|
+
` ${naming_1.naming.product} login ${target.url}\n\n` +
|
|
233
|
+
`or pass --token <token>, or set ${naming_1.naming.envPrefix}_TOKEN.`);
|
|
233
234
|
}
|
|
234
235
|
return { host, token: stored.password };
|
|
235
236
|
}
|