cispacempt-v2 0.1.5 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +120 -122
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "main": "src/index.js",
3
3
  "name": "cispacempt-v2",
4
- "version": "0.1.5",
4
+ "version": "0.1.7",
5
5
  "description": "Fast CI engine for CodespaceMPT with script stages and file system hooks",
6
6
  "author": "argentidev",
7
7
  "license": "MIT",
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,160 +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, // 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
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
- let finalResult = [];
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 convertMsToSec = (ms) => (ms / 1000).toFixed(2);
107
-
108
- const stages = Object.entries(ciScript.stages).map(([name, data]) => ({ name, ...data }));
146
+ const stages = Object.entries(ciScript.stages || {}).map(([name, data]) => ({ name, ...data }));
109
147
  const tempDir = path.join(__dirname, tmp === true ? "tmp/" + repoName : repoName);
110
-
111
- // Helper to substitute env variables in commands
112
- const substituteEnvVariables = (command, env) => {
113
- return command.replace(/\${\[(\w+)\]}/g, (_, varName) => {
114
- return env[varName] ?? '';
115
- });
116
- };
117
-
118
- // 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] ?? '') ?? '';
119
151
  const isFileSystemPath = typeof fileSystem === 'string' && fs.existsSync(fileSystem) && fs.statSync(fileSystem).isDirectory();
120
- console.log(isFileSystemPath);
121
152
 
122
- const stageExecuted = {}; // Track executed stages
153
+ const captureLog = text => {
154
+ const t = text?.toString() ?? '';
155
+ finalResult.push(t.endsWith('\n') ? t : t + '\n');
156
+ onOutput(t);
157
+ };
123
158
 
124
159
  for (const stage of stages) {
125
- extensionSystem.runPreExecution(stage.name);
126
-
127
- if (stage.scripts && Array.isArray(stage.scripts)) {
128
- let workingDir;
129
-
130
- if (isFileSystemPath) {
131
- // Use the existing directory directly
132
- workingDir = fileSystem;
133
- } else {
134
- // Write files to tempDir
135
- if (!fs.existsSync(tempDir)) {
136
- 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();
137
168
  }
138
- downloadFiles(fileSystem, tempDir);
139
- workingDir = tempDir;
140
169
  }
170
+ }
141
171
 
142
- await new Promise((resolve) => {
143
- (async function (commands, callback) {
144
- const results = [];
145
-
146
- if (!stageExecuted[stage.name]) {
147
- onOutput(`[${stage.name}]\n`);
148
- stageExecuted[stage.name] = true;
149
- }
150
-
151
- for (const command of commands) {
152
- const resolvedCommand = substituteEnvVariables(command, ciScript.env || {});
153
-
154
- await new Promise((res) => {
155
- const commandToRun = resolvedCommand;
156
- const child = spawn(commandToRun, { cwd: workingDir, shell: true });
157
- let stdoutData = "";
158
- let stderrData = "";
159
-
160
- onOutput(`$ ${resolvedCommand}\n`);
161
-
162
- child.stdout.on("data", (data) => {
163
- const text = data.toString();
164
- stdoutData += text;
165
- onOutput(text);
166
- });
167
-
168
- child.stderr.on("data", (data) => {
169
- const text = data.toString();
170
- onOutput(`${commandToRun}\n${text}`);
171
- });
172
-
173
- child.on("close", (code) => {
174
- results.push({
175
- command: resolvedCommand,
176
- stdout: stdoutData,
177
- stderr: stderrData,
178
- exitCode: code,
179
- error: code === 0 ? null : `Command failed with exit code ${code}`,
180
- });
181
-
182
- if (code !== 0) onOutput(`[!] Exit code ${code}\n`);
183
- res();
184
- });
185
- });
186
-
187
- if (results.at(-1).exitCode !== 0) break;
188
- }
189
-
190
- callback(results);
191
- extensionSystem.runPostExecution(stage.name, results);
192
- resolve();
193
- })(stage.scripts, (results) => {
194
- let output = "";
195
-
196
- results.forEach((result) => {
197
- output += `$ ${result.command}\n`;
198
- if (result.error) {
199
- output += `Error: ${result.error}\n`;
200
- output += `${result.stderr ? result.stderr : ''}\n`;
201
- } else {
202
- output += `${result.stdout ? result.stdout : ''}\n`;
203
- }
204
-
205
- if (result.exitCode !== 0) {
206
- combinedExitCode = result.exitCode;
207
- }
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,
208
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
+ }
209
191
 
210
- finalResult.push(output);
211
- });
212
- });
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
+ }
213
202
  }
203
+
214
204
  }
215
205
 
216
- // Only parse directory JSON if we created tempDir (fileSystem not a path)
217
206
  const fileSysJson = isFileSystemPath ? null : parseDirectoryToJson(tempDir);
218
-
219
- const finalResultEndTime = Date.now();
220
- timeTaken = convertMsToSec(finalResultEndTime - startTime);
207
+ const timeTaken = ((Date.now() - startTime) / 1000).toFixed(2);
221
208
  const cpuEnd = getCPUUsageSnapshot();
222
209
 
223
- const ciLogResult = {
210
+ let ciLogResult = {
224
211
  output: finalResult.join(''),
225
- timestamp: new Date().toString(),
212
+ timestamp: new Date().toISOString(),
226
213
  ...(returnJsonFs && { fileSys: fileSysJson }),
227
214
  ci_duration: timeTaken,
228
- exitCode: combinedExitCode,
215
+ exitCode: 0,
229
216
  cpu_usage: calculateCPUUsage(cpuStart, cpuEnd),
230
217
  };
231
218
 
232
- if (!isFileSystemPath) {
233
- cleanUpAndRespond(tempDir);
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
+ }
234
231
  }
235
232
 
236
- extensionSystem.runEnd(ciLogResult, stages.map(stage => stage.name), finalResult);
233
+ ciLogResult.output = finalResult.join('');
234
+
235
+ if (!isFileSystemPath) fs.rmSync(tempDir, { recursive: true, force: true });
237
236
 
238
237
  return ciLogResult;
239
238
  },
240
-
241
239
  dirToJson: parseDirectoryToJson
242
240
  };