@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
package/dist/issues.js ADDED
@@ -0,0 +1,308 @@
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.ISSUE_SORTS = exports.MAX_LABELS = exports.MAX_TITLE = exports.MAX_BODY = void 0;
37
+ exports.issuesDir = issuesDir;
38
+ exports.listIssues = listIssues;
39
+ exports.listIssuesAndMatches = listIssuesAndMatches;
40
+ exports.selectIssues = selectIssues;
41
+ exports.labelsInUse = labelsInUse;
42
+ exports.authorsInUse = authorsInUse;
43
+ exports.issueCounts = issueCounts;
44
+ exports.readIssue = readIssue;
45
+ exports.checkLabels = checkLabels;
46
+ exports.createIssue = createIssue;
47
+ exports.addComment = addComment;
48
+ exports.setIssueState = setIssueState;
49
+ exports.editIssue = editIssue;
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const discussion_1 = require("./discussion");
53
+ const ops_1 = require("./ops");
54
+ const layout_1 = require("./layout");
55
+ const scan_1 = require("./scan");
56
+ var discussion_2 = require("./discussion");
57
+ Object.defineProperty(exports, "MAX_BODY", { enumerable: true, get: function () { return discussion_2.MAX_BODY; } });
58
+ Object.defineProperty(exports, "MAX_TITLE", { enumerable: true, get: function () { return discussion_2.MAX_TITLE; } });
59
+ exports.MAX_LABELS = 10;
60
+ /** The issues directory for a repository, whether or not it exists yet. */
61
+ function issuesDir(root, collection, repo) {
62
+ if (!(0, scan_1.isValidName)(collection) || !(0, scan_1.isValidName)(repo))
63
+ return null;
64
+ return (0, layout_1.repoPath)(root, collection, `${(0, scan_1.displayName)(repo)}.issues`);
65
+ }
66
+ const ISSUE = {
67
+ doc: 'issue.md',
68
+ indefinite: 'an issue',
69
+ Indefinite: 'An issue',
70
+ Bare: 'Issue',
71
+ dirFor: issuesDir,
72
+ };
73
+ /** Move the issues directory's timestamp, invalidating the count cache. */
74
+ function touchIssues(root, collection, repo) {
75
+ const dir = issuesDir(root, collection, repo);
76
+ if (dir)
77
+ (0, discussion_1.touch)(dir);
78
+ }
79
+ function issueDir(root, collection, repo, n) {
80
+ return (0, discussion_1.itemDir)(ISSUE, root, collection, repo, n);
81
+ }
82
+ function labelsOf(v) {
83
+ if (Array.isArray(v))
84
+ return v.filter((x) => typeof x === 'string');
85
+ if (typeof v === 'string')
86
+ return v.split(',').map((s) => s.trim()).filter((s) => s !== '');
87
+ return [];
88
+ }
89
+ function summaryFrom(n, dir, doc) {
90
+ const created = (0, discussion_1.str)(doc.meta.created);
91
+ return {
92
+ number: n,
93
+ title: (0, discussion_1.str)(doc.meta.title, `Issue ${n}`),
94
+ state: doc.meta.state === 'closed' ? 'closed' : 'open',
95
+ author: (0, discussion_1.str)(doc.meta.author, 'unknown'),
96
+ created,
97
+ updated: (0, discussion_1.str)(doc.meta.updated, created),
98
+ labels: labelsOf(doc.meta.labels),
99
+ comments: (0, discussion_1.countComments)(dir),
100
+ closedBy: doc.meta.closedBy === undefined ? undefined : (0, discussion_1.str)(doc.meta.closedBy),
101
+ closedAt: doc.meta.closedAt === undefined ? undefined : (0, discussion_1.str)(doc.meta.closedAt),
102
+ };
103
+ }
104
+ /**
105
+ * Every issue in a repository, newest first. A `match` narrows the list while
106
+ * the files are being read, which is where the body is in hand: searching
107
+ * titles alone would miss the issue whose subject is in its first paragraph,
108
+ * and reading every file twice to find it would be silly.
109
+ */
110
+ /** Read every issue once, handing each to `visit` with its body. */
111
+ function scanIssues(root, collection, repo, visit) {
112
+ const dir = issuesDir(root, collection, repo);
113
+ if (!dir)
114
+ return;
115
+ for (const n of (0, discussion_1.numericDirs)(dir)) {
116
+ const sub = path.join(dir, String(n));
117
+ const doc = (0, discussion_1.readDoc)(path.join(sub, 'issue.md'));
118
+ if (!doc)
119
+ continue;
120
+ visit(summaryFrom(n, sub, doc), doc.body);
121
+ }
122
+ }
123
+ /** Whether a reader's query, already lowercased and trimmed, finds an issue. */
124
+ function matchesQuery(summary, body, needle) {
125
+ if (needle === '')
126
+ return true;
127
+ return (summary.title.toLowerCase().includes(needle) ||
128
+ body.toLowerCase().includes(needle) ||
129
+ summary.labels.some((l) => l.toLowerCase().includes(needle)));
130
+ }
131
+ // Newest first, which is how a list of issues is read.
132
+ const newestFirst = (a, b) => b.number - a.number;
133
+ function listIssues(root, collection, repo, opts = {}) {
134
+ const needle = (opts.match ?? '').trim().toLowerCase();
135
+ const out = [];
136
+ scanIssues(root, collection, repo, (summary, body) => {
137
+ if (matchesQuery(summary, body, needle))
138
+ out.push(summary);
139
+ });
140
+ return out.sort(newestFirst);
141
+ }
142
+ /**
143
+ * Every issue, and the ones a query finds, from one pass over the directory.
144
+ *
145
+ * The list page needs both, because the tab counts and the label and author
146
+ * menus are of the whole repository while the rows are of the query. Asking
147
+ * listIssues twice read and YAML-parsed every issue twice -- and that parse is
148
+ * nine tenths of what the page costs -- so the query is applied here instead,
149
+ * while the issue is already in hand. No body is kept afterwards.
150
+ */
151
+ function listIssuesAndMatches(root, collection, repo, query) {
152
+ const needle = query.trim().toLowerCase();
153
+ const all = [];
154
+ const matched = [];
155
+ scanIssues(root, collection, repo, (summary, body) => {
156
+ all.push(summary);
157
+ if (matchesQuery(summary, body, needle))
158
+ matched.push(summary);
159
+ });
160
+ return { all: all.sort(newestFirst), matched: matched.sort(newestFirst) };
161
+ }
162
+ exports.ISSUE_SORTS = [
163
+ { key: 'newest', label: 'Newest' },
164
+ { key: 'oldest', label: 'Oldest' },
165
+ { key: 'updated', label: 'Recently updated' },
166
+ { key: 'comments', label: 'Most commented' },
167
+ ];
168
+ /** Narrow and order a list that has already been read. */
169
+ function selectIssues(list, opts) {
170
+ const state = opts.state ?? 'open';
171
+ const out = list.filter((i) => (state === 'all' || i.state === state) &&
172
+ (!opts.label || i.labels.includes(opts.label)) &&
173
+ (!opts.author || i.author === opts.author));
174
+ const by = {
175
+ newest: (a, b) => b.number - a.number,
176
+ oldest: (a, b) => a.number - b.number,
177
+ updated: (a, b) => (a.updated < b.updated ? 1 : a.updated > b.updated ? -1 : 0),
178
+ comments: (a, b) => b.comments - a.comments || b.number - a.number,
179
+ };
180
+ return out.sort(by[opts.sort ?? 'newest']);
181
+ }
182
+ /** Every label in use, and how many issues carry it. */
183
+ function labelsInUse(list) {
184
+ const counts = new Map();
185
+ for (const issue of list) {
186
+ for (const label of issue.labels)
187
+ counts.set(label, (counts.get(label) ?? 0) + 1);
188
+ }
189
+ return [...counts.entries()]
190
+ .map(([label, count]) => ({ label, count }))
191
+ .sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
192
+ }
193
+ /** Everyone who has opened an issue, most prolific first. */
194
+ function authorsInUse(list) {
195
+ const counts = new Map();
196
+ for (const issue of list)
197
+ counts.set(issue.author, (counts.get(issue.author) ?? 0) + 1);
198
+ return [...counts.entries()]
199
+ .map(([author, count]) => ({ author, count }))
200
+ .sort((a, b) => b.count - a.count || a.author.localeCompare(b.author));
201
+ }
202
+ // Counting open issues means reading every issue's header, and the Issues tab
203
+ // asks for that count on every page of a repository. So the answer is
204
+ // memoized against the issues directory's modification time, and every write
205
+ // below touches that directory - including a state change inside a
206
+ // subdirectory, which would not otherwise move it. One read per change rather
207
+ // than one per page view, and a vault with no issues never pays anything.
208
+ const countMemo = new Map();
209
+ /** Open and closed counts, for the tab and the list's filter. */
210
+ function issueCounts(root, collection, repo) {
211
+ const dir = issuesDir(root, collection, repo);
212
+ if (!dir)
213
+ return { open: 0, closed: 0 };
214
+ let mtimeMs;
215
+ try {
216
+ mtimeMs = fs.statSync(dir).mtimeMs;
217
+ }
218
+ catch {
219
+ return { open: 0, closed: 0 };
220
+ }
221
+ const cached = countMemo.get(dir);
222
+ if (cached && cached.mtimeMs === mtimeMs)
223
+ return cached.counts;
224
+ const counts = { open: 0, closed: 0 };
225
+ for (const issue of listIssues(root, collection, repo)) {
226
+ if (issue.state === 'open')
227
+ counts.open++;
228
+ else
229
+ counts.closed++;
230
+ }
231
+ if (countMemo.size > 2000)
232
+ countMemo.clear();
233
+ countMemo.set(dir, { mtimeMs, counts });
234
+ return counts;
235
+ }
236
+ function readIssue(root, collection, repo, n) {
237
+ const dir = issueDir(root, collection, repo, n);
238
+ if (!dir)
239
+ return null;
240
+ const doc = (0, discussion_1.readDoc)(path.join(dir, 'issue.md'));
241
+ if (!doc)
242
+ return null;
243
+ return { ...summaryFrom(n, dir, doc), body: doc.body, commentList: (0, discussion_1.readComments)(dir) };
244
+ }
245
+ function checkLabels(labels) {
246
+ const out = [];
247
+ for (const raw of labels) {
248
+ const label = raw.trim();
249
+ if (label === '')
250
+ continue;
251
+ if (!/^[A-Za-z0-9][A-Za-z0-9 ._-]{0,29}$/.test(label))
252
+ throw new ops_1.OpError(`"${label}" is not a usable label.`);
253
+ if (!out.includes(label))
254
+ out.push(label);
255
+ }
256
+ if (out.length > exports.MAX_LABELS)
257
+ throw new ops_1.OpError(`An issue may carry at most ${exports.MAX_LABELS} labels.`);
258
+ return out;
259
+ }
260
+ function createIssue(root, collection, repo, input) {
261
+ const dir = issuesDir(root, collection, repo);
262
+ if (!dir)
263
+ throw new ops_1.OpError('invalid collection or repository name');
264
+ const title = (0, discussion_1.checkTitle)(ISSUE, input.title);
265
+ const body = (0, discussion_1.checkBody)(ISSUE, input.body);
266
+ const labels = checkLabels(input.labels ?? []);
267
+ const now = new Date().toISOString();
268
+ const n = (0, discussion_1.allocate)(ISSUE, dir, (sub) => (0, discussion_1.writeDoc)(path.join(sub, 'issue.md'), { title, state: 'open', author: input.author, created: now, updated: now, labels }, body));
269
+ return {
270
+ number: n,
271
+ title,
272
+ state: 'open',
273
+ author: input.author,
274
+ created: now,
275
+ updated: now,
276
+ labels,
277
+ comments: 0,
278
+ };
279
+ }
280
+ function addComment(root, collection, repo, n, input) {
281
+ return (0, discussion_1.addComment)(ISSUE, root, collection, repo, n, input);
282
+ }
283
+ function setIssueState(root, collection, repo, n, state, actor) {
284
+ const { dir, doc } = (0, discussion_1.openFor)(ISSUE, root, collection, repo, n);
285
+ const now = new Date().toISOString();
286
+ const meta = { ...doc.meta, state, updated: now };
287
+ if (state === 'closed') {
288
+ meta.closedBy = actor;
289
+ meta.closedAt = now;
290
+ }
291
+ else {
292
+ delete meta.closedBy;
293
+ delete meta.closedAt;
294
+ }
295
+ (0, discussion_1.writeDoc)(path.join(dir, 'issue.md'), meta, doc.body);
296
+ touchIssues(root, collection, repo);
297
+ }
298
+ function editIssue(root, collection, repo, n, input) {
299
+ const { dir, doc } = (0, discussion_1.openFor)(ISSUE, root, collection, repo, n);
300
+ const meta = { ...doc.meta, updated: new Date().toISOString() };
301
+ if (input.title !== undefined)
302
+ meta.title = (0, discussion_1.checkTitle)(ISSUE, input.title);
303
+ if (input.labels !== undefined)
304
+ meta.labels = checkLabels(input.labels);
305
+ const body = input.body === undefined ? doc.body : (0, discussion_1.checkBody)(ISSUE, input.body);
306
+ (0, discussion_1.writeDoc)(path.join(dir, 'issue.md'), meta, body);
307
+ touchIssues(root, collection, repo);
308
+ }