cispacempt-v2 0.1.2 → 0.1.4
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/package.json +1 -1
- package/src/index.js +63 -210
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -2,92 +2,6 @@ const path = require("path"),
|
|
|
2
2
|
os = require("os"),
|
|
3
3
|
fs = require("fs"),
|
|
4
4
|
{ spawn } = require("child_process"),
|
|
5
|
-
yaml = require("js-yaml"),
|
|
6
|
-
https = require("https"),
|
|
7
|
-
{ spawnSync } = require("child_process"),
|
|
8
|
-
rootfsMap = {
|
|
9
|
-
debian: "https://github.com/EXALAB/Anlinux-Resources/blob/master/Rootfs/Debian/arm64/debian-rootfs-arm64.tar.xz",
|
|
10
|
-
alpine: "https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/alpine-minirootfs-3.19.1-x86_64.tar.gz",
|
|
11
|
-
ubuntu: "https://partner-images.canonical.com/core/focal/current/ubuntu-focal-core-cloudimg-amd64-root.tar.gz",
|
|
12
|
-
arch: "https://mirror.rackspace.com/archlinux/iso/latest/archlinux-bootstrap-x86_64.tar.gz",
|
|
13
|
-
void: "https://repo-default.voidlinux.org/live/current/void-x86_64-ROOTFS-20230628.tar.xz",
|
|
14
|
-
fedora: "https://download.fedoraproject.org/pub/fedora/linux/releases/39/Container/x86_64/images/Fedora-Container-Base-39-1.5.x86_64.tar.xz",
|
|
15
|
-
centos: "https://buildlogs.centos.org/centos/7/container/x86_64/images/CentOS-7-Container-7.9.2009-20210916.0.x86_64.tar.xz"
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const downloadFile = (url, dest) => new Promise((resolve, reject) => {
|
|
19
|
-
console.log(`Starting download from: ${url}`);
|
|
20
|
-
const file = fs.createWriteStream(dest);
|
|
21
|
-
|
|
22
|
-
const request = https.get(url, (response) => {
|
|
23
|
-
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
24
|
-
console.log(`Redirected to: ${response.headers.location}`);
|
|
25
|
-
// Si hay redirección, cerrar este stream y descargar la nueva url
|
|
26
|
-
file.close();
|
|
27
|
-
fs.unlinkSync(dest);
|
|
28
|
-
return downloadFile(response.headers.location, dest).then(resolve).catch(reject);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (response.statusCode !== 200) {
|
|
32
|
-
file.close();
|
|
33
|
-
fs.unlinkSync(dest);
|
|
34
|
-
return reject(new Error(`Failed to download file, status code: ${response.statusCode}`));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
response.pipe(file);
|
|
38
|
-
|
|
39
|
-
file.on("finish", () => {
|
|
40
|
-
console.log(`Download finished: ${dest}`);
|
|
41
|
-
file.close(resolve);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
request.on("error", (err) => {
|
|
46
|
-
console.error(`Download error: ${err.message}`);
|
|
47
|
-
file.close();
|
|
48
|
-
fs.unlinkSync(dest);
|
|
49
|
-
reject(err);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
file.on("error", (err) => {
|
|
53
|
-
console.error(`File write error: ${err.message}`);
|
|
54
|
-
file.close();
|
|
55
|
-
fs.unlinkSync(dest);
|
|
56
|
-
reject(err);
|
|
57
|
-
});
|
|
58
|
-
}),
|
|
59
|
-
prepareProotEnv = async (osName, cacheDir) => {
|
|
60
|
-
const prootBin = path.join(cacheDir, "proot");
|
|
61
|
-
const rootfsDir = path.join(cacheDir, `rootfs_${osName}`);
|
|
62
|
-
const tarball = path.join(cacheDir, `${osName}.tar.${osName.includes("xz") ? "xz" : "gz"}`);
|
|
63
|
-
|
|
64
|
-
// Crear el directorio cacheDir si no existe
|
|
65
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
66
|
-
|
|
67
|
-
// Descargar proot
|
|
68
|
-
if (!fs.existsSync(prootBin)) {
|
|
69
|
-
const url = "https://proot.gitlab.io/proot/bin/proot";
|
|
70
|
-
await downloadFile(url, prootBin);
|
|
71
|
-
fs.chmodSync(prootBin, 0o755);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Descargar rootfs
|
|
75
|
-
if (!fs.existsSync(rootfsDir)) {
|
|
76
|
-
const url = rootfsMap[osName];
|
|
77
|
-
if (!url) throw new Error(`Unsupported OS "${osName}"`);
|
|
78
|
-
await downloadFile(url, tarball);
|
|
79
|
-
|
|
80
|
-
fs.mkdirSync(rootfsDir, { recursive: true });
|
|
81
|
-
|
|
82
|
-
// Extraer el tarball
|
|
83
|
-
const extCmd = tarball.endsWith(".xz") ? "tar -xJf" : "tar -xzf";
|
|
84
|
-
spawnSync(extCmd, [tarball, "-C", rootfsDir], { shell: true });
|
|
85
|
-
|
|
86
|
-
fs.unlinkSync(tarball); // limpiar
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { prootBin, rootfsDir };
|
|
90
|
-
},
|
|
91
5
|
getCPUUsageSnapshot = function () {
|
|
92
6
|
const cpus = os.cpus();
|
|
93
7
|
return cpus.map(cpu => ({ ...cpu.times }));
|
|
@@ -169,40 +83,20 @@ const path = require("path"),
|
|
|
169
83
|
fs.writeFileSync(filePath, fileSystem[file]);
|
|
170
84
|
}
|
|
171
85
|
});
|
|
172
|
-
},
|
|
173
|
-
detectAvailableProotOSID = () => {
|
|
174
|
-
const baseDir = path.resolve(__dirname); // o __dirname donde esté el código
|
|
175
|
-
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
176
|
-
|
|
177
|
-
for (const entry of entries) {
|
|
178
|
-
if (entry.isDirectory() && entry.name.startsWith("rootfs-")) {
|
|
179
|
-
const osReleasePath = path.join(baseDir, entry.name, "etc", "os-release");
|
|
180
|
-
|
|
181
|
-
try {
|
|
182
|
-
const content = fs.readFileSync(osReleasePath, "utf8");
|
|
183
|
-
const match = content.match(/^ID="?([\w\-]+)"?/m);
|
|
184
|
-
|
|
185
|
-
if (match) {
|
|
186
|
-
return {
|
|
187
|
-
id: match[1], // ej. "debian", "alpine"
|
|
188
|
-
rootfsPath: path.join(baseDir, entry.name), // ruta completa
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
} catch (_) {
|
|
192
|
-
// No es un rootfs válido, seguimos con el siguiente
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return null; // No se encontró ninguno válido
|
|
198
86
|
}, { extensionSystem } = require("./extension");
|
|
199
|
-
|
|
200
|
-
// Export as a Node JS module
|
|
201
87
|
module.exports = {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
88
|
+
run: async function (
|
|
89
|
+
ciScript, // the script to run
|
|
90
|
+
fileSystem, // a path or a json representation of your file system (kinda like {"app.js":"console.log('Hi!');"})
|
|
91
|
+
tmp = true, // self explanatory; run in a temporary environment
|
|
92
|
+
returnJsonFs = false, // if you want a json representation of your fs (dont use this if your working on binaries)
|
|
93
|
+
repoName = (
|
|
94
|
+
require('crypto')
|
|
95
|
+
.randomBytes(16)
|
|
96
|
+
.toString('hex')
|
|
97
|
+
), // to name the tmp dir
|
|
98
|
+
onOutput = () => { } // for live outputs
|
|
99
|
+
) {
|
|
206
100
|
let finalResult = [];
|
|
207
101
|
let timeTaken;
|
|
208
102
|
let combinedExitCode = 0;
|
|
@@ -212,106 +106,68 @@ module.exports = {
|
|
|
212
106
|
const convertMsToSec = (ms) => (ms / 1000).toFixed(2);
|
|
213
107
|
|
|
214
108
|
const stages = Object.entries(ciScript.stages).map(([name, data]) => ({ name, ...data }));
|
|
215
|
-
const tempDir = path.join(__dirname, tmp
|
|
109
|
+
const tempDir = path.join(__dirname, tmp === true ? "tmp/" + repoName : repoName);
|
|
216
110
|
|
|
111
|
+
// Helper to substitute env variables in commands
|
|
217
112
|
const substituteEnvVariables = (command, env) => {
|
|
218
113
|
return command.replace(/\${\[(\w+)\]}/g, (_, varName) => {
|
|
219
114
|
return env[varName] ?? '';
|
|
220
115
|
});
|
|
221
116
|
};
|
|
222
117
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
let currentMap = fileSystem;
|
|
226
|
-
|
|
227
|
-
for (let i = 0; i < pathParts.length; i++) {
|
|
228
|
-
const part = pathParts[i];
|
|
118
|
+
// Detect if fileSystem is a directory path string
|
|
119
|
+
const isFileSystemPath = typeof fileSystem === 'string' && fs.existsSync(fileSystem) && fs.statSync(fileSystem).isDirectory();
|
|
229
120
|
|
|
230
|
-
|
|
231
|
-
if (typeof currentMap[part] === "object") {
|
|
232
|
-
currentMap = currentMap[part];
|
|
233
|
-
} else {
|
|
234
|
-
return currentMap[part]; // Return the found file if it exists
|
|
235
|
-
}
|
|
236
|
-
} else {
|
|
237
|
-
return null; // Return null if not found at all
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return null; // Return null if path doesn't exist
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (ciScript.include) {
|
|
245
|
-
for (const includePath of ciScript.include) {
|
|
246
|
-
const templateContent = findFileInMap(fileSystem, includePath);
|
|
247
|
-
if (!templateContent) {
|
|
248
|
-
console.warn(`Included file '${includePath}' not found`);
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const includedYaml = yaml.load(templateContent);
|
|
253
|
-
|
|
254
|
-
// Merge included content
|
|
255
|
-
ciScript.stages = {
|
|
256
|
-
...(includedYaml.stages || {}),
|
|
257
|
-
...(ciScript.stages || {})
|
|
258
|
-
};
|
|
259
|
-
ciScript.env = {
|
|
260
|
-
...(includedYaml.env || {}),
|
|
261
|
-
...(ciScript.env || {}),
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
121
|
+
const stageExecuted = {}; // Track executed stages
|
|
265
122
|
|
|
266
123
|
for (const stage of stages) {
|
|
267
|
-
extensionSystem.runPreExecution(stage.name);
|
|
124
|
+
extensionSystem.runPreExecution(stage.name);
|
|
268
125
|
|
|
269
126
|
if (stage.scripts && Array.isArray(stage.scripts)) {
|
|
270
|
-
|
|
271
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
272
|
-
}
|
|
127
|
+
let workingDir;
|
|
273
128
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
129
|
+
if (isFileSystemPath) {
|
|
130
|
+
// Use the existing directory directly
|
|
131
|
+
workingDir = fileSystem;
|
|
132
|
+
} else {
|
|
133
|
+
// Write files to tempDir
|
|
134
|
+
if (!fs.existsSync(tempDir)) {
|
|
135
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
downloadFiles(fileSystem, tempDir);
|
|
138
|
+
workingDir = tempDir;
|
|
139
|
+
}
|
|
284
140
|
|
|
285
141
|
await new Promise((resolve) => {
|
|
286
142
|
(async function (commands, callback) {
|
|
287
|
-
for (const fileName of Object.keys(installCommands)) {
|
|
288
|
-
const foundKey = Object.keys(parseDirectoryToJson(tempDir)).find(key => key.includes(fileName));
|
|
289
|
-
if (foundKey && !commands.includes(installCommands[fileName])) {
|
|
290
|
-
commands.push(installCommands[fileName]);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
143
|
const results = [];
|
|
294
144
|
|
|
145
|
+
if (!stageExecuted[stage.name]) {
|
|
146
|
+
onOutput(`[${stage.name}]\n`);
|
|
147
|
+
stageExecuted[stage.name] = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
295
150
|
for (const command of commands) {
|
|
296
151
|
const resolvedCommand = substituteEnvVariables(command, ciScript.env || {});
|
|
297
152
|
|
|
298
|
-
await new Promise(
|
|
153
|
+
await new Promise((res) => {
|
|
299
154
|
const commandToRun = resolvedCommand;
|
|
300
|
-
|
|
301
|
-
if (ciScript.os && Object.keys(rootfsMap || {}).includes(ciScript.os)) {
|
|
302
|
-
const { prootBin, rootfsDir } = await prepareProotEnv(ciScript.os, path.join(__dirname, "ci_proot_cache"));
|
|
303
|
-
console.log("PROOT BIN:", prootBin, fs.existsSync(prootBin));
|
|
304
|
-
|
|
305
|
-
finalCommand = `${prootBin} -R ${rootfsDir} -b ${tempDir}:/sandbox -w /sandbox /bin/sh -c "${commandToRun.replace(/"/g, '\\"')}"`;
|
|
306
|
-
} else {
|
|
307
|
-
finalCommand = commandToRun;
|
|
308
|
-
}
|
|
309
|
-
const child = spawn(finalCommand, { cwd: tempDir, shell: true });
|
|
155
|
+
const child = spawn(commandToRun, { cwd: workingDir, shell: true });
|
|
310
156
|
let stdoutData = "";
|
|
311
157
|
let stderrData = "";
|
|
312
158
|
|
|
313
|
-
|
|
314
|
-
|
|
159
|
+
onOutput(`$ ${resolvedCommand}\n`);
|
|
160
|
+
|
|
161
|
+
child.stdout.on("data", (data) => {
|
|
162
|
+
const text = data.toString();
|
|
163
|
+
stdoutData += text;
|
|
164
|
+
onOutput(text);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
child.stderr.on("data", (data) => {
|
|
168
|
+
const text = data.toString();
|
|
169
|
+
onOutput(`${commandToRun}\n${text}`);
|
|
170
|
+
});
|
|
315
171
|
|
|
316
172
|
child.on("close", (code) => {
|
|
317
173
|
results.push({
|
|
@@ -321,27 +177,28 @@ module.exports = {
|
|
|
321
177
|
exitCode: code,
|
|
322
178
|
error: code === 0 ? null : `Command failed with exit code ${code}`,
|
|
323
179
|
});
|
|
180
|
+
|
|
181
|
+
if (code !== 0) onOutput(`[!] Exit code ${code}\n`);
|
|
324
182
|
res();
|
|
325
183
|
});
|
|
326
184
|
});
|
|
327
185
|
|
|
328
186
|
if (results.at(-1).exitCode !== 0) break;
|
|
329
187
|
}
|
|
188
|
+
|
|
330
189
|
callback(results);
|
|
331
|
-
extensionSystem.runPostExecution(stage.name, results);
|
|
190
|
+
extensionSystem.runPostExecution(stage.name, results);
|
|
332
191
|
resolve();
|
|
333
192
|
})(stage.scripts, (results) => {
|
|
334
|
-
let output =
|
|
193
|
+
let output = "";
|
|
335
194
|
|
|
336
195
|
results.forEach((result) => {
|
|
337
196
|
output += `$ ${result.command}\n`;
|
|
338
197
|
if (result.error) {
|
|
339
198
|
output += `Error: ${result.error}\n`;
|
|
340
199
|
output += `${result.stderr ? result.stderr : ''}\n`;
|
|
341
|
-
!errorIn.includes(stage.name) ? errorIn.push(stage.name) : null;
|
|
342
200
|
} else {
|
|
343
201
|
output += `${result.stdout ? result.stdout : ''}\n`;
|
|
344
|
-
!successIn.includes(stage.name) ? successIn.push(stage.name) : null;
|
|
345
202
|
}
|
|
346
203
|
|
|
347
204
|
if (result.exitCode !== 0) {
|
|
@@ -355,31 +212,27 @@ module.exports = {
|
|
|
355
212
|
}
|
|
356
213
|
}
|
|
357
214
|
|
|
358
|
-
|
|
215
|
+
// Only parse directory JSON if we created tempDir (fileSystem not a path)
|
|
216
|
+
const fileSysJson = isFileSystemPath ? null : parseDirectoryToJson(tempDir);
|
|
217
|
+
|
|
359
218
|
const finalResultEndTime = Date.now();
|
|
360
219
|
timeTaken = convertMsToSec(finalResultEndTime - startTime);
|
|
361
220
|
const cpuEnd = getCPUUsageSnapshot();
|
|
362
221
|
|
|
363
222
|
const ciLogResult = {
|
|
364
223
|
output: finalResult.join(''),
|
|
365
|
-
timestamp:
|
|
366
|
-
fileSys:
|
|
224
|
+
timestamp: new Date().toString(),
|
|
225
|
+
...(returnJsonFs && { fileSys: fileSysJson }),
|
|
367
226
|
ci_duration: timeTaken,
|
|
368
227
|
exitCode: combinedExitCode,
|
|
369
228
|
cpu_usage: calculateCPUUsage(cpuStart, cpuEnd),
|
|
370
|
-
os: {
|
|
371
|
-
name: os.platform(),
|
|
372
|
-
version: os.release(),
|
|
373
|
-
arch: os.arch(),
|
|
374
|
-
id: detectAvailableProotOSID()?.id || 'ubuntu',
|
|
375
|
-
},
|
|
376
|
-
errorIn: errorIn.length > 0 ? errorIn : null,
|
|
377
|
-
successIn: successIn.length > 0 ? successIn : null,
|
|
378
229
|
};
|
|
379
230
|
|
|
380
|
-
|
|
231
|
+
if (!isFileSystemPath) {
|
|
232
|
+
cleanUpAndRespond(tempDir);
|
|
233
|
+
}
|
|
381
234
|
|
|
382
|
-
extensionSystem.runEnd(ciLogResult, stages.map(stage => stage.name), finalResult);
|
|
235
|
+
extensionSystem.runEnd(ciLogResult, stages.map(stage => stage.name), finalResult);
|
|
383
236
|
|
|
384
237
|
return ciLogResult;
|
|
385
238
|
},
|