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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +120 -125
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.6",
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,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, // 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 }));
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
- // Helper to substitute env variables in commands
115
- const substituteEnvVariables = (command, env) => {
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 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
+ };
126
158
 
127
159
  for (const stage of stages) {
128
- extensionSystem.runPreExecution(stage.name);
129
-
130
- if (stage.scripts && Array.isArray(stage.scripts)) {
131
- let workingDir;
132
-
133
- if (isFileSystemPath) {
134
- // Use the existing directory directly
135
- workingDir = fileSystem;
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
- await new Promise((resolve) => {
146
- (async function (commands, callback) {
147
- const results = [];
148
-
149
- if (!stageExecuted[stage.name]) {
150
- onOutput(`[${stage.name}]\n`);
151
- stageExecuted[stage.name] = true;
152
- }
153
-
154
- for (const command of commands) {
155
- const resolvedCommand = substituteEnvVariables(command, ciScript.env || {});
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
- finalResult.push(output);
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
- const ciLogResult = {
210
+ let ciLogResult = {
227
211
  output: finalResult.join(''),
228
- timestamp: new Date().toString(),
212
+ timestamp: new Date().toISOString(),
229
213
  ...(returnJsonFs && { fileSys: fileSysJson }),
230
214
  ci_duration: timeTaken,
231
- exitCode: combinedExitCode,
215
+ exitCode: 0,
232
216
  cpu_usage: calculateCPUUsage(cpuStart, cpuEnd),
233
217
  };
234
218
 
235
- if (!isFileSystemPath) {
236
- 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
+ }
237
231
  }
238
232
 
239
- 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 });
240
236
 
241
237
  return ciLogResult;
242
238
  },
243
-
244
239
  dirToJson: parseDirectoryToJson
245
240
  };