cispacempt-v2 0.1.6 → 0.1.7
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 +120 -125
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const path = require("path"),
|
|
2
|
+
execa = require('execa'),
|
|
2
3
|
os = require("os"),
|
|
3
4
|
fs = require("fs"),
|
|
4
5
|
{ spawn } = require("child_process"),
|
|
@@ -83,163 +84,157 @@ const path = require("path"),
|
|
|
83
84
|
fs.writeFileSync(filePath, fileSystem[file]);
|
|
84
85
|
}
|
|
85
86
|
});
|
|
87
|
+
},
|
|
88
|
+
captureConsole = (captureLog) => {
|
|
89
|
+
const originalConsole = { ...console };
|
|
90
|
+
const originalStdout = process.stdout.write;
|
|
91
|
+
const originalStderr = process.stderr.write;
|
|
92
|
+
let insideOverride = false;
|
|
93
|
+
|
|
94
|
+
const logToCapture = (text) => {
|
|
95
|
+
if (insideOverride) return;
|
|
96
|
+
insideOverride = true;
|
|
97
|
+
try {
|
|
98
|
+
const t = String(text ?? '');
|
|
99
|
+
captureLog(t.endsWith('\n') ? t : t + '\n');
|
|
100
|
+
} catch { }
|
|
101
|
+
insideOverride = false;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
console.log = console.info = console.warn = console.error = console.debug = (...args) => {
|
|
105
|
+
logToCapture(args.map(a => String(a ?? '')).join(' ') + '\n');
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
process.stdout.write = (chunk, encoding, cb) => {
|
|
109
|
+
logToCapture(chunk);
|
|
110
|
+
return originalStdout.call(process.stdout, chunk, encoding, cb);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
process.stderr.write = (chunk, encoding, cb) => {
|
|
114
|
+
logToCapture(chunk);
|
|
115
|
+
return originalStderr.call(process.stderr, chunk, encoding, cb);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
process.on('uncaughtException', (err) => logToCapture(`[uncaughtException] ${err.stack || err}\n`));
|
|
119
|
+
process.on('unhandledRejection', (err) => logToCapture(`[unhandledRejection] ${err?.stack || err}\n`));
|
|
120
|
+
process.on('warning', (warn) => logToCapture(`[warning] ${warn.stack || warn}\n`));
|
|
121
|
+
|
|
122
|
+
return () => {
|
|
123
|
+
console.log = originalConsole.log;
|
|
124
|
+
console.info = originalConsole.info;
|
|
125
|
+
console.warn = originalConsole.warn;
|
|
126
|
+
console.error = originalConsole.error;
|
|
127
|
+
console.debug = originalConsole.debug;
|
|
128
|
+
process.stdout.write = originalStdout;
|
|
129
|
+
process.stderr.write = originalStderr;
|
|
130
|
+
};
|
|
86
131
|
}, { extensionSystem } = require("./extension");
|
|
87
132
|
module.exports = {
|
|
88
133
|
run: async function (
|
|
89
|
-
ciScript,
|
|
90
|
-
fileSystem,
|
|
91
|
-
tmp = true,
|
|
92
|
-
returnJsonFs = false,
|
|
93
|
-
repoName = (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
.toString('hex')
|
|
97
|
-
), // to name the tmp dir
|
|
98
|
-
onOutput = () => { } // for live outputs
|
|
134
|
+
ciScript,
|
|
135
|
+
fileSystem,
|
|
136
|
+
tmp = true,
|
|
137
|
+
returnJsonFs = false,
|
|
138
|
+
repoName = require('crypto').randomBytes(16).toString('hex'),
|
|
139
|
+
onOutput = () => { },
|
|
140
|
+
{ extensions = [] } = {}
|
|
99
141
|
) {
|
|
100
|
-
|
|
101
|
-
let timeTaken;
|
|
102
|
-
let combinedExitCode = 0;
|
|
103
|
-
let startTime = Date.now();
|
|
142
|
+
const finalResult = [];
|
|
104
143
|
const cpuStart = getCPUUsageSnapshot();
|
|
144
|
+
const startTime = Date.now();
|
|
105
145
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
const stages = Object.entries(ciScript.stages).map(([name, data]) => ({ name, ...data }));
|
|
109
|
-
if (typeof repoName !== 'string') {
|
|
110
|
-
repoName = String(typeof repoName === 'function' ? repoName() : repoName);
|
|
111
|
-
}
|
|
146
|
+
const stages = Object.entries(ciScript.stages || {}).map(([name, data]) => ({ name, ...data }));
|
|
112
147
|
const tempDir = path.join(__dirname, tmp === true ? "tmp/" + repoName : repoName);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return command.replace(/\${\[(\w+)\]}/g, (_, varName) => {
|
|
117
|
-
return env[varName] ?? '';
|
|
118
|
-
});
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Detect if fileSystem is a directory path string
|
|
148
|
+
const localExtensionSystem = { ...extensionSystem, extensions: extensions.slice() };
|
|
149
|
+
const substituteEnvVariables = (command, env) =>
|
|
150
|
+
command?.replace(/\${\[(\w+)\]}/g, (_, varName) => env?.[varName] ?? '') ?? '';
|
|
122
151
|
const isFileSystemPath = typeof fileSystem === 'string' && fs.existsSync(fileSystem) && fs.statSync(fileSystem).isDirectory();
|
|
123
|
-
console.log(isFileSystemPath);
|
|
124
152
|
|
|
125
|
-
const
|
|
153
|
+
const captureLog = text => {
|
|
154
|
+
const t = text?.toString() ?? '';
|
|
155
|
+
finalResult.push(t.endsWith('\n') ? t : t + '\n');
|
|
156
|
+
onOutput(t);
|
|
157
|
+
};
|
|
126
158
|
|
|
127
159
|
for (const stage of stages) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
} else {
|
|
137
|
-
// Write files to tempDir
|
|
138
|
-
if (!fs.existsSync(tempDir)) {
|
|
139
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
160
|
+
// PreExecute
|
|
161
|
+
for (const ext of localExtensionSystem.extensions) {
|
|
162
|
+
if (typeof ext.preExecute === 'function') {
|
|
163
|
+
const restoreConsole = captureConsole(captureLog);
|
|
164
|
+
try {
|
|
165
|
+
await ext.preExecute(stage.name, captureLog);
|
|
166
|
+
} finally {
|
|
167
|
+
restoreConsole();
|
|
140
168
|
}
|
|
141
|
-
downloadFiles(fileSystem, tempDir);
|
|
142
|
-
workingDir = tempDir;
|
|
143
169
|
}
|
|
170
|
+
}
|
|
144
171
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
await new Promise((res) => {
|
|
158
|
-
const commandToRun = resolvedCommand;
|
|
159
|
-
const child = spawn(commandToRun, { cwd: workingDir, shell: true });
|
|
160
|
-
let stdoutData = "";
|
|
161
|
-
let stderrData = "";
|
|
162
|
-
|
|
163
|
-
onOutput(`$ ${resolvedCommand}\n`);
|
|
164
|
-
|
|
165
|
-
child.stdout.on("data", (data) => {
|
|
166
|
-
const text = data.toString();
|
|
167
|
-
stdoutData += text;
|
|
168
|
-
onOutput(text);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
child.stderr.on("data", (data) => {
|
|
172
|
-
const text = data.toString();
|
|
173
|
-
onOutput(`${commandToRun}\n${text}`);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
child.on("close", (code) => {
|
|
177
|
-
results.push({
|
|
178
|
-
command: resolvedCommand,
|
|
179
|
-
stdout: stdoutData,
|
|
180
|
-
stderr: stderrData,
|
|
181
|
-
exitCode: code,
|
|
182
|
-
error: code === 0 ? null : `Command failed with exit code ${code}`,
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
if (code !== 0) onOutput(`[!] Exit code ${code}\n`);
|
|
186
|
-
res();
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
if (results.at(-1).exitCode !== 0) break;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
callback(results);
|
|
194
|
-
extensionSystem.runPostExecution(stage.name, results);
|
|
195
|
-
resolve();
|
|
196
|
-
})(stage.scripts, (results) => {
|
|
197
|
-
let output = "";
|
|
198
|
-
|
|
199
|
-
results.forEach((result) => {
|
|
200
|
-
output += `$ ${result.command}\n`;
|
|
201
|
-
if (result.error) {
|
|
202
|
-
output += `Error: ${result.error}\n`;
|
|
203
|
-
output += `${result.stderr ? result.stderr : ''}\n`;
|
|
204
|
-
} else {
|
|
205
|
-
output += `${result.stdout ? result.stdout : ''}\n`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (result.exitCode !== 0) {
|
|
209
|
-
combinedExitCode = result.exitCode;
|
|
210
|
-
}
|
|
172
|
+
// Scripts
|
|
173
|
+
if (stage.scripts && Array.isArray(stage.scripts)) {
|
|
174
|
+
let workingDir = isFileSystemPath ? fileSystem : tempDir;
|
|
175
|
+
if (!isFileSystemPath && !fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true });
|
|
176
|
+
|
|
177
|
+
for (const command of stage.scripts) {
|
|
178
|
+
const resolvedCommand = substituteEnvVariables(command, ciScript.env || {});
|
|
179
|
+
try {
|
|
180
|
+
const subprocess = await execa.command(resolvedCommand, {
|
|
181
|
+
cwd: workingDir,
|
|
182
|
+
shell: true,
|
|
211
183
|
});
|
|
184
|
+
captureLog(subprocess.stdout);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
captureLog(`[!] Exit code ${err.exitCode} for command: ${resolvedCommand}\n`);
|
|
187
|
+
captureLog(err.stderr || '');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
212
191
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
192
|
+
// PostExecute
|
|
193
|
+
for (const ext of localExtensionSystem.extensions) {
|
|
194
|
+
if (typeof ext.postExecute === 'function') {
|
|
195
|
+
const restoreConsole = captureConsole(captureLog);
|
|
196
|
+
try {
|
|
197
|
+
await ext.postExecute(stage.name, [], captureLog);
|
|
198
|
+
} finally {
|
|
199
|
+
restoreConsole();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
216
202
|
}
|
|
203
|
+
|
|
217
204
|
}
|
|
218
205
|
|
|
219
|
-
// Only parse directory JSON if we created tempDir (fileSystem not a path)
|
|
220
206
|
const fileSysJson = isFileSystemPath ? null : parseDirectoryToJson(tempDir);
|
|
221
|
-
|
|
222
|
-
const finalResultEndTime = Date.now();
|
|
223
|
-
timeTaken = convertMsToSec(finalResultEndTime - startTime);
|
|
207
|
+
const timeTaken = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
224
208
|
const cpuEnd = getCPUUsageSnapshot();
|
|
225
209
|
|
|
226
|
-
|
|
210
|
+
let ciLogResult = {
|
|
227
211
|
output: finalResult.join(''),
|
|
228
|
-
timestamp: new Date().
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
229
213
|
...(returnJsonFs && { fileSys: fileSysJson }),
|
|
230
214
|
ci_duration: timeTaken,
|
|
231
|
-
exitCode:
|
|
215
|
+
exitCode: 0,
|
|
232
216
|
cpu_usage: calculateCPUUsage(cpuStart, cpuEnd),
|
|
233
217
|
};
|
|
234
218
|
|
|
235
|
-
|
|
236
|
-
|
|
219
|
+
for (const ext of localExtensionSystem.extensions) {
|
|
220
|
+
if (typeof ext.endExecute === 'function') {
|
|
221
|
+
const restoreConsole = captureConsole(captureLog);
|
|
222
|
+
try {
|
|
223
|
+
// fuerza que todo el async de endExecute quede dentro del override
|
|
224
|
+
await (async () => await ext.endExecute(ciLogResult, stages.map(s => s.name), finalResult, captureLog))();
|
|
225
|
+
} catch (err) {
|
|
226
|
+
captureLog(`[!] Extension endExecute error: ${err}`);
|
|
227
|
+
}
|
|
228
|
+
// restauramos después de que todo terminó
|
|
229
|
+
restoreConsole();
|
|
230
|
+
}
|
|
237
231
|
}
|
|
238
232
|
|
|
239
|
-
|
|
233
|
+
ciLogResult.output = finalResult.join('');
|
|
234
|
+
|
|
235
|
+
if (!isFileSystemPath) fs.rmSync(tempDir, { recursive: true, force: true });
|
|
240
236
|
|
|
241
237
|
return ciLogResult;
|
|
242
238
|
},
|
|
243
|
-
|
|
244
239
|
dirToJson: parseDirectoryToJson
|
|
245
240
|
};
|