@secureci/cli 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,484 @@
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.builtinActionFor = builtinActionFor;
37
+ exports.hostFileSystem = hostFileSystem;
38
+ exports.containerFileSystem = containerFileSystem;
39
+ exports.collectFiles = collectFiles;
40
+ exports.uploadArtifact = uploadArtifact;
41
+ exports.downloadArtifact = downloadArtifact;
42
+ exports.runCacheAction = runCacheAction;
43
+ const crypto = __importStar(require("crypto"));
44
+ const fs = __importStar(require("fs"));
45
+ const os = __importStar(require("os"));
46
+ const path = __importStar(require("path"));
47
+ const hash_files_1 = require("./hash-files");
48
+ /** Which built-in, if any, handles a `uses:` reference. */
49
+ function builtinActionFor(uses) {
50
+ const match = uses.match(/^actions\/(upload-artifact|download-artifact|cache(?:\/restore|\/save)?)@/i);
51
+ if (!match)
52
+ return undefined;
53
+ return match[1].toLowerCase().replace('/', '-');
54
+ }
55
+ /** A step file system for steps running directly on the host. */
56
+ function hostFileSystem(workspace) {
57
+ return {
58
+ workspace,
59
+ home: os.homedir(),
60
+ path: path,
61
+ readable: async (logical) => (fs.existsSync(logical) ? logical : null),
62
+ writeInto: async (logical, fill) => {
63
+ fs.mkdirSync(logical, { recursive: true });
64
+ fill(logical);
65
+ },
66
+ cleanup: () => undefined
67
+ };
68
+ }
69
+ /** A step file system for steps running in a job container. */
70
+ function containerFileSystem(access) {
71
+ const p = path.posix;
72
+ const temps = [];
73
+ const tempDir = () => {
74
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'secureci-files-'));
75
+ temps.push(dir);
76
+ return dir;
77
+ };
78
+ const inWorkspace = (logical) => logical === access.workspace || logical.startsWith(access.workspace + '/');
79
+ const toHost = (logical) => path.join(access.hostWorkspace, ...p.relative(access.workspace, logical).split('/'));
80
+ return {
81
+ workspace: access.workspace,
82
+ home: access.home,
83
+ path: p,
84
+ readable: async (logical) => {
85
+ if (inWorkspace(logical)) {
86
+ const host = toHost(logical);
87
+ return fs.existsSync(host) ? host : null;
88
+ }
89
+ const target = path.join(tempDir(), 'copy');
90
+ return (await access.copyFrom(logical, target)) ? target : null;
91
+ },
92
+ writeInto: async (logical, fill) => {
93
+ if (inWorkspace(logical)) {
94
+ const host = toHost(logical);
95
+ fs.mkdirSync(host, { recursive: true });
96
+ fill(host);
97
+ return;
98
+ }
99
+ const stage = tempDir();
100
+ fill(stage);
101
+ await access.mkdir(logical);
102
+ if (!(await access.copyTo(`${stage}${path.sep}.`, logical))) {
103
+ throw new Error(`Could not copy files into the container at ${logical}`);
104
+ }
105
+ },
106
+ cleanup: () => {
107
+ for (const dir of temps.splice(0)) {
108
+ fs.rmSync(dir, { recursive: true, force: true });
109
+ }
110
+ }
111
+ };
112
+ }
113
+ function listFiles(hostRoot, base = '') {
114
+ const stat = fs.statSync(hostRoot);
115
+ if (stat.isFile())
116
+ return [base];
117
+ const files = [];
118
+ for (const entry of fs.readdirSync(hostRoot, { withFileTypes: true })) {
119
+ const rel = base ? `${base}/${entry.name}` : entry.name;
120
+ const full = path.join(hostRoot, entry.name);
121
+ if (entry.isDirectory())
122
+ files.push(...listFiles(full, rel));
123
+ else if (entry.isFile())
124
+ files.push(rel);
125
+ }
126
+ return files;
127
+ }
128
+ /** Expand a leading `~` and resolve against the workspace. */
129
+ function resolveLogical(fsys, raw) {
130
+ const expanded = raw === '~' || raw.startsWith('~/')
131
+ ? fsys.path.join(fsys.home, raw.slice(1))
132
+ : raw;
133
+ return fsys.path.resolve(fsys.workspace, expanded);
134
+ }
135
+ function commonAncestor(p, dirs) {
136
+ let common = dirs[0].split(p.sep);
137
+ for (const dir of dirs.slice(1)) {
138
+ const parts = dir.split(p.sep);
139
+ let i = 0;
140
+ while (i < common.length && i < parts.length && common[i] === parts[i]) {
141
+ i++;
142
+ }
143
+ common = common.slice(0, i);
144
+ }
145
+ return common.join(p.sep) || p.sep;
146
+ }
147
+ /**
148
+ * Files matching multi-line path patterns, the way upload-artifact and
149
+ * cache read them: each line is a file, a directory (all files under it)
150
+ * or a glob (`*`, `**`, `?`); `!pattern` excludes. `root` is the least
151
+ * common ancestor of the search paths, which file paths are relative to.
152
+ */
153
+ async function collectFiles(fsys, patterns, options) {
154
+ const p = fsys.path;
155
+ const lines = patterns
156
+ .split('\n')
157
+ .map((line) => line.trim())
158
+ .filter(Boolean);
159
+ const excludes = lines
160
+ .filter((line) => line.startsWith('!'))
161
+ .map((line) => (0, hash_files_1.globToRegExp)(resolveLogical(fsys, line.slice(1).trim())));
162
+ const searchPaths = [];
163
+ const found = new Map();
164
+ for (const line of lines.filter((l) => !l.startsWith('!'))) {
165
+ const pattern = resolveLogical(fsys, line);
166
+ const globAt = pattern.search(/[*?[]/);
167
+ const searchPath = globAt === -1
168
+ ? pattern
169
+ : pattern.slice(0, pattern.lastIndexOf('/', globAt)) || p.sep;
170
+ const host = await fsys.readable(searchPath);
171
+ if (!host)
172
+ continue;
173
+ const isFile = fs.statSync(host).isFile();
174
+ searchPaths.push(isFile ? p.dirname(searchPath) : searchPath);
175
+ const matcher = globAt === -1 ? undefined : (0, hash_files_1.globToRegExp)(pattern);
176
+ for (const rel of listFiles(host)) {
177
+ const logical = rel ? p.join(searchPath, rel) : searchPath;
178
+ if (matcher && !matcher.test(logical))
179
+ continue;
180
+ found.set(logical, {
181
+ logical,
182
+ host: rel ? path.join(host, ...rel.split('/')) : host
183
+ });
184
+ }
185
+ }
186
+ const root = searchPaths.length
187
+ ? commonAncestor(p, searchPaths)
188
+ : fsys.workspace;
189
+ const files = [...found.values()].filter((file) => !excludes.some((re) => re.test(file.logical)) &&
190
+ (options.includeHidden ||
191
+ !p
192
+ .relative(root, file.logical)
193
+ .split('/')
194
+ .some((part) => part.startsWith('.'))));
195
+ return { root, files };
196
+ }
197
+ function copyFile(from, to) {
198
+ fs.mkdirSync(path.dirname(to), { recursive: true });
199
+ fs.copyFileSync(from, to);
200
+ }
201
+ function copyTree(from, to) {
202
+ fs.cpSync(from, to, { recursive: true });
203
+ }
204
+ function secureciDir(...parts) {
205
+ const home = process.env.HOME || process.env.USERPROFILE || os.tmpdir();
206
+ return path.join(home, '.secureci', ...parts);
207
+ }
208
+ const ok = (output, outputs = {}) => ({
209
+ success: true,
210
+ output,
211
+ outputs
212
+ });
213
+ const fail = (error) => ({
214
+ success: false,
215
+ output: '',
216
+ outputs: {},
217
+ error
218
+ });
219
+ const isTrue = (value) => value?.trim().toLowerCase() === 'true';
220
+ /** Artifacts are kept per run, for 7 days. */
221
+ const ARTIFACT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
222
+ function artifactsDir(runId) {
223
+ return secureciDir('artifacts', runId.replace(/[^\w.-]/g, '_'));
224
+ }
225
+ function pruneOldArtifacts() {
226
+ const base = secureciDir('artifacts');
227
+ let runs;
228
+ try {
229
+ runs = fs.readdirSync(base);
230
+ }
231
+ catch {
232
+ return;
233
+ }
234
+ for (const run of runs) {
235
+ const dir = path.join(base, run);
236
+ try {
237
+ if (Date.now() - fs.statSync(dir).mtimeMs > ARTIFACT_RETENTION_MS) {
238
+ fs.rmSync(dir, { recursive: true, force: true });
239
+ }
240
+ }
241
+ catch {
242
+ // Best effort
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * GitHub's invalid artifact-name characters, plus anything that would let a
248
+ * crafted name escape the per-run store (`/`, `\\`, `..`). The store path
249
+ * is built from the name, so an unchecked name like `../../../.ssh` would
250
+ * write onto the host even from a fully containerized job.
251
+ */
252
+ function safeName(name) {
253
+ try {
254
+ assertSafeArtifactName(name);
255
+ return true;
256
+ }
257
+ catch {
258
+ return false;
259
+ }
260
+ }
261
+ function assertSafeArtifactName(name) {
262
+ if (name === '' ||
263
+ name === '.' ||
264
+ name === '..' ||
265
+ /[\\/"<>|:*?\r\n]/.test(name)) {
266
+ throw new Error(`Invalid artifact name: "${name}". Artifact names cannot be empty or contain any of: \\ / " < > | : * ? or line breaks.`);
267
+ }
268
+ }
269
+ /** `actions/upload-artifact`. */
270
+ async function uploadArtifact(fsys, inputs, runId) {
271
+ const name = inputs.name || 'artifact';
272
+ try {
273
+ assertSafeArtifactName(name);
274
+ }
275
+ catch (error) {
276
+ return fail(error instanceof Error ? error.message : String(error));
277
+ }
278
+ if (!inputs.path)
279
+ return fail('Input required and not supplied: path');
280
+ const { root, files } = await collectFiles(fsys, inputs.path, {
281
+ includeHidden: isTrue(inputs['include-hidden-files'])
282
+ });
283
+ if (files.length === 0) {
284
+ const mode = (inputs['if-no-files-found'] || 'warn').toLowerCase();
285
+ const message = `No files were found with the provided path: ${inputs.path.trim()}. No artifacts will be uploaded.`;
286
+ if (mode === 'error')
287
+ return fail(message);
288
+ return { ...ok(''), ...(mode === 'ignore' ? {} : { warning: message }) };
289
+ }
290
+ pruneOldArtifacts();
291
+ const target = path.join(artifactsDir(runId), name);
292
+ if (fs.existsSync(target)) {
293
+ if (!isTrue(inputs.overwrite)) {
294
+ return fail(`An artifact with the name "${name}" already exists in this run; set overwrite: true to replace it`);
295
+ }
296
+ fs.rmSync(target, { recursive: true, force: true });
297
+ }
298
+ const digest = crypto.createHash('sha256');
299
+ for (const file of files.sort((a, b) => a.logical.localeCompare(b.logical))) {
300
+ const rel = fsys.path.relative(root, file.logical) ||
301
+ fsys.path.basename(file.logical);
302
+ copyFile(file.host, path.join(target, ...rel.split('/')));
303
+ digest.update(rel).update(fs.readFileSync(file.host));
304
+ }
305
+ const id = String(parseInt(crypto.createHash('sha1').update(target).digest('hex').slice(0, 8), 16));
306
+ return ok(`Uploaded ${files.length} file(s) as artifact "${name}" to ${target}`, {
307
+ 'artifact-id': id,
308
+ 'artifact-url': '',
309
+ 'artifact-digest': digest.digest('hex')
310
+ });
311
+ }
312
+ /** `actions/download-artifact`. */
313
+ async function downloadArtifact(fsys, inputs, runId) {
314
+ const store = artifactsDir(runId);
315
+ const available = fs.existsSync(store) ? fs.readdirSync(store).sort() : [];
316
+ const dest = resolveLogical(fsys, inputs.path || '.');
317
+ let names;
318
+ if (inputs.name) {
319
+ try {
320
+ assertSafeArtifactName(inputs.name);
321
+ }
322
+ catch (error) {
323
+ return fail(error instanceof Error ? error.message : String(error));
324
+ }
325
+ if (!available.includes(inputs.name)) {
326
+ return fail(`Artifact not found for name: ${inputs.name}`);
327
+ }
328
+ names = [inputs.name];
329
+ }
330
+ else {
331
+ const pattern = inputs.pattern ? (0, hash_files_1.globToRegExp)(inputs.pattern) : undefined;
332
+ names = available.filter((name) => safeName(name) && (!pattern || pattern.test(name)));
333
+ }
334
+ // A single named artifact, or merge-multiple, goes straight into path;
335
+ // otherwise each artifact gets its own <path>/<name> directory.
336
+ const flat = !!inputs.name || isTrue(inputs['merge-multiple']);
337
+ await fsys.writeInto(dest, (hostDest) => {
338
+ for (const name of names) {
339
+ copyTree(path.join(store, name), flat ? hostDest : path.join(hostDest, name));
340
+ }
341
+ });
342
+ return ok(`Downloaded ${names.length} artifact(s) to ${dest}${names.length ? `: ${names.join(', ')}` : ''}`, { 'download-path': dest });
343
+ }
344
+ function cacheScope(repository) {
345
+ return secureciDir('step-cache', repository.replace(/[^\w.-]+/g, '_') || 'default');
346
+ }
347
+ /** Caches are versioned by their paths, as on GitHub. */
348
+ function cacheVersion(paths) {
349
+ return crypto
350
+ .createHash('sha256')
351
+ .update(paths
352
+ .split('\n')
353
+ .map((line) => line.trim())
354
+ .filter(Boolean)
355
+ .join('\n'))
356
+ .digest('hex');
357
+ }
358
+ function cacheEntryDir(scope, version, key) {
359
+ return path.join(scope, crypto.createHash('sha256').update(`${version}\0${key}`).digest('hex'));
360
+ }
361
+ function findCache(scope, version, key, restoreKeys) {
362
+ const exact = cacheEntryDir(scope, version, key);
363
+ if (fs.existsSync(path.join(exact, 'meta.json'))) {
364
+ return { dir: exact, key, created: 0 };
365
+ }
366
+ let entries = [];
367
+ try {
368
+ entries = fs
369
+ .readdirSync(scope)
370
+ .map((name) => {
371
+ try {
372
+ const meta = JSON.parse(fs.readFileSync(path.join(scope, name, 'meta.json'), 'utf8'));
373
+ return meta.version === version
374
+ ? {
375
+ dir: path.join(scope, name),
376
+ key: meta.key,
377
+ created: meta.created
378
+ }
379
+ : undefined;
380
+ }
381
+ catch {
382
+ return undefined;
383
+ }
384
+ })
385
+ .filter((entry) => !!entry);
386
+ }
387
+ catch {
388
+ return undefined;
389
+ }
390
+ for (const prefix of restoreKeys) {
391
+ const match = entries
392
+ .filter((entry) => entry.key.startsWith(prefix))
393
+ .sort((a, b) => b.created - a.created)[0];
394
+ if (match)
395
+ return match;
396
+ }
397
+ return undefined;
398
+ }
399
+ /** Where a cached file is stored: relative to the workspace, home, or /. */
400
+ function cachedLocation(fsys, logical) {
401
+ const p = fsys.path;
402
+ for (const [area, base] of [
403
+ ['workspace', fsys.workspace],
404
+ ['home', fsys.home]
405
+ ]) {
406
+ const rel = p.relative(base, logical);
407
+ if (rel && !rel.startsWith('..') && !p.isAbsolute(rel))
408
+ return `${area}/${rel}`;
409
+ }
410
+ return `root/${logical.replace(/^\/+/, '')}`;
411
+ }
412
+ async function restoreCache(fsys, entryDir) {
413
+ const files = path.join(entryDir, 'files');
414
+ for (const [area, base] of [
415
+ ['workspace', fsys.workspace],
416
+ ['home', fsys.home],
417
+ ['root', '/']
418
+ ]) {
419
+ const from = path.join(files, area);
420
+ if (fs.existsSync(from)) {
421
+ await fsys.writeInto(base, (hostDir) => copyTree(from, hostDir));
422
+ }
423
+ }
424
+ }
425
+ async function saveCache(fsys, scope, key, paths) {
426
+ const version = cacheVersion(paths);
427
+ const dir = cacheEntryDir(scope, version, key);
428
+ if (fs.existsSync(path.join(dir, 'meta.json'))) {
429
+ return ok(`Cache for key "${key}" already exists; not saving`);
430
+ }
431
+ const { files } = await collectFiles(fsys, paths, { includeHidden: true });
432
+ if (files.length === 0) {
433
+ return {
434
+ ...ok(''),
435
+ warning: 'Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.'
436
+ };
437
+ }
438
+ const staging = `${dir}.tmp-${process.pid}`;
439
+ fs.rmSync(staging, { recursive: true, force: true });
440
+ for (const file of files) {
441
+ copyFile(file.host, path.join(staging, 'files', ...cachedLocation(fsys, file.logical).split('/')));
442
+ }
443
+ fs.writeFileSync(path.join(staging, 'meta.json'), JSON.stringify({ key, version, created: Date.now() }));
444
+ fs.mkdirSync(path.dirname(dir), { recursive: true });
445
+ fs.renameSync(staging, dir);
446
+ return ok(`Cache saved with key: ${key} (${files.length} file(s))`);
447
+ }
448
+ /** `actions/cache`, `actions/cache/restore` and `actions/cache/save`. */
449
+ async function runCacheAction(kind, fsys, inputs, repository) {
450
+ const key = (inputs.key || '').trim();
451
+ const paths = inputs.path || '';
452
+ if (!key)
453
+ return fail('Input required and not supplied: key');
454
+ if (!paths.trim())
455
+ return fail('Input required and not supplied: path');
456
+ const scope = cacheScope(repository);
457
+ if (kind === 'cache-save')
458
+ return saveCache(fsys, scope, key, paths);
459
+ const restoreKeys = (inputs['restore-keys'] || '')
460
+ .split('\n')
461
+ .map((line) => line.trim())
462
+ .filter(Boolean);
463
+ const found = findCache(scope, cacheVersion(paths), key, restoreKeys);
464
+ const exact = found?.key === key;
465
+ if (!found && isTrue(inputs['fail-on-cache-miss'])) {
466
+ return fail(`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${key}`);
467
+ }
468
+ if (found && !isTrue(inputs['lookup-only'])) {
469
+ await restoreCache(fsys, found.dir);
470
+ }
471
+ const outputs = { 'cache-primary-key': key };
472
+ if (found) {
473
+ outputs['cache-hit'] = String(exact);
474
+ outputs['cache-matched-key'] = found.key;
475
+ }
476
+ const result = ok(found
477
+ ? `Cache ${isTrue(inputs['lookup-only']) ? 'found' : 'restored'} from key: ${found.key}`
478
+ : `Cache not found for input keys: ${[key, ...restoreKeys].join(', ')}`, outputs);
479
+ if (kind === 'cache' && !exact && !isTrue(inputs['lookup-only'])) {
480
+ result.post = (postFs) => saveCache(postFs, scope, key, paths);
481
+ }
482
+ return result;
483
+ }
484
+ //# sourceMappingURL=builtin-actions.js.map
@@ -0,0 +1,142 @@
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.copyWorkingCopy = copyWorkingCopy;
37
+ exports.isLocalCheckout = isLocalCheckout;
38
+ exports.localCheckout = localCheckout;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const logger_1 = require("../utils/logger");
42
+ const expression_1 = require("./expression");
43
+ /** Marks a `path:` copy made by localCheckout, so it may be replaced. */
44
+ const CHECKOUT_MARKER = '.secureci-checkout';
45
+ /**
46
+ * Copy `srcDir` into `dstDir`, skipping `exclude` (the copy's own location,
47
+ * which may lie inside `srcDir`) and earlier local checkout copies.
48
+ */
49
+ function copyWorkingCopy(srcDir, dstDir, exclude) {
50
+ fs.mkdirSync(dstDir, { recursive: true });
51
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
52
+ const src = path.join(srcDir, entry.name);
53
+ const dst = path.join(dstDir, entry.name);
54
+ if (src === exclude)
55
+ continue;
56
+ if (entry.isDirectory()) {
57
+ if (fs.existsSync(path.join(src, CHECKOUT_MARKER)))
58
+ continue;
59
+ if (exclude.startsWith(src + path.sep)) {
60
+ copyWorkingCopy(src, dst, exclude);
61
+ continue;
62
+ }
63
+ }
64
+ fs.cpSync(src, dst, {
65
+ recursive: true,
66
+ verbatimSymlinks: true,
67
+ filter: (file) => !fs.existsSync(path.join(file, CHECKOUT_MARKER))
68
+ });
69
+ }
70
+ }
71
+ /**
72
+ * Whether a step is `actions/checkout` of the repository being run (no
73
+ * `repository:` input, or the current one in any case), which is served
74
+ * from the local working copy rather than cloned.
75
+ */
76
+ function isLocalCheckout(uses, withValues, ctx) {
77
+ if (!/^actions\/checkout@/i.test(uses))
78
+ return false;
79
+ const repository = (0, expression_1.substituteRecord)(withValues, ctx).repository ?? '';
80
+ return (repository === '' ||
81
+ repository.toLowerCase() === String(ctx.github.repository).toLowerCase());
82
+ }
83
+ /**
84
+ * `actions/checkout` of the current repository: the workspace already is
85
+ * the user's working copy (uncommitted changes included), so nothing is
86
+ * cloned and `.git` is never touched. With `path:`, the working copy is
87
+ * copied into that subdirectory and the copy is removed at job end.
88
+ */
89
+ function localCheckout(step, ctx, runtime, startTime) {
90
+ const withValues = (0, expression_1.substituteRecord)(step.with, ctx);
91
+ const outputs = {
92
+ ref: String(ctx.github.ref ?? ''),
93
+ commit: String(ctx.github.sha ?? '')
94
+ };
95
+ const fail = (error) => {
96
+ logger_1.logger.error(error);
97
+ return { step, success: false, error, duration: Date.now() - startTime };
98
+ };
99
+ const ref = withValues.ref;
100
+ if (ref &&
101
+ ![ctx.github.ref, ctx.github.ref_name, ctx.github.sha].includes(ref)) {
102
+ logger_1.logger.warn(`actions/checkout: ignoring ref "${ref}"; using the local working copy (pass --no-local-checkout to run the real action)`);
103
+ }
104
+ const workspace = process.cwd();
105
+ const dest = path.resolve(workspace, withValues.path || '.');
106
+ const relative = path.relative(workspace, dest);
107
+ if (relative.startsWith('..') || path.isAbsolute(relative)) {
108
+ return fail(`actions/checkout: path "${withValues.path}" is outside the workspace`);
109
+ }
110
+ let output = 'Using local working copy';
111
+ if (relative !== '') {
112
+ if (fs.existsSync(dest)) {
113
+ if (fs.existsSync(path.join(dest, CHECKOUT_MARKER))) {
114
+ fs.rmSync(dest, { recursive: true, force: true });
115
+ }
116
+ else if (fs.readdirSync(dest).length > 0) {
117
+ return fail(`actions/checkout: path "${withValues.path}" already exists in the working copy; remove it or pass --no-local-checkout`);
118
+ }
119
+ }
120
+ copyWorkingCopy(workspace, dest, dest);
121
+ fs.writeFileSync(path.join(dest, CHECKOUT_MARKER), '');
122
+ output = `Using local working copy (copied to ${relative})`;
123
+ const postStep = { name: `Post ${step.name || step.uses}` };
124
+ runtime.postSteps.push({
125
+ step: postStep,
126
+ condition: 'always()',
127
+ run: async () => {
128
+ fs.rmSync(dest, { recursive: true, force: true });
129
+ return { step: postStep, success: true, duration: 0 };
130
+ }
131
+ });
132
+ }
133
+ logger_1.logger.info(output);
134
+ return {
135
+ step,
136
+ success: true,
137
+ output,
138
+ outputs,
139
+ duration: Date.now() - startTime
140
+ };
141
+ }
142
+ //# sourceMappingURL=checkout.js.map