@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.
Files changed (116) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +108 -0
  3. package/dist/ansi.js +174 -0
  4. package/dist/api/admin.js +416 -0
  5. package/dist/api/auth.js +166 -0
  6. package/dist/api/backup.js +598 -0
  7. package/dist/api/ci.js +336 -0
  8. package/dist/api/contents.js +339 -0
  9. package/dist/api/issues.js +165 -0
  10. package/dist/api/pulls.js +244 -0
  11. package/dist/api/releases.js +83 -0
  12. package/dist/api/repos.js +156 -0
  13. package/dist/api/write.js +518 -0
  14. package/dist/api.js +326 -0
  15. package/dist/assets.js +29 -0
  16. package/dist/atom.js +32 -0
  17. package/dist/atomic.js +171 -0
  18. package/dist/avatar.js +81 -0
  19. package/dist/browse.js +630 -0
  20. package/dist/build-info.json +4 -0
  21. package/dist/ci/actionref.js +86 -0
  22. package/dist/ci/api.js +829 -0
  23. package/dist/ci/artifacts.js +201 -0
  24. package/dist/ci/dispatch.js +30 -0
  25. package/dist/ci/engine.js +1321 -0
  26. package/dist/ci/expr.js +526 -0
  27. package/dist/ci/manual.js +199 -0
  28. package/dist/ci/present.js +82 -0
  29. package/dist/ci/protocol.js +6 -0
  30. package/dist/ci/runners.js +256 -0
  31. package/dist/ci/runs.js +208 -0
  32. package/dist/ci/trigger.js +28 -0
  33. package/dist/ci/views.js +441 -0
  34. package/dist/ci/wake.js +194 -0
  35. package/dist/ci/web.js +617 -0
  36. package/dist/ci/workflow.js +436 -0
  37. package/dist/cli/admin-cmd.js +324 -0
  38. package/dist/cli/api-cmd.js +128 -0
  39. package/dist/cli/backup-cmd.js +1500 -0
  40. package/dist/cli/exit.js +69 -0
  41. package/dist/cli/input.js +64 -0
  42. package/dist/cli/issue-cmd.js +243 -0
  43. package/dist/cli/output.js +93 -0
  44. package/dist/cli/parse.js +317 -0
  45. package/dist/cli/pr-cmd.js +289 -0
  46. package/dist/cli/release-cmd.js +171 -0
  47. package/dist/cli/repo-cmd.js +763 -0
  48. package/dist/cli/repo.js +101 -0
  49. package/dist/cli/run-cmd.js +438 -0
  50. package/dist/cli/target.js +54 -0
  51. package/dist/cli-api.js +84 -0
  52. package/dist/compare.js +111 -0
  53. package/dist/config.js +212 -0
  54. package/dist/credentials.js +235 -0
  55. package/dist/deploy-cli.js +859 -0
  56. package/dist/deploy-runner-cli.js +592 -0
  57. package/dist/diff.js +171 -0
  58. package/dist/discussion.js +253 -0
  59. package/dist/egress.js +559 -0
  60. package/dist/filecache.js +68 -0
  61. package/dist/find.js +162 -0
  62. package/dist/forms.js +737 -0
  63. package/dist/git.js +547 -0
  64. package/dist/githttp.js +428 -0
  65. package/dist/html.js +87 -0
  66. package/dist/icons.js +101 -0
  67. package/dist/import-cli.js +316 -0
  68. package/dist/index.js +752 -0
  69. package/dist/issues.js +308 -0
  70. package/dist/issueweb.js +447 -0
  71. package/dist/job-cli.js +197 -0
  72. package/dist/jobtoken.js +96 -0
  73. package/dist/languages.js +383 -0
  74. package/dist/layout.js +100 -0
  75. package/dist/lfs.js +438 -0
  76. package/dist/lfsstore.js +425 -0
  77. package/dist/limit.js +259 -0
  78. package/dist/logo.js +61 -0
  79. package/dist/markdown.js +382 -0
  80. package/dist/migrate.js +334 -0
  81. package/dist/multipart.js +90 -0
  82. package/dist/ops.js +869 -0
  83. package/dist/pagescript.js +465 -0
  84. package/dist/perms.js +370 -0
  85. package/dist/pointer.js +55 -0
  86. package/dist/profile.js +106 -0
  87. package/dist/pulls.js +320 -0
  88. package/dist/pullweb.js +461 -0
  89. package/dist/redirects.js +455 -0
  90. package/dist/releases.js +435 -0
  91. package/dist/render.js +233 -0
  92. package/dist/runner/actions.js +448 -0
  93. package/dist/runner/client.js +428 -0
  94. package/dist/runner/context.js +247 -0
  95. package/dist/runner/docker.js +197 -0
  96. package/dist/runner/externals.js +175 -0
  97. package/dist/runner/job.js +290 -0
  98. package/dist/runner/manual-run.js +272 -0
  99. package/dist/runner/overrides.js +554 -0
  100. package/dist/runner/steps.js +571 -0
  101. package/dist/runner/wake.js +84 -0
  102. package/dist/runner-cli.js +405 -0
  103. package/dist/scan.js +231 -0
  104. package/dist/server.js +424 -0
  105. package/dist/session.js +267 -0
  106. package/dist/site.js +259 -0
  107. package/dist/siteshost.js +94 -0
  108. package/dist/source.js +90 -0
  109. package/dist/style.js +1295 -0
  110. package/dist/themes.js +369 -0
  111. package/dist/vault.js +442 -0
  112. package/dist/version.js +88 -0
  113. package/dist/views.js +1007 -0
  114. package/dist/web.js +182 -0
  115. package/dist/webops.js +1402 -0
  116. package/package.json +71 -0
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ // Exit codes and the error type that carries them. A person reads the message
3
+ // on stderr; a program reads the code, so the codes are part of the CLI's
4
+ // contract and are documented in docs/cli.md and docs/agents.md. The two that
5
+ // pay for themselves are 4 and 5: "does this exist" and "did someone else get
6
+ // there first" are the questions a retrying caller asks, and both used to be
7
+ // indistinguishable from every other failure.
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.UnreachableError = exports.CliError = exports.EXIT_CONFLICT = exports.EXIT_NOTFOUND = exports.EXIT_AUTH = exports.EXIT_USAGE = exports.EXIT_FAIL = exports.EXIT_OK = void 0;
10
+ exports.exitCodeForStatus = exitCodeForStatus;
11
+ exports.setJsonErrors = setJsonErrors;
12
+ exports.jsonErrorsWanted = jsonErrorsWanted;
13
+ exports.EXIT_OK = 0;
14
+ /** Generic failure, including a 4xx or 5xx from the vault with no code of its own. */
15
+ exports.EXIT_FAIL = 1;
16
+ /** Unknown command, unknown flag, missing argument. */
17
+ exports.EXIT_USAGE = 2;
18
+ /** No token, or the vault rejected the one given. */
19
+ exports.EXIT_AUTH = 3;
20
+ /** The vault answered 404 for the addressed resource. */
21
+ exports.EXIT_NOTFOUND = 4;
22
+ /** The vault answered 409, or an operation was refused because of state. */
23
+ exports.EXIT_CONFLICT = 5;
24
+ class CliError extends Error {
25
+ code;
26
+ constructor(message, code = exports.EXIT_FAIL) {
27
+ super(message);
28
+ this.code = code;
29
+ }
30
+ }
31
+ exports.CliError = CliError;
32
+ /**
33
+ * The vault could not be reached at all: DNS, the connection, or the TLS
34
+ * handshake, rather than anything the vault said. It is an ordinary failure to
35
+ * anything that just reports it, but a polling command wants to tell it apart,
36
+ * because one dropped request in the middle of a watch is not a reason to
37
+ * abandon a run that is still going.
38
+ */
39
+ class UnreachableError extends CliError {
40
+ constructor(message) {
41
+ super(message, exports.EXIT_FAIL);
42
+ }
43
+ }
44
+ exports.UnreachableError = UnreachableError;
45
+ /**
46
+ * The exit code an HTTP status from the vault maps to. 403 is deliberately a
47
+ * generic failure rather than 3: the token was accepted and is simply not
48
+ * allowed to do this, which is not something a caller fixes by logging in
49
+ * again.
50
+ */
51
+ function exitCodeForStatus(status) {
52
+ if (status === 401)
53
+ return exports.EXIT_AUTH;
54
+ if (status === 404)
55
+ return exports.EXIT_NOTFOUND;
56
+ if (status === 409)
57
+ return exports.EXIT_CONFLICT;
58
+ return exports.EXIT_FAIL;
59
+ }
60
+ // Whether this run asked for JSON. Set by the parser, because a failure can
61
+ // happen before the command reaches its own output code, and a caller that
62
+ // passed --json expects {"error": "..."} either way.
63
+ let jsonRequested = false;
64
+ function setJsonErrors(on) {
65
+ jsonRequested = on;
66
+ }
67
+ function jsonErrorsWanted() {
68
+ return jsonRequested;
69
+ }
@@ -0,0 +1,64 @@
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.readStdin = readStdin;
37
+ exports.readFileArg = readFileArg;
38
+ const fs = __importStar(require("fs"));
39
+ const exit_1 = require("./exit");
40
+ // Reading input that is too long, too secret, or too awkward to be an argument.
41
+ // A token in argv is in the process table and in shell history; an issue body
42
+ // in argv is subject to whatever the shell does to it and to ARG_MAX. Both are
43
+ // read from a file or from stdin instead.
44
+ /** Everything on stdin, as text. */
45
+ function readStdin() {
46
+ return new Promise((resolve, reject) => {
47
+ const chunks = [];
48
+ process.stdin.on('data', (c) => chunks.push(c));
49
+ process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
50
+ process.stdin.on('error', reject);
51
+ process.stdin.resume();
52
+ });
53
+ }
54
+ /** A file's contents, or stdin when the path is '-'. */
55
+ async function readFileArg(file) {
56
+ if (file === '-')
57
+ return await readStdin();
58
+ try {
59
+ return fs.readFileSync(file, 'utf8');
60
+ }
61
+ catch (e) {
62
+ throw new exit_1.CliError(`Could not read ${file}: ${e instanceof Error ? e.message : String(e)}`, exit_1.EXIT_USAGE);
63
+ }
64
+ }
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.issueCommands = void 0;
4
+ const cli_api_1 = require("../cli-api");
5
+ const exit_1 = require("./exit");
6
+ const input_1 = require("./input");
7
+ const output_1 = require("./output");
8
+ const repo_1 = require("./repo");
9
+ const target_1 = require("./target");
10
+ // The issue commands, named as gh names them, because that is the point: someone
11
+ // who knows gh should guess right.
12
+ const COMMON = [repo_1.REPO_OPTION, ...target_1.TARGET_OPTIONS];
13
+ const BODY_OPTIONS = [
14
+ { name: 'body', type: 'string', value: '<b>', summary: 'Body text' },
15
+ { name: 'body-file', type: 'string', value: '<f>', summary: "Read the body from a file, or '-' for stdin" },
16
+ ];
17
+ /**
18
+ * The body a command was given. An issue body is frequently longer than a shell
19
+ * argument should be, so --body-file exists alongside --body, and asking for
20
+ * both is a mistake rather than a precedence question.
21
+ */
22
+ async function bodyOf(inv, required = false) {
23
+ const inline = inv.str('body');
24
+ const file = inv.str('body-file');
25
+ if (inline !== null && file !== null)
26
+ throw new exit_1.CliError('Pass either --body or --body-file, not both.', exit_1.EXIT_USAGE);
27
+ if (file !== null)
28
+ return await (0, input_1.readFileArg)(file);
29
+ if (inline !== null)
30
+ return inline;
31
+ if (required)
32
+ throw new exit_1.CliError('A body is required: pass --body <text> or --body-file <path>.', exit_1.EXIT_USAGE);
33
+ return undefined;
34
+ }
35
+ function issueRef(inv) {
36
+ const raw = inv.args[0] ?? '';
37
+ if (!/^[1-9][0-9]*$/.test(raw))
38
+ throw new exit_1.CliError(`'${raw}' is not an issue number`, exit_1.EXIT_USAGE);
39
+ return parseInt(raw, 10);
40
+ }
41
+ exports.issueCommands = [
42
+ {
43
+ path: ['issue', 'list'],
44
+ summary: 'List issues',
45
+ options: [
46
+ { name: 'state', type: 'string', value: '<s>', summary: 'open (default), closed, or all' },
47
+ { name: 'label', type: 'string', value: '<l>', summary: 'Only issues carrying this label' },
48
+ { name: 'author', type: 'string', value: '<a>', summary: 'Only issues opened by this user' },
49
+ { name: 'search', type: 'string', value: '<q>', summary: 'Only issues matching this text' },
50
+ { name: 'sort', type: 'string', value: '<s>', summary: 'newest, oldest, updated, or comments' },
51
+ { name: 'limit', type: 'int', value: '<n>', summary: 'At most this many' },
52
+ output_1.JSON_OPTION,
53
+ ...COMMON,
54
+ ],
55
+ async run(inv) {
56
+ const target = await (0, target_1.targetFrom)(inv);
57
+ const ref = await (0, repo_1.resolveRepo)(inv, target);
58
+ const query = new URLSearchParams();
59
+ for (const [flag, key] of [
60
+ ['state', 'state'],
61
+ ['label', 'label'],
62
+ ['author', 'author'],
63
+ ['sort', 'sort'],
64
+ ['search', 'q'],
65
+ ]) {
66
+ const v = inv.str(flag);
67
+ if (v !== null)
68
+ query.set(key, v);
69
+ }
70
+ const limit = inv.int('limit');
71
+ if (limit !== null)
72
+ query.set('limit', String(limit));
73
+ const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(ref)}/issues?${query.toString()}`);
74
+ const issues = (data.issues ?? []);
75
+ const json = (0, output_1.jsonMode)(inv);
76
+ if (json.enabled) {
77
+ (0, output_1.printJson)({ issues: (0, output_1.pickFields)(issues, json.fields) });
78
+ return;
79
+ }
80
+ if (issues.length === 0) {
81
+ console.log(`No issues in ${ref.collection}/${ref.repo}`);
82
+ return;
83
+ }
84
+ (0, output_1.printTable)(issues.map((i) => [
85
+ `#${i.number}`,
86
+ String(i.state),
87
+ String(i.title),
88
+ String(i.author),
89
+ (0, output_1.shortDate)(i.updated),
90
+ i.labels.join(','),
91
+ ]));
92
+ },
93
+ },
94
+ {
95
+ path: ['issue', 'view'],
96
+ summary: 'Show one issue, with its comments',
97
+ args: [{ name: 'number', required: true }],
98
+ options: [
99
+ { name: 'comments', type: 'boolean', summary: 'Print the comments in full (implied by --json)' },
100
+ output_1.JSON_OPTION,
101
+ ...COMMON,
102
+ ],
103
+ async run(inv) {
104
+ const target = await (0, target_1.targetFrom)(inv);
105
+ const ref = await (0, repo_1.resolveRepo)(inv, target);
106
+ const data = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(ref)}/issues/${issueRef(inv)}`);
107
+ const json = (0, output_1.jsonMode)(inv);
108
+ if (json.enabled) {
109
+ (0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
110
+ return;
111
+ }
112
+ console.log(`#${data.number} ${data.title}`);
113
+ console.log(`${data.state} - opened by ${data.author} on ${(0, output_1.shortDate)(data.created)}`);
114
+ if (data.labels.length)
115
+ console.log(`labels: ${data.labels.join(', ')}`);
116
+ console.log('');
117
+ console.log(String(data.body ?? '').trimEnd());
118
+ const comments = (data.commentList ?? []);
119
+ if (comments.length && inv.bool('comments')) {
120
+ for (const c of comments) {
121
+ console.log('');
122
+ console.log(`--- ${c.author} on ${(0, output_1.shortDate)(c.created)}`);
123
+ console.log(String(c.body ?? '').trimEnd());
124
+ }
125
+ }
126
+ else if (comments.length) {
127
+ console.log('');
128
+ console.log(`(${comments.length} comment${comments.length === 1 ? '' : 's'}; --comments shows them)`);
129
+ }
130
+ },
131
+ },
132
+ {
133
+ path: ['issue', 'create'],
134
+ summary: 'Open an issue',
135
+ options: [
136
+ { name: 'title', type: 'string', value: '<t>', summary: 'Title' },
137
+ ...BODY_OPTIONS,
138
+ { name: 'label', type: 'string[]', value: '<l>', summary: 'Label to attach' },
139
+ output_1.JSON_OPTION,
140
+ ...COMMON,
141
+ ],
142
+ async run(inv) {
143
+ const title = inv.str('title');
144
+ if (title === null)
145
+ throw new exit_1.CliError('--title is required', exit_1.EXIT_USAGE);
146
+ const target = await (0, target_1.targetFrom)(inv);
147
+ const ref = await (0, repo_1.resolveRepo)(inv, target);
148
+ const labels = inv.list('label');
149
+ const data = await (0, cli_api_1.api)(target, 'POST', `${(0, repo_1.repoPath)(ref)}/issues`, {
150
+ title,
151
+ body: (await bodyOf(inv)) ?? '',
152
+ labels: labels.length ? labels : undefined,
153
+ });
154
+ const json = (0, output_1.jsonMode)(inv);
155
+ if (json.enabled) {
156
+ (0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
157
+ return;
158
+ }
159
+ console.log(`${target.host}/${ref.collection}/${ref.repo}/issues/${data.number}`);
160
+ },
161
+ },
162
+ {
163
+ path: ['issue', 'edit'],
164
+ summary: 'Change an issue title, body, or labels',
165
+ description: `--label replaces the whole set, which is what the API does; --add-label keeps what is
166
+ there and adds to it, which means reading the issue first.`,
167
+ args: [{ name: 'number', required: true }],
168
+ options: [
169
+ { name: 'title', type: 'string', value: '<t>', summary: 'New title' },
170
+ ...BODY_OPTIONS,
171
+ { name: 'label', type: 'string[]', value: '<l>', summary: 'Replace the labels with these' },
172
+ { name: 'add-label', type: 'string[]', value: '<l>', summary: 'Keep the existing labels and add these' },
173
+ output_1.JSON_OPTION,
174
+ ...COMMON,
175
+ ],
176
+ async run(inv) {
177
+ const target = await (0, target_1.targetFrom)(inv);
178
+ const ref = await (0, repo_1.resolveRepo)(inv, target);
179
+ const n = issueRef(inv);
180
+ const title = inv.str('title');
181
+ const body = await bodyOf(inv);
182
+ const set = inv.list('label');
183
+ const add = inv.list('add-label');
184
+ if (set.length && add.length)
185
+ throw new exit_1.CliError('Pass either --label or --add-label, not both.', exit_1.EXIT_USAGE);
186
+ let labels = set.length ? set : undefined;
187
+ if (add.length) {
188
+ const current = await (0, cli_api_1.api)(target, 'GET', `${(0, repo_1.repoPath)(ref)}/issues/${n}`);
189
+ labels = [...new Set([...(current.labels ?? []), ...add])];
190
+ }
191
+ if (title === null && body === undefined && labels === undefined) {
192
+ throw new exit_1.CliError('Nothing to change. Pass --title, --body, --body-file, --label, or --add-label.', exit_1.EXIT_USAGE);
193
+ }
194
+ const data = await (0, cli_api_1.api)(target, 'PATCH', `${(0, repo_1.repoPath)(ref)}/issues/${n}`, { title: title ?? undefined, body, labels });
195
+ const json = (0, output_1.jsonMode)(inv);
196
+ if (json.enabled) {
197
+ (0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
198
+ return;
199
+ }
200
+ console.log(`Updated #${n}`);
201
+ },
202
+ },
203
+ {
204
+ path: ['issue', 'comment'],
205
+ summary: 'Add a comment to an issue',
206
+ args: [{ name: 'number', required: true }],
207
+ options: [...BODY_OPTIONS, output_1.JSON_OPTION, ...COMMON],
208
+ async run(inv) {
209
+ const target = await (0, target_1.targetFrom)(inv);
210
+ const ref = await (0, repo_1.resolveRepo)(inv, target);
211
+ const n = issueRef(inv);
212
+ const data = await (0, cli_api_1.api)(target, 'POST', `${(0, repo_1.repoPath)(ref)}/issues/${n}/comments`, {
213
+ body: await bodyOf(inv, true),
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
+ console.log(`Commented on #${n}`);
221
+ },
222
+ },
223
+ ...['close', 'reopen'].map((verb) => ({
224
+ path: ['issue', verb],
225
+ summary: verb === 'close' ? 'Close an issue' : 'Reopen a closed issue',
226
+ args: [{ name: 'number', required: true }],
227
+ options: [output_1.JSON_OPTION, ...COMMON],
228
+ async run(inv) {
229
+ const target = await (0, target_1.targetFrom)(inv);
230
+ const ref = await (0, repo_1.resolveRepo)(inv, target);
231
+ const n = issueRef(inv);
232
+ const data = await (0, cli_api_1.api)(target, 'POST', `${(0, repo_1.repoPath)(ref)}/issues/${n}/state`, {
233
+ state: verb === 'close' ? 'closed' : 'open',
234
+ });
235
+ const json = (0, output_1.jsonMode)(inv);
236
+ if (json.enabled) {
237
+ (0, output_1.printJson)((0, output_1.pickObject)(data, json.fields));
238
+ return;
239
+ }
240
+ console.log(`#${n} is now ${data.state}`);
241
+ },
242
+ })),
243
+ ];
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JSON_OPTION = void 0;
4
+ exports.jsonMode = jsonMode;
5
+ exports.printJson = printJson;
6
+ exports.pickFields = pickFields;
7
+ exports.pickObject = pickObject;
8
+ exports.printTable = printTable;
9
+ exports.shortDate = shortDate;
10
+ const exit_1 = require("./exit");
11
+ // The output contract, which a program depends on more than a person does:
12
+ // --json puts a single JSON value on stdout and nothing else, and every
13
+ // diagnostic goes to stderr, so `mochi issue list --json | jq` never has to
14
+ // filter anything out.
15
+ exports.JSON_OPTION = {
16
+ name: 'json',
17
+ type: 'string?',
18
+ value: '[<fields>]',
19
+ summary: 'JSON on stdout; --json=<a,b> or --json <a,b> keeps only those fields',
20
+ };
21
+ function jsonMode(inv) {
22
+ const v = inv.optional('json');
23
+ if (v === false)
24
+ return { enabled: false, fields: null };
25
+ if (v === true)
26
+ return { enabled: true, fields: null };
27
+ const fields = v
28
+ .split(',')
29
+ .map((f) => f.trim())
30
+ .filter((f) => f.length > 0);
31
+ return { enabled: true, fields: fields.length ? fields : null };
32
+ }
33
+ /** One JSON value on stdout, with a trailing newline and nothing else. */
34
+ function printJson(value) {
35
+ process.stdout.write(JSON.stringify(value, null, 2) + '\n');
36
+ }
37
+ /**
38
+ * Keep only the requested keys. Unknown names are an error naming the valid
39
+ * ones, since silently returning nothing for a misspelled field is the kind of
40
+ * failure a caller does not notice until much later.
41
+ */
42
+ function pickFields(rows, fields) {
43
+ if (!fields)
44
+ return rows;
45
+ const valid = new Set();
46
+ for (const row of rows)
47
+ for (const k of Object.keys(row))
48
+ valid.add(k);
49
+ // An empty result set has no keys to check against, so nothing can be
50
+ // rejected; the filter is then a no-op over no rows anyway.
51
+ if (valid.size > 0) {
52
+ const unknown = fields.filter((f) => !valid.has(f));
53
+ if (unknown.length) {
54
+ throw new exit_1.CliError(`unknown field${unknown.length > 1 ? 's' : ''} ${unknown.join(', ')}. Valid: ${[...valid].sort().join(', ')}`, exit_1.EXIT_USAGE);
55
+ }
56
+ }
57
+ return rows.map((row) => {
58
+ const out = {};
59
+ for (const f of fields)
60
+ if (f in row)
61
+ out[f] = row[f];
62
+ return out;
63
+ });
64
+ }
65
+ /** The same, for a single object rather than a list. */
66
+ function pickObject(row, fields) {
67
+ return pickFields([row], fields)[0];
68
+ }
69
+ /**
70
+ * A plain table for a human reader: columns padded to the widest cell, two
71
+ * spaces between. Nothing here is for a program, which asks for --json instead,
72
+ * so there is no colour, no box drawing, and no terminal width detection.
73
+ */
74
+ function printTable(rows) {
75
+ if (rows.length === 0)
76
+ return;
77
+ const cells = rows.map((r) => r.map((c) => String(c)));
78
+ const widths = [];
79
+ for (const row of cells) {
80
+ row.forEach((c, i) => {
81
+ widths[i] = Math.max(widths[i] ?? 0, c.length);
82
+ });
83
+ }
84
+ for (const row of cells) {
85
+ // The last column is not padded, so a table does not carry trailing spaces.
86
+ const line = row.map((c, i) => (i === row.length - 1 ? c : c.padEnd(widths[i]))).join(' ');
87
+ process.stdout.write(line.replace(/\s+$/, '') + '\n');
88
+ }
89
+ }
90
+ /** An ISO timestamp as a date, which is what a table column has room for. */
91
+ function shortDate(iso) {
92
+ return typeof iso === 'string' && iso.length >= 10 ? iso.slice(0, 10) : '';
93
+ }