@red-hat-developer-hub/e2e-test-utils 2.1.15 → 2.1.17
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/dist/deployment/rhdh/config/common/dynamic-plugins.yaml +5 -0
- package/dist/deployment/rhdh/constants.d.ts +0 -5
- package/dist/deployment/rhdh/constants.d.ts.map +1 -1
- package/dist/deployment/rhdh/constants.js +0 -5
- package/dist/deployment/rhdh/deployment.d.ts +2 -2
- package/dist/deployment/rhdh/deployment.d.ts.map +1 -1
- package/dist/deployment/rhdh/deployment.js +9 -25
- package/dist/deployment/rhdh/deployment.test.js +9 -9
- package/dist/deployment/rhdh/types.d.ts +0 -4
- package/dist/deployment/rhdh/types.d.ts.map +1 -1
- package/dist/playwright/helpers/github-session.test.js +1 -1
- package/dist/secrets/bitwarden.d.ts +43 -0
- package/dist/secrets/bitwarden.d.ts.map +1 -1
- package/dist/secrets/bitwarden.js +465 -53
- package/dist/secrets/cli.d.ts +48 -1
- package/dist/secrets/cli.d.ts.map +1 -1
- package/dist/secrets/cli.js +455 -28
- package/dist/secrets/command.d.ts +7 -0
- package/dist/secrets/command.d.ts.map +1 -1
- package/dist/secrets/command.js +103 -26
- package/dist/secrets/config.d.ts +11 -1
- package/dist/secrets/config.d.ts.map +1 -1
- package/dist/secrets/config.js +32 -1
- package/dist/secrets/environment-name.d.ts +2 -0
- package/dist/secrets/environment-name.d.ts.map +1 -0
- package/dist/secrets/environment-name.js +4 -0
- package/dist/secrets/environment.d.ts +10 -0
- package/dist/secrets/environment.d.ts.map +1 -1
- package/dist/secrets/environment.js +35 -4
- package/dist/secrets/exec.d.ts +9 -1
- package/dist/secrets/exec.d.ts.map +1 -1
- package/dist/secrets/exec.js +318 -38
- package/dist/secrets/gsm-wrapper.d.ts +42 -0
- package/dist/secrets/gsm-wrapper.d.ts.map +1 -0
- package/dist/secrets/gsm-wrapper.js +238 -0
- package/dist/secrets/gsm.d.ts +33 -0
- package/dist/secrets/gsm.d.ts.map +1 -0
- package/dist/secrets/gsm.js +172 -0
- package/dist/secrets/index.d.ts +9 -4
- package/dist/secrets/index.d.ts.map +1 -1
- package/dist/secrets/index.js +7 -2
- package/dist/secrets/lock.d.ts +3 -0
- package/dist/secrets/lock.d.ts.map +1 -0
- package/dist/secrets/lock.js +28 -0
- package/dist/secrets/mutation.d.ts +71 -0
- package/dist/secrets/mutation.d.ts.map +1 -0
- package/dist/secrets/mutation.js +210 -0
- package/dist/secrets/secret-input.d.ts +17 -0
- package/dist/secrets/secret-input.d.ts.map +1 -0
- package/dist/secrets/secret-input.js +40 -0
- package/dist/secrets/stream.d.ts +9 -0
- package/dist/secrets/stream.d.ts.map +1 -0
- package/dist/secrets/stream.js +211 -0
- package/dist/secrets/temporary-secret.d.ts +13 -0
- package/dist/secrets/temporary-secret.d.ts.map +1 -0
- package/dist/secrets/temporary-secret.js +87 -0
- package/dist/secrets/tests/bitwarden.test.js +640 -0
- package/dist/secrets/tests/cli.integration.test.js +131 -3
- package/dist/secrets/tests/cli.test.js +281 -6
- package/dist/secrets/tests/command.test.d.ts +2 -0
- package/dist/secrets/tests/command.test.d.ts.map +1 -0
- package/dist/secrets/tests/command.test.js +39 -0
- package/dist/secrets/tests/config.test.js +20 -1
- package/dist/secrets/tests/environment.test.js +145 -3
- package/dist/secrets/tests/exec.test.js +469 -2
- package/dist/secrets/tests/gsm-wrapper.test.d.ts +2 -0
- package/dist/secrets/tests/gsm-wrapper.test.d.ts.map +1 -0
- package/dist/secrets/tests/gsm-wrapper.test.js +166 -0
- package/dist/secrets/tests/gsm.test.d.ts +2 -0
- package/dist/secrets/tests/gsm.test.d.ts.map +1 -0
- package/dist/secrets/tests/gsm.test.js +181 -0
- package/dist/secrets/tests/mutation.test.d.ts +2 -0
- package/dist/secrets/tests/mutation.test.d.ts.map +1 -0
- package/dist/secrets/tests/mutation.test.js +390 -0
- package/dist/secrets/tests/secret-input.test.d.ts +2 -0
- package/dist/secrets/tests/secret-input.test.d.ts.map +1 -0
- package/dist/secrets/tests/secret-input.test.js +71 -0
- package/dist/secrets/tests/stream.test.d.ts +2 -0
- package/dist/secrets/tests/stream.test.d.ts.map +1 -0
- package/dist/secrets/tests/stream.test.js +88 -0
- package/dist/secrets/tests/temporary-secret.test.d.ts +2 -0
- package/dist/secrets/tests/temporary-secret.test.d.ts.map +1 -0
- package/dist/secrets/tests/temporary-secret.test.js +76 -0
- package/dist/utils/plugin-metadata.d.ts +5 -0
- package/dist/utils/plugin-metadata.d.ts.map +1 -1
- package/dist/utils/plugin-metadata.js +19 -0
- package/dist/utils/tests/plugin-metadata.test.js +17 -1
- package/dist/utils/workspace-paths.d.ts +0 -2
- package/dist/utils/workspace-paths.d.ts.map +1 -1
- package/dist/utils/workspace-paths.js +0 -4
- package/package.json +1 -1
- package/dist/deployment/rhdh/config/new-frontend-system/dynamic-plugins.yaml +0 -5
- package/dist/deployment/rhdh/config/new-frontend-system/value_file.yaml +0 -2
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { isValidEnvironmentName } from "./environment-name.js";
|
|
2
|
+
export const SECRET_STREAM_ENVIRONMENT_VARIABLE = "RHDH_E2E_SECRET_FD";
|
|
3
|
+
export const SECRET_STREAM_FD = 3;
|
|
4
|
+
const HEADER = new TextEncoder().encode("RHDHSEC1");
|
|
5
|
+
const FOOTER = new TextEncoder().encode("RHDHEND1");
|
|
6
|
+
const HEADER_BYTES = HEADER.byteLength + 4;
|
|
7
|
+
const FOOTER_BYTES = FOOTER.byteLength;
|
|
8
|
+
const ENTRY_FRAME_BYTES = 8;
|
|
9
|
+
const MAX_ENTRIES = 65535;
|
|
10
|
+
const MAX_FIELD_BYTES = 8 * 1024 * 1024;
|
|
11
|
+
const MAX_STREAM_BYTES = 64 * 1024 * 1024;
|
|
12
|
+
const UTF8_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
13
|
+
export async function writeSecretStream(stream, entries) {
|
|
14
|
+
const encodedEntries = validateEntries(entries);
|
|
15
|
+
const header = new Uint8Array(HEADER_BYTES);
|
|
16
|
+
header.set(HEADER);
|
|
17
|
+
writeUint32(header, HEADER.byteLength, encodedEntries.length);
|
|
18
|
+
await writeChunk(stream, header);
|
|
19
|
+
for (const entry of encodedEntries) {
|
|
20
|
+
const frame = new Uint8Array(ENTRY_FRAME_BYTES);
|
|
21
|
+
writeUint32(frame, 0, entry.nameBytes);
|
|
22
|
+
writeUint32(frame, 4, entry.valueBytes);
|
|
23
|
+
await writeChunk(stream, frame);
|
|
24
|
+
await writeChunk(stream, new TextEncoder().encode(entry.name));
|
|
25
|
+
await writeChunk(stream, new TextEncoder().encode(entry.value));
|
|
26
|
+
}
|
|
27
|
+
await writeChunk(stream, FOOTER);
|
|
28
|
+
await endStream(stream);
|
|
29
|
+
}
|
|
30
|
+
export function decodeSecretStream(input) {
|
|
31
|
+
if (input.byteLength > MAX_STREAM_BYTES) {
|
|
32
|
+
throw new Error("Secret stream exceeds the maximum size");
|
|
33
|
+
}
|
|
34
|
+
let offset = 0;
|
|
35
|
+
if (input.byteLength < HEADER_BYTES) {
|
|
36
|
+
throw new Error("Secret stream header is truncated");
|
|
37
|
+
}
|
|
38
|
+
if (!sameBytes(input.subarray(0, HEADER.byteLength), HEADER)) {
|
|
39
|
+
throw new Error("Invalid secret stream header");
|
|
40
|
+
}
|
|
41
|
+
offset += HEADER.byteLength;
|
|
42
|
+
const count = readUint32(input, offset);
|
|
43
|
+
offset += 4;
|
|
44
|
+
if (count > MAX_ENTRIES) {
|
|
45
|
+
throw new Error("Secret stream contains too many entries");
|
|
46
|
+
}
|
|
47
|
+
const entries = [];
|
|
48
|
+
const names = new Set();
|
|
49
|
+
for (let index = 0; index < count; index++) {
|
|
50
|
+
if (input.byteLength - offset < ENTRY_FRAME_BYTES) {
|
|
51
|
+
throw new Error("Secret stream entry frame is truncated");
|
|
52
|
+
}
|
|
53
|
+
const nameBytes = readUint32(input, offset);
|
|
54
|
+
const valueBytes = readUint32(input, offset + 4);
|
|
55
|
+
offset += ENTRY_FRAME_BYTES;
|
|
56
|
+
validateFieldSize(nameBytes);
|
|
57
|
+
validateFieldSize(valueBytes);
|
|
58
|
+
const payloadBytes = nameBytes + valueBytes;
|
|
59
|
+
if (input.byteLength - offset < payloadBytes) {
|
|
60
|
+
throw new Error("Secret stream entry payload is truncated");
|
|
61
|
+
}
|
|
62
|
+
const name = decodeUtf8(input.subarray(offset, offset + nameBytes));
|
|
63
|
+
offset += nameBytes;
|
|
64
|
+
const value = decodeUtf8(input.subarray(offset, offset + valueBytes));
|
|
65
|
+
offset += valueBytes;
|
|
66
|
+
validateEntry(name, value, names);
|
|
67
|
+
entries.push({ name, value });
|
|
68
|
+
}
|
|
69
|
+
if (input.byteLength - offset < FOOTER_BYTES) {
|
|
70
|
+
throw new Error("Secret stream footer is truncated");
|
|
71
|
+
}
|
|
72
|
+
if (!sameBytes(input.subarray(offset, offset + FOOTER_BYTES), FOOTER)) {
|
|
73
|
+
throw new Error("Invalid secret stream footer");
|
|
74
|
+
}
|
|
75
|
+
offset += FOOTER_BYTES;
|
|
76
|
+
if (offset !== input.byteLength) {
|
|
77
|
+
throw new Error("Secret stream contains trailing data");
|
|
78
|
+
}
|
|
79
|
+
return entries;
|
|
80
|
+
}
|
|
81
|
+
function validateEntries(entries) {
|
|
82
|
+
if (entries.length > MAX_ENTRIES) {
|
|
83
|
+
throw new Error("Secret stream contains too many entries");
|
|
84
|
+
}
|
|
85
|
+
const names = new Set();
|
|
86
|
+
const encodedEntries = [];
|
|
87
|
+
let totalBytes = HEADER_BYTES + FOOTER_BYTES;
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
if (typeof entry !== "object" ||
|
|
90
|
+
entry === null ||
|
|
91
|
+
typeof entry.name !== "string" ||
|
|
92
|
+
typeof entry.value !== "string") {
|
|
93
|
+
throw new Error("Secret stream entry must contain a name and value");
|
|
94
|
+
}
|
|
95
|
+
validateEntry(entry.name, entry.value, names);
|
|
96
|
+
const nameBytes = new TextEncoder().encode(entry.name).byteLength;
|
|
97
|
+
const valueBytes = new TextEncoder().encode(entry.value).byteLength;
|
|
98
|
+
validateFieldSize(nameBytes);
|
|
99
|
+
validateFieldSize(valueBytes);
|
|
100
|
+
totalBytes += ENTRY_FRAME_BYTES + nameBytes + valueBytes;
|
|
101
|
+
if (totalBytes > MAX_STREAM_BYTES) {
|
|
102
|
+
throw new Error("Secret stream exceeds the maximum size");
|
|
103
|
+
}
|
|
104
|
+
encodedEntries.push({ ...entry, nameBytes, valueBytes });
|
|
105
|
+
}
|
|
106
|
+
return encodedEntries.sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
|
|
107
|
+
}
|
|
108
|
+
function validateEntry(name, value, names) {
|
|
109
|
+
if (name.includes("\0") || value.includes("\0")) {
|
|
110
|
+
throw new Error("Secret stream entries cannot contain NUL bytes");
|
|
111
|
+
}
|
|
112
|
+
if (!isValidEnvironmentName(name)) {
|
|
113
|
+
throw new Error("Secret stream entry has an invalid environment name");
|
|
114
|
+
}
|
|
115
|
+
if (names.has(name)) {
|
|
116
|
+
throw new Error("Secret stream contains duplicate entry names");
|
|
117
|
+
}
|
|
118
|
+
names.add(name);
|
|
119
|
+
}
|
|
120
|
+
function validateFieldSize(byteLength) {
|
|
121
|
+
if (byteLength > MAX_FIELD_BYTES) {
|
|
122
|
+
throw new Error("Secret stream field exceeds the maximum size");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function decodeUtf8(input) {
|
|
126
|
+
try {
|
|
127
|
+
return UTF8_DECODER.decode(input);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new Error("Secret stream contains invalid UTF-8");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function writeUint32(target, offset, value) {
|
|
134
|
+
target[offset] = value >>> 24;
|
|
135
|
+
target[offset + 1] = value >>> 16;
|
|
136
|
+
target[offset + 2] = value >>> 8;
|
|
137
|
+
target[offset + 3] = value;
|
|
138
|
+
}
|
|
139
|
+
function readUint32(input, offset) {
|
|
140
|
+
return (input[offset] * 0x1000000 +
|
|
141
|
+
input[offset + 1] * 0x10000 +
|
|
142
|
+
input[offset + 2] * 0x100 +
|
|
143
|
+
input[offset + 3]);
|
|
144
|
+
}
|
|
145
|
+
function sameBytes(left, right) {
|
|
146
|
+
if (left.byteLength !== right.byteLength)
|
|
147
|
+
return false;
|
|
148
|
+
return left.every((byte, index) => byte === right[index]);
|
|
149
|
+
}
|
|
150
|
+
function writeChunk(stream, chunk) {
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
let settled = false;
|
|
153
|
+
const cleanup = () => {
|
|
154
|
+
stream.removeListener("drain", onDrain);
|
|
155
|
+
stream.removeListener("error", onError);
|
|
156
|
+
};
|
|
157
|
+
const resolveOnce = () => {
|
|
158
|
+
if (settled)
|
|
159
|
+
return;
|
|
160
|
+
settled = true;
|
|
161
|
+
cleanup();
|
|
162
|
+
resolve();
|
|
163
|
+
};
|
|
164
|
+
const rejectOnce = () => {
|
|
165
|
+
if (settled)
|
|
166
|
+
return;
|
|
167
|
+
settled = true;
|
|
168
|
+
cleanup();
|
|
169
|
+
reject(new Error("Unable to write secret stream"));
|
|
170
|
+
};
|
|
171
|
+
const onDrain = () => resolveOnce();
|
|
172
|
+
const onError = () => rejectOnce();
|
|
173
|
+
stream.once("drain", onDrain);
|
|
174
|
+
stream.once("error", onError);
|
|
175
|
+
try {
|
|
176
|
+
if (stream.write(chunk))
|
|
177
|
+
resolveOnce();
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
rejectOnce();
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function endStream(stream) {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
let settled = false;
|
|
187
|
+
const cleanup = () => stream.removeListener("error", onError);
|
|
188
|
+
const resolveOnce = () => {
|
|
189
|
+
if (settled)
|
|
190
|
+
return;
|
|
191
|
+
settled = true;
|
|
192
|
+
cleanup();
|
|
193
|
+
resolve();
|
|
194
|
+
};
|
|
195
|
+
const rejectOnce = () => {
|
|
196
|
+
if (settled)
|
|
197
|
+
return;
|
|
198
|
+
settled = true;
|
|
199
|
+
cleanup();
|
|
200
|
+
reject(new Error("Unable to close secret stream"));
|
|
201
|
+
};
|
|
202
|
+
const onError = () => rejectOnce();
|
|
203
|
+
stream.once("error", onError);
|
|
204
|
+
try {
|
|
205
|
+
stream.end(resolveOnce);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
rejectOnce();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface TemporarySecretFile {
|
|
2
|
+
path: string;
|
|
3
|
+
remove(): Promise<void>;
|
|
4
|
+
}
|
|
5
|
+
export interface TemporarySecretFileOptions {
|
|
6
|
+
rootDirectory?: string;
|
|
7
|
+
processId?: number;
|
|
8
|
+
processExists?: (processId: number) => boolean;
|
|
9
|
+
removeDirectory?: (directory: string) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function defaultTemporarySecretRoot(): string;
|
|
12
|
+
export declare function createTemporarySecretFile(value: string, fileName: string, options?: TemporarySecretFileOptions): Promise<TemporarySecretFile>;
|
|
13
|
+
//# sourceMappingURL=temporary-secret.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporary-secret.d.ts","sourceRoot":"","sources":["../../src/secrets/temporary-secret.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IAC/C,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD;AAED,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,mBAAmB,CAAC,CA2C9B"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { chmod, lstat, mkdir, mkdtemp, readdir, rm, stat, writeFile, } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const TEMPORARY_SECRET_DIRECTORY = "rhdh-e2e-secrets";
|
|
5
|
+
export function defaultTemporarySecretRoot() {
|
|
6
|
+
return path.join(os.tmpdir(), TEMPORARY_SECRET_DIRECTORY);
|
|
7
|
+
}
|
|
8
|
+
export async function createTemporarySecretFile(value, fileName, options = {}) {
|
|
9
|
+
if (!fileName || path.basename(fileName) !== fileName) {
|
|
10
|
+
throw new Error(`Temporary secret filename is unsafe: ${fileName}`);
|
|
11
|
+
}
|
|
12
|
+
const rootDirectory = path.resolve(options.rootDirectory ?? defaultTemporarySecretRoot());
|
|
13
|
+
const processId = options.processId ?? process.pid;
|
|
14
|
+
const processExists = options.processExists ?? isProcessAlive;
|
|
15
|
+
const removeDirectory = options.removeDirectory ??
|
|
16
|
+
((directory) => rm(directory, { recursive: true, force: true }));
|
|
17
|
+
await ensureSecureDirectory(rootDirectory);
|
|
18
|
+
await removeStaleDirectories(rootDirectory, processId, processExists);
|
|
19
|
+
const directory = await mkdtemp(path.join(rootDirectory, `${processId}-`));
|
|
20
|
+
try {
|
|
21
|
+
await chmod(directory, 0o700);
|
|
22
|
+
await writeFile(path.join(directory, fileName), value, {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
mode: 0o600,
|
|
25
|
+
});
|
|
26
|
+
await chmod(path.join(directory, fileName), 0o600);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
try {
|
|
30
|
+
await removeDirectory(directory);
|
|
31
|
+
}
|
|
32
|
+
catch (cleanupError) {
|
|
33
|
+
throw new AggregateError([error], "Temporary secret creation and cleanup failed", { cause: cleanupError });
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const filePath = path.join(directory, fileName);
|
|
38
|
+
return {
|
|
39
|
+
path: filePath,
|
|
40
|
+
remove: () => removeDirectory(directory),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function ensureSecureDirectory(directory) {
|
|
44
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
45
|
+
const details = await lstat(directory);
|
|
46
|
+
if (details.isSymbolicLink()) {
|
|
47
|
+
throw new Error(`Temporary secret directory must not be a symlink: ${directory}`);
|
|
48
|
+
}
|
|
49
|
+
if (!details.isDirectory()) {
|
|
50
|
+
throw new Error(`Temporary secret path is not a directory: ${directory}`);
|
|
51
|
+
}
|
|
52
|
+
const userId = process.getuid?.();
|
|
53
|
+
if (userId !== undefined && details.uid !== userId) {
|
|
54
|
+
throw new Error(`Temporary secret directory has the wrong owner: ${directory}`);
|
|
55
|
+
}
|
|
56
|
+
await chmod(directory, 0o700);
|
|
57
|
+
const secured = await stat(directory);
|
|
58
|
+
if ((secured.mode & 0o777) !== 0o700) {
|
|
59
|
+
throw new Error(`Temporary secret directory is not private: ${directory}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function removeStaleDirectories(rootDirectory, currentProcessId, processExists) {
|
|
63
|
+
const entries = await readdir(rootDirectory, { withFileTypes: true });
|
|
64
|
+
await Promise.all(entries.map(async (entry) => {
|
|
65
|
+
if (!entry.isDirectory() || entry.isSymbolicLink())
|
|
66
|
+
return;
|
|
67
|
+
const match = /^(\d+)-/.exec(entry.name);
|
|
68
|
+
if (!match)
|
|
69
|
+
return;
|
|
70
|
+
const processId = Number(match[1]);
|
|
71
|
+
if (processId === currentProcessId || processExists(processId))
|
|
72
|
+
return;
|
|
73
|
+
await rm(path.join(rootDirectory, entry.name), {
|
|
74
|
+
recursive: true,
|
|
75
|
+
force: true,
|
|
76
|
+
});
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
function isProcessAlive(processId) {
|
|
80
|
+
try {
|
|
81
|
+
process.kill(processId, 0);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return error.code === "EPERM";
|
|
86
|
+
}
|
|
87
|
+
}
|