@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,272 @@
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.ManualSession = void 0;
37
+ const os = __importStar(require("os"));
38
+ const actions_1 = require("./actions");
39
+ const client_1 = require("./client");
40
+ const docker_1 = require("./docker");
41
+ const externals_1 = require("./externals");
42
+ const job_1 = require("./job");
43
+ // `mochi job run`: the process behind a pasted command. It redeems the
44
+ // command for a session, then takes the run's manual jobs one at a time,
45
+ // showing each job's steps and asking before executing anything, and exits
46
+ // when the run has nothing left for it. Execution itself is the runner's own
47
+ // path (executeSpec), so a job runs here exactly as it would on a registered
48
+ // runner; what differs is how the lease was come by, and that a person
49
+ // approved the steps.
50
+ const ACQUIRE_BACKOFF_MAX = 60_000;
51
+ class ManualSession {
52
+ opts;
53
+ session = '';
54
+ redeemed = null;
55
+ stopping = false;
56
+ polling = null;
57
+ ran = [];
58
+ workDir;
59
+ actions;
60
+ externals;
61
+ constructor(opts) {
62
+ this.opts = opts;
63
+ this.workDir = opts.workDir ?? (0, job_1.defaultWorkDir)();
64
+ const actionCache = opts.cacheDir ? `${opts.cacheDir}/actions` : (0, actions_1.defaultActionCacheDir)();
65
+ const externalsCache = opts.cacheDir ? `${opts.cacheDir}/externals` : (0, externals_1.defaultExternalsDir)();
66
+ this.actions = new actions_1.ActionStore(actionCache, opts.actionsUrl, opts.actionCache);
67
+ this.externals = new externals_1.Externals(externalsCache);
68
+ }
69
+ /** Finish the job in hand, then leave; a second call is the caller's business. */
70
+ stop() {
71
+ this.stopping = true;
72
+ this.polling?.abort();
73
+ }
74
+ /** True when any job this session executed concluded failure. */
75
+ sawFailure() {
76
+ return this.ran.some((r) => r.conclusion === 'failure');
77
+ }
78
+ async redeem() {
79
+ let res;
80
+ try {
81
+ res = await fetch(`${this.opts.host}/api/manual/redeem`, {
82
+ method: 'POST',
83
+ headers: { 'content-type': 'application/json' },
84
+ body: JSON.stringify({ token: this.opts.token, host: os.hostname() }),
85
+ });
86
+ }
87
+ catch (e) {
88
+ throw new Error(`could not reach ${this.opts.host}: ${e instanceof Error ? e.message : String(e)}`);
89
+ }
90
+ if (!res.ok) {
91
+ const body = (await res.json().catch(() => ({})));
92
+ throw new Error(body.error ?? `the server answered ${res.status}`);
93
+ }
94
+ return (await res.json());
95
+ }
96
+ preview(spec) {
97
+ const image = (0, client_1.imageForLabels)({ ...client_1.DEFAULT_IMAGES, ...this.opts.images }, spec.runsOn);
98
+ console.log('');
99
+ console.log(` ${spec.address.collection}/${spec.address.repo} run #${spec.runNumber} job "${spec.name}" at ${spec.sha.slice(0, 8)} on ${spec.ref}`);
100
+ console.log(` image: ${image} (engine: ${(0, docker_1.containerEngine)()})`);
101
+ if (spec.matrix)
102
+ console.log(` matrix: ${JSON.stringify(spec.matrix)}`);
103
+ const steps = spec.steps;
104
+ steps.forEach((s, i) => {
105
+ const n = String(i + 1).padStart(3);
106
+ if (s.uses) {
107
+ console.log(`${n} uses: ${s.uses}${s.name ? ` (${s.name})` : ''}`);
108
+ }
109
+ else if (s.run !== undefined) {
110
+ const lines = s.run.split('\n').filter((l) => l.trim() !== '');
111
+ console.log(`${n} run: ${lines[0] ?? ''}${s.name ? ` (${s.name})` : ''}`);
112
+ for (const extra of lines.slice(1, 20))
113
+ console.log(` ${extra}`);
114
+ if (lines.length > 21)
115
+ console.log(` ... ${lines.length - 21} more lines`);
116
+ }
117
+ else {
118
+ console.log(`${n} (empty step)`);
119
+ }
120
+ });
121
+ console.log('');
122
+ }
123
+ async release(spec) {
124
+ const a = spec.address;
125
+ try {
126
+ await fetch(`${this.opts.host}/api/manual/jobs/${encodeURIComponent(a.collection)}/${encodeURIComponent(a.repo)}/${a.run}/${encodeURIComponent(a.job)}/release`, {
127
+ method: 'POST',
128
+ headers: { authorization: `Bearer ${this.session}`, 'x-mochi-lease': spec.lease },
129
+ });
130
+ }
131
+ catch {
132
+ // The lease expires on its own; a release that could not be delivered
133
+ // costs a minute, not a job... except that an expired manual lease
134
+ // fails the job, so say so rather than leaving it to be discovered.
135
+ console.error(' could not hand the job back; the vault will fail it when the lease expires');
136
+ }
137
+ }
138
+ async acquire() {
139
+ const poll = new AbortController();
140
+ this.polling = poll;
141
+ try {
142
+ const res = await fetch(`${this.opts.host}/api/manual/acquire`, {
143
+ method: 'POST',
144
+ headers: { authorization: `Bearer ${this.session}`, 'content-type': 'application/json' },
145
+ body: JSON.stringify({ job: this.opts.jobFilter ?? undefined }),
146
+ signal: poll.signal,
147
+ });
148
+ if (res.status === 204)
149
+ return { kind: 'wait' };
150
+ if (res.status === 410) {
151
+ return { kind: 'done', body: (await res.json().catch(() => ({}))) };
152
+ }
153
+ if (!res.ok) {
154
+ const body = (await res.json().catch(() => ({})));
155
+ throw new Error(body.error ?? `acquire failed with ${res.status}`);
156
+ }
157
+ return { kind: 'job', spec: (await res.json()) };
158
+ }
159
+ catch (e) {
160
+ if (this.stopping)
161
+ return { kind: 'wait' };
162
+ throw e;
163
+ }
164
+ finally {
165
+ this.polling = null;
166
+ }
167
+ }
168
+ /**
169
+ * Run the session to its end. Returns 0 when everything this session ran
170
+ * succeeded (or it ran nothing), 1 when something it ran failed, so a
171
+ * scripted `--yes` use gets an exit status worth checking.
172
+ */
173
+ async run() {
174
+ const redeemed = await this.redeem();
175
+ this.redeemed = redeemed;
176
+ this.session = redeemed.sessionToken;
177
+ console.log(`mochi job run: ${redeemed.collection}/${redeemed.repo} run #${redeemed.run.number} "${redeemed.run.workflowName}"`);
178
+ console.log(` server: ${this.opts.host}`);
179
+ console.log(` commit: ${redeemed.run.sha.slice(0, 8)} on ${redeemed.run.refName}`);
180
+ console.log(` workdir: ${this.workDir}`);
181
+ const manualJobs = redeemed.jobs;
182
+ const waiting = manualJobs.filter((j) => j.status === 'queued');
183
+ console.log(` manual jobs: ${manualJobs.map((j) => `${j.name}${j.status === 'completed' ? ` (${j.conclusion})` : ''}`).join(', ') || 'none'}`);
184
+ if (this.opts.jobFilter) {
185
+ const matches = manualJobs.some((j) => j.key === this.opts.jobFilter || j.id === this.opts.jobFilter || j.name === this.opts.jobFilter);
186
+ // A glob can match without equalling, so this is a hint, not a refusal;
187
+ // the server's answer is the authority and arrives on the first acquire.
188
+ if (!matches)
189
+ console.log(` note: --job ${this.opts.jobFilter} names none of them exactly; globs are matched by the vault`);
190
+ }
191
+ if (waiting.length === 0)
192
+ console.log(' nothing is waiting right now; jobs may yet become eligible as others finish');
193
+ let backoff = 1000;
194
+ let saidWaiting = false;
195
+ for (;;) {
196
+ if (this.stopping) {
197
+ console.log('Stopped. Unclaimed manual jobs stay waiting; the run page mints a fresh command for them.');
198
+ break;
199
+ }
200
+ let got;
201
+ try {
202
+ got = await this.acquire();
203
+ backoff = 1000;
204
+ }
205
+ catch (e) {
206
+ console.error(` ${e instanceof Error ? e.message : e}; retrying in ${Math.round(backoff / 1000)}s`);
207
+ await new Promise((r) => setTimeout(r, backoff));
208
+ backoff = Math.min(backoff * 2, ACQUIRE_BACKOFF_MAX);
209
+ continue;
210
+ }
211
+ if (got.kind === 'wait') {
212
+ if (!this.stopping && !saidWaiting) {
213
+ console.log(' waiting: the next manual job is blocked on jobs running elsewhere...');
214
+ saidWaiting = true;
215
+ }
216
+ continue;
217
+ }
218
+ if (got.kind === 'done') {
219
+ this.summarize(got.body);
220
+ break;
221
+ }
222
+ saidWaiting = false;
223
+ const spec = got.spec;
224
+ if (!this.opts.yes) {
225
+ this.preview(spec);
226
+ const steps = spec.steps.length;
227
+ const ok = await this.opts.confirm(`Run ${steps === 1 ? 'this step' : `these ${steps} steps`} on ${os.hostname()}? [y/N] `);
228
+ if (!ok) {
229
+ await this.release(spec);
230
+ console.log('Declined; the job goes back to waiting for a command.');
231
+ break;
232
+ }
233
+ }
234
+ const result = await (0, client_1.executeSpec)(spec, {
235
+ host: this.opts.host,
236
+ token: this.session,
237
+ runnerName: `a manual session on ${os.hostname()}`,
238
+ imageFor: (labels) => (0, client_1.imageForLabels)({ ...client_1.DEFAULT_IMAGES, ...this.opts.images }, labels),
239
+ workDir: this.workDir,
240
+ network: this.opts.network,
241
+ actions: this.actions,
242
+ externals: this.externals,
243
+ });
244
+ this.ran.push({ name: spec.name, conclusion: result.conclusion });
245
+ }
246
+ return this.sawFailure() ? 1 : 0;
247
+ }
248
+ summarize(body) {
249
+ console.log('');
250
+ if (this.ran.length > 0) {
251
+ for (const r of this.ran)
252
+ console.log(` ${r.name}: ${r.conclusion}`);
253
+ }
254
+ const run = body.run;
255
+ if (run?.status === 'completed') {
256
+ console.log(`Run #${run.number} finished: ${run.conclusion ?? 'completed'}`);
257
+ }
258
+ else if (body.reason) {
259
+ console.log(`Nothing more for this session: ${body.reason}`);
260
+ if (run)
261
+ console.log(`Run #${run.number} is ${run.status}; other jobs may still be running elsewhere.`);
262
+ }
263
+ else {
264
+ console.log('Nothing more for this session.');
265
+ }
266
+ const n = this.redeemed;
267
+ if (n) {
268
+ console.log(` ${this.opts.host}/${encodeURIComponent(n.collection)}/${encodeURIComponent(n.repo)}/actions/runs/${n.run.number}`);
269
+ }
270
+ }
271
+ }
272
+ exports.ManualSession = ManualSession;