@effected/github-actions 0.2.0 → 0.4.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.
- package/ActionCache.js +128 -17
- package/ActionEnvironment.js +34 -1
- package/ActionInput.js +61 -8
- package/ActionOutputs.js +71 -2
- package/ActionState.js +33 -6
- package/Artifact.js +2 -2
- package/BlobStore.githubCache.js +2 -1
- package/CacheKey.js +147 -8
- package/ChildEnv.js +124 -0
- package/PackageManagerInstaller.js +494 -0
- package/Secret.js +21 -0
- package/ToolInstaller.js +98 -52
- package/index.d.ts +878 -125
- package/index.js +3 -1
- package/package.json +5 -4
package/ToolInstaller.js
CHANGED
|
@@ -53,6 +53,8 @@ var ToolInstallerError = class extends Schema.TaggedErrorClass()("ToolInstallerE
|
|
|
53
53
|
};
|
|
54
54
|
/** How many times a retryable download is retried before giving up. */
|
|
55
55
|
const DOWNLOAD_RETRIES = 2;
|
|
56
|
+
/** The default per-attempt download budget. See {@link ToolDownloadOptions.timeout}. */
|
|
57
|
+
const DOWNLOAD_TIMEOUT = "5 minutes";
|
|
56
58
|
const make = Effect.gen(function* () {
|
|
57
59
|
const env = yield* ActionEnvironment;
|
|
58
60
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -83,29 +85,37 @@ const make = Effect.gen(function* () {
|
|
|
83
85
|
})));
|
|
84
86
|
};
|
|
85
87
|
/**
|
|
86
|
-
* Run an extraction command, keeping its stderr.
|
|
88
|
+
* Run an extraction command ONCE, keeping its stderr.
|
|
87
89
|
*
|
|
88
90
|
* @remarks
|
|
89
91
|
* `tar`'s exit code says only that it failed; its stderr says why, and it is
|
|
90
92
|
* the difference between "this is not a gzip archive" and "tar is not
|
|
91
93
|
* installed". Discarding it is what makes an extraction failure unactionable.
|
|
94
|
+
*
|
|
95
|
+
* **One spawn, not two — load-bearing.** The spawner's convenience members
|
|
96
|
+
* each spawn independently: `string` collects output without inspecting the
|
|
97
|
+
* exit code, and `exitCode` runs the command AGAIN (core's
|
|
98
|
+
* `ChildProcessSpawner.make` derives both from `spawn`). Calling them
|
|
99
|
+
* back-to-back double-executed every extraction — harmless for idempotent
|
|
100
|
+
* `tar`/`unzip -o`, but .NET's `ZipFile.ExtractToDirectory` refuses to
|
|
101
|
+
* overwrite, so the second run failed 5/5 on real Windows runners while the
|
|
102
|
+
* captured "complaint" was the FIRST run's silent success. Output and exit
|
|
103
|
+
* code must come from the same `spawn` handle.
|
|
92
104
|
*/
|
|
93
|
-
const extractWith = (command, archive) => Effect.gen(function* () {
|
|
94
|
-
const
|
|
95
|
-
reason: "extractFailed",
|
|
96
|
-
subject: archive,
|
|
97
|
-
cause
|
|
98
|
-
})));
|
|
99
|
-
if ((yield* spawner.exitCode(command).pipe(Effect.mapError((cause) => new ToolInstallerError({
|
|
105
|
+
const extractWith = (command, archive) => Effect.scoped(Effect.gen(function* () {
|
|
106
|
+
const extractError = (cause) => new ToolInstallerError({
|
|
100
107
|
reason: "extractFailed",
|
|
101
108
|
subject: archive,
|
|
102
109
|
cause
|
|
103
|
-
})
|
|
110
|
+
});
|
|
111
|
+
const handle = yield* spawner.spawn(command).pipe(Effect.mapError(extractError));
|
|
112
|
+
const output = yield* Stream.mkString(Stream.decodeText(handle.all)).pipe(Effect.mapError(extractError));
|
|
113
|
+
if ((yield* handle.exitCode.pipe(Effect.mapError(extractError))) !== 0) return yield* Effect.fail(new ToolInstallerError({
|
|
104
114
|
reason: "extractFailed",
|
|
105
115
|
subject: archive,
|
|
106
116
|
stderr: output.trim()
|
|
107
117
|
}));
|
|
108
|
-
});
|
|
118
|
+
}));
|
|
109
119
|
/**
|
|
110
120
|
* Install a staged directory into the tool cache by renaming it into place.
|
|
111
121
|
*
|
|
@@ -146,38 +156,55 @@ const make = Effect.gen(function* () {
|
|
|
146
156
|
recursive: true,
|
|
147
157
|
force: true
|
|
148
158
|
}))))));
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
subject: url,
|
|
161
|
-
status: response.status
|
|
162
|
-
}));
|
|
163
|
-
const directory = yield* fs.makeTempDirectory({ prefix: "effected-download-" }).pipe(Effect.mapError((cause) => new ToolInstallerError({
|
|
164
|
-
reason: "downloadFailed",
|
|
165
|
-
subject: url,
|
|
166
|
-
cause
|
|
167
|
-
})));
|
|
168
|
-
const file = path.join(directory, "download");
|
|
169
|
-
yield* Stream.run(response.stream, fs.sink(file)).pipe(Effect.mapError((cause) => new ToolInstallerError({
|
|
170
|
-
reason: "downloadFailed",
|
|
171
|
-
subject: url,
|
|
172
|
-
cause
|
|
173
|
-
})));
|
|
174
|
-
return file;
|
|
175
|
-
}).pipe(Effect.retry({
|
|
176
|
-
schedule: Schedule.exponential("1 second"),
|
|
177
|
-
times: DOWNLOAD_RETRIES,
|
|
178
|
-
while: (error) => error.retryable
|
|
159
|
+
const download = Effect.fn("ToolInstaller.download")(function* (url, options) {
|
|
160
|
+
return yield* Effect.gen(function* () {
|
|
161
|
+
const response = yield* http.get(url).pipe(Effect.mapError((cause) => new ToolInstallerError({
|
|
162
|
+
reason: "downloadFailed",
|
|
163
|
+
subject: url,
|
|
164
|
+
cause
|
|
165
|
+
})));
|
|
166
|
+
if (response.status < 200 || response.status >= 300) return yield* Effect.fail(new ToolInstallerError({
|
|
167
|
+
reason: "downloadFailed",
|
|
168
|
+
subject: url,
|
|
169
|
+
status: response.status
|
|
179
170
|
}));
|
|
180
|
-
|
|
171
|
+
const directory = yield* fs.makeTempDirectory({ prefix: "effected-download-" }).pipe(Effect.mapError((cause) => new ToolInstallerError({
|
|
172
|
+
reason: "downloadFailed",
|
|
173
|
+
subject: url,
|
|
174
|
+
cause
|
|
175
|
+
})));
|
|
176
|
+
const file = path.join(directory, "download");
|
|
177
|
+
yield* Stream.run(response.stream, fs.sink(file)).pipe(Effect.mapError((cause) => new ToolInstallerError({
|
|
178
|
+
reason: "downloadFailed",
|
|
179
|
+
subject: url,
|
|
180
|
+
cause
|
|
181
|
+
})));
|
|
182
|
+
return file;
|
|
183
|
+
}).pipe(Effect.timeout(options?.timeout ?? DOWNLOAD_TIMEOUT), Effect.catchTag("TimeoutError", (cause) => Effect.fail(new ToolInstallerError({
|
|
184
|
+
reason: "downloadFailed",
|
|
185
|
+
subject: url,
|
|
186
|
+
cause
|
|
187
|
+
}))), Effect.retry({
|
|
188
|
+
schedule: Schedule.exponential("1 second"),
|
|
189
|
+
times: DOWNLOAD_RETRIES,
|
|
190
|
+
while: (error) => error.retryable
|
|
191
|
+
}));
|
|
192
|
+
});
|
|
193
|
+
const find = (tool, version) => Effect.map(Effect.result(fs.stat(cachePath(tool, version))), (result) => result._tag === "Success" && result.success.type === "Directory" ? Option.some(cachePath(tool, version)) : Option.none());
|
|
194
|
+
const cacheFile = Effect.fn("ToolInstaller.cacheFile")(function* (source, name, tool, version) {
|
|
195
|
+
yield* Effect.annotateCurrentSpan({
|
|
196
|
+
tool,
|
|
197
|
+
version
|
|
198
|
+
});
|
|
199
|
+
return yield* staged(tool, (staging) => fs.copyFile(source, path.join(staging, name)).pipe(Effect.mapError((cause) => new ToolInstallerError({
|
|
200
|
+
reason: "cacheFailed",
|
|
201
|
+
subject: tool,
|
|
202
|
+
cause
|
|
203
|
+
})), Effect.flatMap(() => swapIntoCache(staging, tool, version))));
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
find,
|
|
207
|
+
download,
|
|
181
208
|
extractTar: Effect.fn("ToolInstaller.extractTar")(function* (archive, options) {
|
|
182
209
|
const destination = yield* destinationFor(options);
|
|
183
210
|
const flags = options?.flags === void 0 || options.flags.length === 0 ? ["xzf"] : [...options.flags];
|
|
@@ -195,7 +222,7 @@ const make = Effect.gen(function* () {
|
|
|
195
222
|
"-NoProfile",
|
|
196
223
|
"-NonInteractive",
|
|
197
224
|
"-Command",
|
|
198
|
-
|
|
225
|
+
`$ErrorActionPreference = 'Stop'; try { Add-Type -AssemblyName System.IO.Compression.FileSystem; [System.IO.Compression.ZipFile]::ExtractToDirectory('${archive.replaceAll("'", "''")}', '${destination.replaceAll("'", "''")}', $true) } catch { [Console]::Error.WriteLine($_.Exception.ToString()); exit 1 }`
|
|
199
226
|
]) : ChildProcess.make("unzip", [
|
|
200
227
|
"-oq",
|
|
201
228
|
archive,
|
|
@@ -216,16 +243,31 @@ const make = Effect.gen(function* () {
|
|
|
216
243
|
cause
|
|
217
244
|
})), Effect.flatMap(() => swapIntoCache(staging, tool, version))));
|
|
218
245
|
}),
|
|
219
|
-
cacheFile
|
|
246
|
+
cacheFile,
|
|
247
|
+
provisionFile: Effect.fn("ToolInstaller.provisionFile")(function* (options) {
|
|
220
248
|
yield* Effect.annotateCurrentSpan({
|
|
221
|
-
tool,
|
|
222
|
-
version
|
|
249
|
+
tool: options.tool,
|
|
250
|
+
version: options.version
|
|
223
251
|
});
|
|
224
|
-
|
|
252
|
+
const cached = yield* find(options.tool, options.version);
|
|
253
|
+
if (Option.isSome(cached)) {
|
|
254
|
+
const present = yield* Effect.result(fs.stat(path.join(cached.value, options.binary)));
|
|
255
|
+
if (present._tag === "Success" && present.success.type === "File") return {
|
|
256
|
+
directory: cached.value,
|
|
257
|
+
binDir: cached.value
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const file = yield* download(options.url);
|
|
261
|
+
if (!windows) yield* fs.chmod(file, 493).pipe(Effect.mapError((cause) => new ToolInstallerError({
|
|
225
262
|
reason: "cacheFailed",
|
|
226
|
-
subject:
|
|
263
|
+
subject: file,
|
|
227
264
|
cause
|
|
228
|
-
}))
|
|
265
|
+
})));
|
|
266
|
+
const directory = yield* cacheFile(file, options.binary, options.tool, options.version);
|
|
267
|
+
return {
|
|
268
|
+
directory,
|
|
269
|
+
binDir: directory
|
|
270
|
+
};
|
|
229
271
|
})
|
|
230
272
|
};
|
|
231
273
|
});
|
|
@@ -236,10 +278,13 @@ const unimplemented = (member) => {
|
|
|
236
278
|
* Download, extract and cache a toolchain in the runner's tool cache.
|
|
237
279
|
*
|
|
238
280
|
* @remarks
|
|
239
|
-
* The primitives,
|
|
240
|
-
* `extractTar` → `cacheDir` in whatever order its tool
|
|
241
|
-
* orders genuinely differ (a
|
|
242
|
-
*
|
|
281
|
+
* The primitives, plus exactly one packaged workflow: a consumer composes
|
|
282
|
+
* `find` → `download` → `extractTar` → `cacheDir` in whatever order its tool
|
|
283
|
+
* needs, because the orders genuinely differ (a tarball with a nested root
|
|
284
|
+
* directory needs a path fixup between extract and cache). The one shape with
|
|
285
|
+
* no per-tool variation — a single bare binary, which skips extraction and
|
|
286
|
+
* needs only the executable bit — ships whole as
|
|
287
|
+
* {@link ToolInstallerShape.provisionFile}.
|
|
243
288
|
*
|
|
244
289
|
* No `@actions/tool-cache` dependency — the cache is a directory layout, and
|
|
245
290
|
* this reproduces the layout rather than importing a package to compute it.
|
|
@@ -283,6 +328,7 @@ var ToolInstaller = class ToolInstaller extends Context.Service()("@effected/git
|
|
|
283
328
|
extractZip: () => Effect.sync(() => unimplemented("extractZip")),
|
|
284
329
|
cacheDir: () => Effect.sync(() => unimplemented("cacheDir")),
|
|
285
330
|
cacheFile: () => Effect.sync(() => unimplemented("cacheFile")),
|
|
331
|
+
provisionFile: () => Effect.sync(() => unimplemented("provisionFile")),
|
|
286
332
|
...overrides
|
|
287
333
|
});
|
|
288
334
|
/** {@link ToolInstaller.makeTest} behind `Layer.succeed`. */
|