Skip to content

Commit cd83b08

Browse files
committed
Add streaming API for zstd extraction
1 parent 5b6984e commit cd83b08

File tree

6 files changed

+80
-47
lines changed

6 files changed

+80
-47
lines changed

lib/actions-util.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/tar.js

+28-19
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/tar.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions-util.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ export class CommandInvocationError extends Error {
483483
constructor(
484484
public cmd: string,
485485
public args: string[],
486-
public exitCode: number,
486+
public exitCode: number | undefined,
487487
public stderr: string,
488488
public stdout: string,
489489
) {

src/cli-errors.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ConfigurationError } from "./util";
1010
* An error from a CodeQL CLI invocation, with associated exit code, stderr, etc.
1111
*/
1212
export class CliError extends Error {
13-
public readonly exitCode: number;
13+
public readonly exitCode: number | undefined;
1414
public readonly stderr: string;
1515

1616
constructor({ cmd, args, exitCode, stderr }: CommandInvocationError) {

src/tar.ts

+48-24
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import { spawn } from "child_process";
12
import * as fs from "fs";
23
import path from "path";
4+
import * as stream from "stream";
35

46
import { ToolRunner } from "@actions/exec/lib/toolrunner";
57
import * as toolcache from "@actions/tool-cache";
68
import { safeWhich } from "@chrisgavin/safe-which";
79
import { v4 as uuidV4 } from "uuid";
810

9-
import { getTemporaryDirectory, runTool } from "./actions-util";
11+
import { CommandInvocationError, getTemporaryDirectory } from "./actions-util";
1012
import { Logger } from "./logging";
1113
import { assertNever, cleanUpGlob } from "./util";
1214

@@ -123,7 +125,11 @@ export async function extract(
123125
"Could not determine tar version, which is required to extract a Zstandard archive.",
124126
);
125127
}
126-
return await extractTarZst(tarPath, tarVersion, logger);
128+
return await extractTarZst(
129+
fs.createReadStream(tarPath),
130+
tarVersion,
131+
logger,
132+
);
127133
}
128134
}
129135

@@ -135,46 +141,64 @@ export async function extract(
135141
* @returns path to the destination directory
136142
*/
137143
export async function extractTarZst(
138-
file: string,
144+
tarStream: stream.Readable,
139145
tarVersion: TarVersion,
140146
logger: Logger,
141147
): Promise<string> {
142-
if (!file) {
143-
throw new Error("parameter 'file' is required");
144-
}
145-
146-
// Create dest
147148
const dest = await createExtractFolder();
148149

149150
try {
150151
// Initialize args
151-
const args = ["-x", "-v"];
152-
153-
let destArg = dest;
154-
let fileArg = file;
155-
if (process.platform === "win32" && tarVersion.type === "gnu") {
156-
args.push("--force-local");
157-
destArg = dest.replace(/\\/g, "/");
158-
159-
// Technically only the dest needs to have `/` but for aesthetic consistency
160-
// convert slashes in the file arg too.
161-
fileArg = file.replace(/\\/g, "/");
162-
}
152+
const args = ["-x", "--zstd"];
163153

164154
if (tarVersion.type === "gnu") {
165155
// Suppress warnings when using GNU tar to extract archives created by BSD tar
166156
args.push("--warning=no-unknown-keyword");
167157
args.push("--overwrite");
168158
}
169159

170-
args.push("-C", destArg, "-f", fileArg);
171-
await runTool(`tar`, args);
160+
args.push("-f", "-", "-C", dest);
161+
162+
process.stdout.write(`[command]tar ${args.join(" ")}\n`);
163+
164+
const tarProcess = spawn("tar", args, { stdio: "pipe" });
165+
let stdout = "";
166+
tarProcess.stdout?.on("data", (data: Buffer) => {
167+
stdout += data.toString();
168+
process.stdout.write(data);
169+
});
170+
171+
let stderr = "";
172+
tarProcess.stderr?.on("data", (data: Buffer) => {
173+
stderr += data.toString();
174+
// Mimic the standard behavior of the toolrunner by writing stderr to stdout
175+
process.stdout.write(data);
176+
});
177+
178+
tarStream.pipe(tarProcess.stdin);
179+
180+
await new Promise<void>((resolve, reject) => {
181+
tarProcess.on("exit", (code) => {
182+
if (code !== 0) {
183+
reject(
184+
new CommandInvocationError(
185+
"tar",
186+
args,
187+
code ?? undefined,
188+
stdout,
189+
stderr,
190+
),
191+
);
192+
}
193+
resolve();
194+
});
195+
});
196+
197+
return dest;
172198
} catch (e) {
173199
await cleanUpGlob(dest, "extraction destination directory", logger);
174200
throw e;
175201
}
176-
177-
return dest;
178202
}
179203

180204
async function createExtractFolder(): Promise<string> {

0 commit comments

Comments
 (0)