@magland/mochi 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +108 -0
- package/dist/ansi.js +174 -0
- package/dist/api/admin.js +416 -0
- package/dist/api/auth.js +166 -0
- package/dist/api/backup.js +598 -0
- package/dist/api/ci.js +336 -0
- package/dist/api/contents.js +339 -0
- package/dist/api/issues.js +165 -0
- package/dist/api/pulls.js +244 -0
- package/dist/api/releases.js +83 -0
- package/dist/api/repos.js +156 -0
- package/dist/api/write.js +518 -0
- package/dist/api.js +326 -0
- package/dist/assets.js +29 -0
- package/dist/atom.js +32 -0
- package/dist/atomic.js +171 -0
- package/dist/avatar.js +81 -0
- package/dist/browse.js +630 -0
- package/dist/build-info.json +4 -0
- package/dist/ci/actionref.js +86 -0
- package/dist/ci/api.js +829 -0
- package/dist/ci/artifacts.js +201 -0
- package/dist/ci/dispatch.js +30 -0
- package/dist/ci/engine.js +1321 -0
- package/dist/ci/expr.js +526 -0
- package/dist/ci/manual.js +199 -0
- package/dist/ci/present.js +82 -0
- package/dist/ci/protocol.js +6 -0
- package/dist/ci/runners.js +256 -0
- package/dist/ci/runs.js +208 -0
- package/dist/ci/trigger.js +28 -0
- package/dist/ci/views.js +441 -0
- package/dist/ci/wake.js +194 -0
- package/dist/ci/web.js +617 -0
- package/dist/ci/workflow.js +436 -0
- package/dist/cli/admin-cmd.js +324 -0
- package/dist/cli/api-cmd.js +128 -0
- package/dist/cli/backup-cmd.js +1500 -0
- package/dist/cli/exit.js +69 -0
- package/dist/cli/input.js +64 -0
- package/dist/cli/issue-cmd.js +243 -0
- package/dist/cli/output.js +93 -0
- package/dist/cli/parse.js +317 -0
- package/dist/cli/pr-cmd.js +289 -0
- package/dist/cli/release-cmd.js +171 -0
- package/dist/cli/repo-cmd.js +763 -0
- package/dist/cli/repo.js +101 -0
- package/dist/cli/run-cmd.js +438 -0
- package/dist/cli/target.js +54 -0
- package/dist/cli-api.js +84 -0
- package/dist/compare.js +111 -0
- package/dist/config.js +212 -0
- package/dist/credentials.js +235 -0
- package/dist/deploy-cli.js +859 -0
- package/dist/deploy-runner-cli.js +592 -0
- package/dist/diff.js +171 -0
- package/dist/discussion.js +253 -0
- package/dist/egress.js +559 -0
- package/dist/filecache.js +68 -0
- package/dist/find.js +162 -0
- package/dist/forms.js +737 -0
- package/dist/git.js +547 -0
- package/dist/githttp.js +428 -0
- package/dist/html.js +87 -0
- package/dist/icons.js +101 -0
- package/dist/import-cli.js +316 -0
- package/dist/index.js +752 -0
- package/dist/issues.js +308 -0
- package/dist/issueweb.js +447 -0
- package/dist/job-cli.js +197 -0
- package/dist/jobtoken.js +96 -0
- package/dist/languages.js +383 -0
- package/dist/layout.js +100 -0
- package/dist/lfs.js +438 -0
- package/dist/lfsstore.js +425 -0
- package/dist/limit.js +259 -0
- package/dist/logo.js +61 -0
- package/dist/markdown.js +382 -0
- package/dist/migrate.js +334 -0
- package/dist/multipart.js +90 -0
- package/dist/ops.js +869 -0
- package/dist/pagescript.js +465 -0
- package/dist/perms.js +370 -0
- package/dist/pointer.js +55 -0
- package/dist/profile.js +106 -0
- package/dist/pulls.js +320 -0
- package/dist/pullweb.js +461 -0
- package/dist/redirects.js +455 -0
- package/dist/releases.js +435 -0
- package/dist/render.js +233 -0
- package/dist/runner/actions.js +448 -0
- package/dist/runner/client.js +428 -0
- package/dist/runner/context.js +247 -0
- package/dist/runner/docker.js +197 -0
- package/dist/runner/externals.js +175 -0
- package/dist/runner/job.js +290 -0
- package/dist/runner/manual-run.js +272 -0
- package/dist/runner/overrides.js +554 -0
- package/dist/runner/steps.js +571 -0
- package/dist/runner/wake.js +84 -0
- package/dist/runner-cli.js +405 -0
- package/dist/scan.js +231 -0
- package/dist/server.js +424 -0
- package/dist/session.js +267 -0
- package/dist/site.js +259 -0
- package/dist/siteshost.js +94 -0
- package/dist/source.js +90 -0
- package/dist/style.js +1295 -0
- package/dist/themes.js +369 -0
- package/dist/vault.js +442 -0
- package/dist/version.js +88 -0
- package/dist/views.js +1007 -0
- package/dist/web.js +182 -0
- package/dist/webops.js +1402 -0
- package/package.json +71 -0
|
@@ -0,0 +1,448 @@
|
|
|
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.ActionStore = exports.ActionError = void 0;
|
|
37
|
+
exports.parseActionDef = parseActionDef;
|
|
38
|
+
exports.defaultActionCacheDir = defaultActionCacheDir;
|
|
39
|
+
exports.pickCommit = pickCommit;
|
|
40
|
+
exports.inputEnvName = inputEnvName;
|
|
41
|
+
exports.resolveInputs = resolveInputs;
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const YAML = __importStar(require("yaml"));
|
|
47
|
+
const actionref_1 = require("../ci/actionref");
|
|
48
|
+
// Fetching actions and reading their definitions. Actions come from a forge
|
|
49
|
+
// over HTTPS (github.com by default) as a source tarball, and are cached on
|
|
50
|
+
// the runner's disk so a repeated job does not re-download them.
|
|
51
|
+
//
|
|
52
|
+
// Nothing here executes anything: the store hands back a directory and a
|
|
53
|
+
// parsed action.yml, and steps.ts decides what to do with them.
|
|
54
|
+
class ActionError extends Error {
|
|
55
|
+
}
|
|
56
|
+
exports.ActionError = ActionError;
|
|
57
|
+
function isObj(v) {
|
|
58
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
59
|
+
}
|
|
60
|
+
function asString(v) {
|
|
61
|
+
if (typeof v === 'string')
|
|
62
|
+
return v;
|
|
63
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
64
|
+
return String(v);
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
function parseActionDef(source, where) {
|
|
68
|
+
let doc;
|
|
69
|
+
try {
|
|
70
|
+
doc = YAML.parse(source);
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
throw new ActionError(`${where}: could not parse action.yml: ${e instanceof Error ? e.message : e}`);
|
|
74
|
+
}
|
|
75
|
+
if (!isObj(doc))
|
|
76
|
+
throw new ActionError(`${where}: action.yml is not a YAML map`);
|
|
77
|
+
const runsRaw = doc.runs;
|
|
78
|
+
if (!isObj(runsRaw))
|
|
79
|
+
throw new ActionError(`${where}: action.yml has no "runs"`);
|
|
80
|
+
const using = asString(runsRaw.using);
|
|
81
|
+
if (!using)
|
|
82
|
+
throw new ActionError(`${where}: action.yml has no "runs.using"`);
|
|
83
|
+
const inputs = {};
|
|
84
|
+
if (isObj(doc.inputs)) {
|
|
85
|
+
for (const [name, def] of Object.entries(doc.inputs)) {
|
|
86
|
+
const d = isObj(def) ? def : {};
|
|
87
|
+
inputs[name] = {
|
|
88
|
+
default: asString(d.default),
|
|
89
|
+
required: d.required === true,
|
|
90
|
+
description: asString(d.description),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const outputs = {};
|
|
95
|
+
if (isObj(doc.outputs)) {
|
|
96
|
+
for (const [name, def] of Object.entries(doc.outputs)) {
|
|
97
|
+
const d = isObj(def) ? def : {};
|
|
98
|
+
outputs[name] = { value: asString(d.value), description: asString(d.description) };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const name = asString(doc.name);
|
|
102
|
+
const description = asString(doc.description);
|
|
103
|
+
const nodeMatch = using.match(/^node(\d+)$/);
|
|
104
|
+
if (nodeMatch) {
|
|
105
|
+
const main = asString(runsRaw.main);
|
|
106
|
+
if (!main)
|
|
107
|
+
throw new ActionError(`${where}: a ${using} action needs "runs.main"`);
|
|
108
|
+
return {
|
|
109
|
+
name,
|
|
110
|
+
description,
|
|
111
|
+
inputs,
|
|
112
|
+
outputs,
|
|
113
|
+
runs: {
|
|
114
|
+
using: 'node',
|
|
115
|
+
nodeMajor: parseInt(nodeMatch[1], 10),
|
|
116
|
+
main,
|
|
117
|
+
pre: asString(runsRaw.pre),
|
|
118
|
+
preIf: asString(runsRaw['pre-if']),
|
|
119
|
+
post: asString(runsRaw.post),
|
|
120
|
+
postIf: asString(runsRaw['post-if']),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (using === 'composite') {
|
|
125
|
+
const stepsRaw = runsRaw.steps;
|
|
126
|
+
if (!Array.isArray(stepsRaw))
|
|
127
|
+
throw new ActionError(`${where}: a composite action needs "runs.steps"`);
|
|
128
|
+
const steps = stepsRaw.map((s, i) => {
|
|
129
|
+
if (!isObj(s))
|
|
130
|
+
throw new ActionError(`${where}: composite step ${i + 1} is not a map`);
|
|
131
|
+
const step = { env: {} };
|
|
132
|
+
if (isObj(s.env)) {
|
|
133
|
+
for (const [k, v] of Object.entries(s.env)) {
|
|
134
|
+
const val = asString(v);
|
|
135
|
+
if (val !== undefined)
|
|
136
|
+
step.env[k] = val;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
step.id = asString(s.id);
|
|
140
|
+
step.name = asString(s.name);
|
|
141
|
+
step.if = asString(s.if);
|
|
142
|
+
step.run = asString(s.run);
|
|
143
|
+
step.uses = asString(s.uses);
|
|
144
|
+
step.shell = asString(s.shell);
|
|
145
|
+
step.workingDirectory = asString(s['working-directory']);
|
|
146
|
+
step.continueOnError = asString(s['continue-on-error']);
|
|
147
|
+
if (isObj(s.with)) {
|
|
148
|
+
step.with = {};
|
|
149
|
+
for (const [k, v] of Object.entries(s.with)) {
|
|
150
|
+
const val = asString(v);
|
|
151
|
+
if (val !== undefined)
|
|
152
|
+
step.with[k] = val;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (!step.run && !step.uses) {
|
|
156
|
+
throw new ActionError(`${where}: composite step ${i + 1} needs "run" or "uses"`);
|
|
157
|
+
}
|
|
158
|
+
if (step.run && !step.shell) {
|
|
159
|
+
// GitHub requires this and it is worth keeping: a composite whose
|
|
160
|
+
// shell depends on the caller's defaults behaves differently
|
|
161
|
+
// depending on who calls it.
|
|
162
|
+
throw new ActionError(`${where}: composite step ${i + 1} has "run" but no "shell"`);
|
|
163
|
+
}
|
|
164
|
+
return step;
|
|
165
|
+
});
|
|
166
|
+
return { name, description, inputs, outputs, runs: { using: 'composite', steps } };
|
|
167
|
+
}
|
|
168
|
+
if (using === 'docker') {
|
|
169
|
+
const image = asString(runsRaw.image);
|
|
170
|
+
if (!image)
|
|
171
|
+
throw new ActionError(`${where}: a docker action needs "runs.image"`);
|
|
172
|
+
const env = {};
|
|
173
|
+
if (isObj(runsRaw.env)) {
|
|
174
|
+
for (const [k, v] of Object.entries(runsRaw.env)) {
|
|
175
|
+
const val = asString(v);
|
|
176
|
+
if (val !== undefined)
|
|
177
|
+
env[k] = val;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
name,
|
|
182
|
+
description,
|
|
183
|
+
inputs,
|
|
184
|
+
outputs,
|
|
185
|
+
runs: {
|
|
186
|
+
using: 'docker',
|
|
187
|
+
image,
|
|
188
|
+
entrypoint: asString(runsRaw.entrypoint),
|
|
189
|
+
args: Array.isArray(runsRaw.args)
|
|
190
|
+
? runsRaw.args.map((a) => asString(a) ?? '').filter((a) => a !== '')
|
|
191
|
+
: undefined,
|
|
192
|
+
env,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
throw new ActionError(`${where}: unsupported "runs.using": ${using}`);
|
|
197
|
+
}
|
|
198
|
+
function run(cmd, args, opts = {}) {
|
|
199
|
+
return new Promise((resolve, reject) => {
|
|
200
|
+
(0, child_process_1.execFile)(cmd, args, {
|
|
201
|
+
cwd: opts.cwd,
|
|
202
|
+
timeout: opts.timeout,
|
|
203
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
204
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
205
|
+
}, (err, stdout, stderr) => {
|
|
206
|
+
if (err)
|
|
207
|
+
reject(new ActionError((stderr || err.message).toString().trim()));
|
|
208
|
+
else
|
|
209
|
+
resolve(stdout.toString());
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function defaultActionCacheDir() {
|
|
214
|
+
const base = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache');
|
|
215
|
+
return path.join(base, 'mochi', 'actions');
|
|
216
|
+
}
|
|
217
|
+
// The fallback when a ref could not be resolved to a commit: the entry is
|
|
218
|
+
// keyed by a name, and a name moves, so it is re-fetched after a day. A key
|
|
219
|
+
// that names a commit needs none of this, since the bytes cannot change.
|
|
220
|
+
const STALE_MS = 24 * 60 * 60 * 1000;
|
|
221
|
+
// Which ref `uses: owner/repo@thing` means, when a tag and a branch share the
|
|
222
|
+
// name. This is git's own precedence (rev-parse tries refs/<name>, then tags,
|
|
223
|
+
// then heads), and an annotated tag is peeled to the commit it points at,
|
|
224
|
+
// since that is what a tarball can be built from.
|
|
225
|
+
function pickCommit(lsRemote, ref) {
|
|
226
|
+
const byName = new Map();
|
|
227
|
+
for (const line of lsRemote.split('\n')) {
|
|
228
|
+
const m = /^([0-9a-f]{40})\s+(\S+)$/.exec(line.trim());
|
|
229
|
+
if (m)
|
|
230
|
+
byName.set(m[2], m[1]);
|
|
231
|
+
}
|
|
232
|
+
const candidates = [ref, `${ref}^{}`, `refs/tags/${ref}^{}`, `refs/tags/${ref}`, `refs/heads/${ref}`];
|
|
233
|
+
for (const name of candidates) {
|
|
234
|
+
const sha = byName.get(name);
|
|
235
|
+
if (sha)
|
|
236
|
+
return sha;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
function describeRef(ref, commit) {
|
|
241
|
+
return `${ref.owner}/${ref.repo}@${ref.ref}${commit ? ` (${commit.slice(0, 12)})` : ''}`;
|
|
242
|
+
}
|
|
243
|
+
function ago(ms) {
|
|
244
|
+
const minutes = Math.round(ms / 60000);
|
|
245
|
+
if (minutes < 60)
|
|
246
|
+
return `${minutes}m`;
|
|
247
|
+
const hours = minutes / 60;
|
|
248
|
+
return hours < 24 ? `${Math.round(hours)}h` : `${Math.round(hours / 24)}d`;
|
|
249
|
+
}
|
|
250
|
+
class ActionStore {
|
|
251
|
+
cacheDir;
|
|
252
|
+
forgeUrl;
|
|
253
|
+
useCache;
|
|
254
|
+
// Refs resolved during the current job. A job whose steps all use the same
|
|
255
|
+
// action then asks the forge once rather than once per step. Cleared
|
|
256
|
+
// between jobs (see `beginJob`), so nothing here outlives the job that
|
|
257
|
+
// learned it and a branch that moves is seen on the next run.
|
|
258
|
+
resolved = new Map();
|
|
259
|
+
constructor(cacheDir = defaultActionCacheDir(), forgeUrl = process.env.MOCHI_ACTIONS_URL ?? 'https://github.com',
|
|
260
|
+
// Reusing a download between jobs is what the cache is for; turning it
|
|
261
|
+
// off is for when something about an action itself is being debugged.
|
|
262
|
+
useCache = process.env.MOCHI_ACTIONS_NO_CACHE !== '1') {
|
|
263
|
+
this.cacheDir = cacheDir;
|
|
264
|
+
this.forgeUrl = forgeUrl;
|
|
265
|
+
this.useCache = useCache;
|
|
266
|
+
}
|
|
267
|
+
// Called at the start of every job: what a ref pointed at during the last
|
|
268
|
+
// job says nothing about where it points now.
|
|
269
|
+
beginJob() {
|
|
270
|
+
this.resolved.clear();
|
|
271
|
+
}
|
|
272
|
+
// Fetch (or reuse) an action's source and read its definition. The
|
|
273
|
+
// workspace path is needed for local (`./…`) actions, which live in the
|
|
274
|
+
// repository being built rather than being downloaded.
|
|
275
|
+
async resolve(ref, workspaceDir, onLine) {
|
|
276
|
+
if (ref.kind === 'docker') {
|
|
277
|
+
throw new ActionError(`docker actions are not supported yet (uses: ${ref.raw}); rewrite the step as a run: step or use a JavaScript or composite action`);
|
|
278
|
+
}
|
|
279
|
+
if (ref.kind === 'local') {
|
|
280
|
+
const dir = path.join(workspaceDir, ref.path);
|
|
281
|
+
const real = fs.realpathSync(dir);
|
|
282
|
+
const wsReal = fs.realpathSync(workspaceDir);
|
|
283
|
+
if (real !== wsReal && !real.startsWith(wsReal + path.sep)) {
|
|
284
|
+
throw new ActionError(`local action ${ref.raw} resolves outside the workspace`);
|
|
285
|
+
}
|
|
286
|
+
return { dir, def: this.readDef(dir, ref.raw), key: (0, actionref_1.actionCacheKey)(ref) };
|
|
287
|
+
}
|
|
288
|
+
const commit = await this.resolveCommit(ref, onLine);
|
|
289
|
+
const key = (0, actionref_1.actionCacheKey)(ref, commit ?? undefined);
|
|
290
|
+
const dest = path.join(this.cacheDir, key);
|
|
291
|
+
const stamp = path.join(dest, '.mochi-fetched');
|
|
292
|
+
let fetchedAt = null;
|
|
293
|
+
try {
|
|
294
|
+
fetchedAt = fs.statSync(stamp).mtimeMs;
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
fetchedAt = null;
|
|
298
|
+
}
|
|
299
|
+
// A key that names a commit identifies the bytes, so a hit is always
|
|
300
|
+
// good. Without one the key is a name, which moves.
|
|
301
|
+
const age = fetchedAt === null ? Infinity : Date.now() - fetchedAt;
|
|
302
|
+
const usable = fetchedAt !== null && (commit !== null || age < STALE_MS);
|
|
303
|
+
if (usable && this.useCache) {
|
|
304
|
+
// Say so: a run that quietly used a day-old copy of an action is hard
|
|
305
|
+
// to tell from one that used the tip, which is the whole trap.
|
|
306
|
+
const why = commit ? '' : ` (fetched ${ago(age)} ago, by name)`;
|
|
307
|
+
onLine(`Using cached ${describeRef(ref, commit)}${why}`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
onLine(`Downloading ${describeRef(ref, commit)}${!this.useCache ? ' (cache disabled)' : ''}`);
|
|
311
|
+
await this.download(ref, dest, commit);
|
|
312
|
+
fs.writeFileSync(stamp, new Date().toISOString());
|
|
313
|
+
if (commit)
|
|
314
|
+
this.pruneOtherCommits(ref, key);
|
|
315
|
+
}
|
|
316
|
+
const dir = ref.subpath ? path.join(dest, ref.subpath) : dest;
|
|
317
|
+
if (!fs.existsSync(dir)) {
|
|
318
|
+
throw new ActionError(`${ref.raw}: ${ref.subpath || '.'} does not exist in ${ref.owner}/${ref.repo}@${ref.ref}`);
|
|
319
|
+
}
|
|
320
|
+
return { dir, def: this.readDef(dir, ref.raw), key };
|
|
321
|
+
}
|
|
322
|
+
// Resolve a ref to the commit it names, so the cache is keyed by the object
|
|
323
|
+
// rather than by a name. One `git ls-remote` is a small request, needs no
|
|
324
|
+
// token against a public forge, and costs far less than the tarball it
|
|
325
|
+
// saves re-downloading. When it fails (no network, a private repository, a
|
|
326
|
+
// forge that does not speak git over HTTP) we say so and fall back to the
|
|
327
|
+
// older behavior of keying by name and re-fetching after a day.
|
|
328
|
+
async resolveCommit(ref, onLine) {
|
|
329
|
+
if ((0, actionref_1.refIsImmutable)(ref))
|
|
330
|
+
return ref.ref.toLowerCase();
|
|
331
|
+
const memoKey = `${ref.owner}/${ref.repo}@${ref.ref}`;
|
|
332
|
+
const hit = this.resolved.get(memoKey);
|
|
333
|
+
if (hit !== undefined)
|
|
334
|
+
return hit;
|
|
335
|
+
const url = `${this.forgeUrl.replace(/\/+$/, '')}/${ref.owner}/${ref.repo}`;
|
|
336
|
+
let out;
|
|
337
|
+
try {
|
|
338
|
+
// No ref patterns: git filters them on the client anyway, after the
|
|
339
|
+
// server has advertised everything, and a pattern would hide the
|
|
340
|
+
// peeled entry (refs/tags/v1^{}) that an annotated tag needs.
|
|
341
|
+
out = await run('git', ['ls-remote', url], {
|
|
342
|
+
timeout: 20000,
|
|
343
|
+
// Never stop for a credential prompt: a private or missing
|
|
344
|
+
// repository must fail rather than hang the job.
|
|
345
|
+
env: { GIT_TERMINAL_PROMPT: '0', GIT_ASKPASS: '', GCM_INTERACTIVE: 'never' },
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
catch (e) {
|
|
349
|
+
onLine(`Could not resolve ${ref.owner}/${ref.repo}@${ref.ref} (${e instanceof Error ? e.message : e}); ` +
|
|
350
|
+
`caching it by name instead`);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
const commit = pickCommit(out, ref.ref);
|
|
354
|
+
if (!commit) {
|
|
355
|
+
onLine(`${ref.owner}/${ref.repo} has no ref named ${ref.ref}; caching it by name instead`);
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
this.resolved.set(memoKey, commit);
|
|
359
|
+
return commit;
|
|
360
|
+
}
|
|
361
|
+
// A ref that moves would otherwise leave its old commit behind on every
|
|
362
|
+
// move. Removing the entries for the same owner/repo/ref keeps the cache
|
|
363
|
+
// at one copy per ref, the footprint it had when keys were names.
|
|
364
|
+
pruneOtherCommits(ref, keep) {
|
|
365
|
+
const prefix = `${(0, actionref_1.commitPrefix)(ref)}__`;
|
|
366
|
+
try {
|
|
367
|
+
for (const name of fs.readdirSync(this.cacheDir)) {
|
|
368
|
+
if (name === keep || !name.startsWith(prefix))
|
|
369
|
+
continue;
|
|
370
|
+
fs.rmSync(path.join(this.cacheDir, name), { recursive: true, force: true });
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// A cache we cannot tidy is not a reason to fail the job.
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
readDef(dir, where) {
|
|
378
|
+
for (const name of ['action.yml', 'action.yaml']) {
|
|
379
|
+
const file = path.join(dir, name);
|
|
380
|
+
if (fs.existsSync(file))
|
|
381
|
+
return parseActionDef(fs.readFileSync(file, 'utf8'), where);
|
|
382
|
+
}
|
|
383
|
+
throw new ActionError(`${where}: no action.yml or action.yaml in the action directory`);
|
|
384
|
+
}
|
|
385
|
+
async download(ref, dest, commit) {
|
|
386
|
+
// Ask for the commit when it is known, so the tarball is the one the key
|
|
387
|
+
// promises even if the ref moves between resolving and downloading.
|
|
388
|
+
const url = `${this.forgeUrl.replace(/\/+$/, '')}/${ref.owner}/${ref.repo}/archive/${encodeURIComponent(commit ?? ref.ref)}.tar.gz`;
|
|
389
|
+
let res;
|
|
390
|
+
try {
|
|
391
|
+
res = await fetch(url, { redirect: 'follow' });
|
|
392
|
+
}
|
|
393
|
+
catch (e) {
|
|
394
|
+
throw new ActionError(`could not fetch ${ref.raw}: ${e instanceof Error ? e.message : e}`);
|
|
395
|
+
}
|
|
396
|
+
if (!res.ok) {
|
|
397
|
+
throw new ActionError(`could not fetch ${ref.raw}: ${url} answered ${res.status}${res.status === 404 ? ' (check the owner, repository, and ref)' : ''}`);
|
|
398
|
+
}
|
|
399
|
+
const tmp = `${dest}.tmp-${process.pid}`;
|
|
400
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
401
|
+
fs.mkdirSync(tmp, { recursive: true });
|
|
402
|
+
const tarball = path.join(tmp, 'source.tar.gz');
|
|
403
|
+
fs.writeFileSync(tarball, Buffer.from(await res.arrayBuffer()));
|
|
404
|
+
const extracted = path.join(tmp, 'src');
|
|
405
|
+
fs.mkdirSync(extracted);
|
|
406
|
+
try {
|
|
407
|
+
// The archive holds a single top-level directory named for the repo and
|
|
408
|
+
// ref, which --strip-components removes.
|
|
409
|
+
await run('tar', ['-xzf', tarball, '-C', extracted, '--strip-components=1']);
|
|
410
|
+
}
|
|
411
|
+
catch (e) {
|
|
412
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
413
|
+
throw new ActionError(`could not unpack ${ref.raw}: ${e instanceof Error ? e.message : e}`);
|
|
414
|
+
}
|
|
415
|
+
fs.rmSync(tarball, { force: true });
|
|
416
|
+
fs.rmSync(dest, { recursive: true, force: true });
|
|
417
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
418
|
+
fs.renameSync(extracted, dest);
|
|
419
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
exports.ActionStore = ActionStore;
|
|
423
|
+
// Turn an action's declared inputs plus a step's `with:` into the INPUT_*
|
|
424
|
+
// environment an action reads. GitHub uppercases the name and replaces
|
|
425
|
+
// spaces with underscores, and leaves everything else alone.
|
|
426
|
+
function inputEnvName(name) {
|
|
427
|
+
return `INPUT_${name.replace(/ /g, '_').toUpperCase()}`;
|
|
428
|
+
}
|
|
429
|
+
function resolveInputs(def, provided) {
|
|
430
|
+
const values = {};
|
|
431
|
+
const missing = [];
|
|
432
|
+
for (const [name, spec] of Object.entries(def.inputs)) {
|
|
433
|
+
if (provided[name] !== undefined)
|
|
434
|
+
values[name] = provided[name];
|
|
435
|
+
else if (spec.default !== undefined)
|
|
436
|
+
values[name] = spec.default;
|
|
437
|
+
else if (spec.required)
|
|
438
|
+
missing.push(name);
|
|
439
|
+
}
|
|
440
|
+
// Inputs a step passes that the action does not declare are still exported,
|
|
441
|
+
// which matches the runner's behavior and is relied on by actions that read
|
|
442
|
+
// undeclared inputs.
|
|
443
|
+
for (const [name, value] of Object.entries(provided)) {
|
|
444
|
+
if (values[name] === undefined)
|
|
445
|
+
values[name] = value;
|
|
446
|
+
}
|
|
447
|
+
return { values, missing };
|
|
448
|
+
}
|