cispacempt-v2 0.0.1

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.
@@ -0,0 +1 @@
1
+ codespacempt_ci
package/.csmptignore ADDED
@@ -0,0 +1,3 @@
1
+ tmp/
2
+ test.test.js
3
+ // jfksdhfkjshfjskdhfkjhdfkjhsfdkjfhds
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # CodespaceMPT CI
2
+
3
+ **CodespaceMPT CI** (CISpaceMPT) is a fast, efficient, and scalable Continuous Integration (CI) server that automates the execution of scripts in various stages of your CI pipeline. It is designed to integrate seamlessly with your repository, run defined commands, and log detailed results.
4
+
5
+ ## Installation
6
+
7
+ ## How It Works
8
+
9
+ ### CI Script Structure
10
+
11
+ Your CI pipeline is defined in a JSON-based script. Each CI script contains several stages, each with its own set of commands to run. Below is an example of how to structure the CI script:
12
+
13
+ ```json
14
+ {
15
+ "stages": {
16
+ "build": {
17
+ "scripts": [
18
+ "npm install",
19
+ "npm run build"
20
+ ]
21
+ },
22
+ "test": {
23
+ "scripts": [
24
+ "npm test"
25
+ ]
26
+ }
27
+ }
28
+ }
29
+ ```
30
+
31
+ - **stages**: Defines the different stages of the CI pipeline.
32
+ - **scripts**: Lists the commands to be executed in each stage.
33
+
34
+ ### Execution Flow
35
+
36
+ 1. **Clone Repository**: The CI server clones the repository and checks out the required branch.
37
+ 2. **Parse Directory**: The file system of the repository is recursively traversed (excluding `node_modules`), capturing the directory structure and content.
38
+ 3. **Stage Execution**: For each stage defined in the CI script, the server executes the specified commands sequentially.
39
+ 4. **Log Results**: The output (stdout, stderr) from each command is captured and logged along with any errors and exit codes.
40
+ 5. **Time Tracking**: The duration of the CI process is calculated and logged.
41
+ 6. **Clean-up**: Once the pipeline is complete, temporary directories are deleted.
42
+
43
+ ### Example Usage
44
+
45
+ Here’s how you can use the `parse` function to execute a CI script:
46
+
47
+ ```js
48
+ const ci = require('codespacempt-ci');
49
+
50
+ // Sample CI script
51
+ const ciScript = {
52
+ stages: {
53
+ "build": {
54
+ scripts: [
55
+ "npm install",
56
+ "npm run build"
57
+ ]
58
+ },
59
+ "test": {
60
+ scripts: [
61
+ "npm test"
62
+ ]
63
+ }
64
+ }
65
+ };
66
+
67
+ // Repository name
68
+ const repoName = "my-repo";
69
+
70
+ // File system content to be used during execution
71
+ const fileSystem = {/* your repository's file system in a json */};
72
+
73
+ ci.parse(ciScript, repoName, fileSystem)
74
+ .then(result => {
75
+ console.log("CI Log Result:", result);
76
+ })
77
+ .catch(err => {
78
+ console.error("CI process failed:", err);
79
+ });
80
+ ```
81
+
82
+ ### Output
83
+
84
+ The `parse` function returns an object containing the CI process result:
85
+
86
+ ```json
87
+ {
88
+ "output": "CI logs for each stage",
89
+ "timestamp": "2025-04-14 12:00:00",
90
+ "fileSys": { /* Parsed file system after ci */ },
91
+ "ci_duration": "10.23", // Duration in seconds
92
+ "exitCode": 0 // Exit code (0 for success, non-zero for failure)
93
+ }
94
+ ```
95
+
96
+ - **output**: Logs from each stage.
97
+ - **timestamp**: The timestamp of when the CI process was run.
98
+ - **fileSys**: A JSON object representing the repository’s file structure.
99
+ - **ci_duration**: Total time taken for the CI process (in seconds).
100
+ - **exitCode**: The exit code from the final stage (0 if all commands succeed).
101
+
102
+ ### Clean-Up
103
+
104
+ After the execution, temporary directories are automatically cleaned up to save disk space.
105
+
106
+ ## License
107
+
108
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "main": "src/index.js",
3
+ "name": "cispacempt-v2",
4
+ "version": "0.0.1",
5
+ "description": "Fast CI engine for CodespaceMPT with script stages and file system hooks",
6
+ "author": "argentidev",
7
+ "license": "MIT",
8
+ "homepage": "https://codespacemptio.onrender.com/home",
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "repository": {
13
+ "type": "csmpt",
14
+ "url": "https://codespacemptio.onrender.com/codespacempt/ci"
15
+ },
16
+ "keywords": [],
17
+ "dependencies": {
18
+ "typescript": "^5.8.3"
19
+ }
20
+ }
@@ -0,0 +1,31 @@
1
+ // Extension system
2
+ const extensionSystem = {
3
+ extensions: [],
4
+ loadExtension: function (extension) {
5
+ this.extensions.push(extension);
6
+ },
7
+ runPreExecution: function (stageName) {
8
+ this.extensions.forEach((extension) => {
9
+ if (extension.preExecute) {
10
+ extension.preExecute(stageName);
11
+ }
12
+ });
13
+ },
14
+ runPostExecution: function (stageName, results) {
15
+ this.extensions.forEach((extension) => {
16
+ if (extension.postExecute) {
17
+ extension.postExecute(stageName, results);
18
+ }
19
+ });
20
+ },
21
+ runEnd: function (ciLogResult, stageNames, finalResult) {
22
+ this.extensions.forEach((extension) => {
23
+ if (extension.endExecute) {
24
+ extension.endExecute(ciLogResult, stageNames, finalResult);
25
+ }
26
+ });
27
+ },
28
+ };
29
+
30
+ // Export the extension system
31
+ module.exports.extensionSystem = extensionSystem;
package/src/index.js ADDED
@@ -0,0 +1,199 @@
1
+ const path = require("path"),
2
+ os = require("os"),
3
+ fs = require("fs"),
4
+ { spawn } = require("child_process"),
5
+ getCPUUsageSnapshot = function () {
6
+ const cpus = os.cpus();
7
+ return cpus.map(cpu => ({ ...cpu.times }));
8
+ },
9
+ calculateCPUUsage = function (startSnapshot, endSnapshot) {
10
+ let idleDiff = 0;
11
+ let totalDiff = 0;
12
+
13
+ for (let i = 0; i < startSnapshot.length; i++) {
14
+ const start = startSnapshot[i];
15
+ const end = endSnapshot[i];
16
+
17
+ const idle = end.idle - start.idle;
18
+ const total = Object.keys(end).reduce((acc, key) => acc + (end[key] - start[key]), 0);
19
+
20
+ idleDiff += idle;
21
+ totalDiff += total;
22
+ }
23
+
24
+ const usage = 1 - idleDiff / totalDiff;
25
+ return (usage * 100).toFixed(2); // Percentage string
26
+ },
27
+ parseDirectoryToJson = function (directory) {
28
+ function parse(dirPath) {
29
+ let result = {};
30
+
31
+ const walkSync = (dir) => {
32
+ const files = fs.readdirSync(dir);
33
+ const dirs = files.filter((file) =>
34
+ fs.statSync(path.join(dir, file)).isDirectory(),
35
+ );
36
+
37
+ const currentFolder = {};
38
+
39
+ // Excluir el directorio node_modules
40
+ const dirsToProcess = dirs.filter((dirName) => dirName !== 'node_modules');
41
+
42
+ dirsToProcess.forEach((dirName) => {
43
+ const dirPath = path.join(dir, dirName);
44
+ walkSync(dirPath); // Recursively process subdirectories
45
+ });
46
+
47
+ files.forEach((fileName) => {
48
+ const filePath = path.join(dir, fileName);
49
+ if (fs.statSync(filePath).isFile()) {
50
+ const content = fs.readFileSync(filePath, "utf8");
51
+ if (dir === directory) {
52
+ result[fileName] = content; // Add files directly to the root
53
+ } else {
54
+ currentFolder[fileName] = content; // File content as string
55
+ }
56
+ }
57
+ });
58
+
59
+ // Agregar la estructura de carpetas, excepto node_modules
60
+ if (Object.keys(currentFolder).length > 0) {
61
+ const relativePath = path.relative(directory, dir);
62
+ if (relativePath !== 'node_modules') {
63
+ result[relativePath] = currentFolder; // Add folder structure
64
+ }
65
+ }
66
+ };
67
+
68
+ walkSync(dirPath);
69
+ return result;
70
+ }
71
+
72
+ return parse(directory);
73
+ }, cleanUpAndRespond = (tempDir) => fs.rmSync(tempDir, { recursive: true, force: true }),
74
+ downloadFiles = (fileSystem, baseDir) => {
75
+ Object.keys(fileSystem).forEach((file) => {
76
+ const filePath = path.join(baseDir, file);
77
+ if (typeof fileSystem[file] === "object") {
78
+ if (!fs.existsSync(filePath)) {
79
+ fs.mkdirSync(filePath, { recursive: true });
80
+ }
81
+ downloadFiles(fileSystem[file], filePath);
82
+ } else {
83
+ fs.writeFileSync(filePath, fileSystem[file]);
84
+ }
85
+ });
86
+ }, { extensionSystem } = require("./extension");
87
+
88
+ // Export as a Node JS module
89
+ module.exports = {
90
+
91
+ parse: async function (ciScript, repoName, fileSystem, tmp = true) {
92
+ const isLinux = os.platform() === "linux";
93
+ const isRoot = process.getuid && process.getuid() === 0;
94
+ let finalResult = [];
95
+ let timeTaken;
96
+ let combinedExitCode = 0;
97
+ let startTime = Date.now();
98
+ const cpuStart = getCPUUsageSnapshot();
99
+
100
+ const convertMsToSec = (ms) => (ms / 1000).toFixed(2);
101
+
102
+ const stages = Object.entries(ciScript.stages).map(([name, data]) => ({ name, ...data }));
103
+ const tempDir = path.join(__dirname, tmp == true ? "tmp/" + repoName : repoName);
104
+
105
+ const substituteEnvVariables = (command, env) => {
106
+ return command.replace(/\${\[(\w+)\]}/g, (_, varName) => {
107
+ return env[varName] ?? '';
108
+ });
109
+ };
110
+
111
+ for (const stage of stages) {
112
+ extensionSystem.runPreExecution(stage.name); // <-- pre execution hook
113
+
114
+ if (stage.scripts && Array.isArray(stage.scripts)) {
115
+ if (!fs.existsSync(tempDir)) {
116
+ fs.mkdirSync(tempDir, { recursive: true });
117
+ }
118
+
119
+ downloadFiles(fileSystem, tempDir);
120
+
121
+ await new Promise((resolve) => {
122
+ (async function (commands, callback) {
123
+ const results = [];
124
+
125
+ for (const command of commands) {
126
+ const resolvedCommand = substituteEnvVariables(command, ciScript.env || {});
127
+
128
+ await new Promise((res) => {
129
+ const commandToRun = resolvedCommand;
130
+ const child = spawn(commandToRun, { cwd: tempDir, shell: true });
131
+ let stdoutData = "";
132
+ let stderrData = "";
133
+
134
+ child.stdout.on("data", (data) => stdoutData += data.toString());
135
+ child.stderr.on("data", (data) => stderrData += data.toString());
136
+
137
+ child.on("close", (code) => {
138
+ results.push({
139
+ command: resolvedCommand,
140
+ stdout: stdoutData,
141
+ stderr: stderrData,
142
+ exitCode: code,
143
+ error: code === 0 ? null : `Command failed with exit code ${code}`,
144
+ });
145
+ res();
146
+ });
147
+ });
148
+
149
+ if (results.at(-1).exitCode !== 0) break;
150
+ }
151
+
152
+ callback(results);
153
+ extensionSystem.runPostExecution(stage.name, results); // <-- post execution hook
154
+ resolve();
155
+ })(stage.scripts, (results) => {
156
+ let output = `[${stage.name}]\n\nResults:\n`;
157
+
158
+ results.forEach((result) => {
159
+ output += `$ ${result.command}\n`;
160
+ if (result.error) {
161
+ output += `Error: ${result.error}\n`;
162
+ output += `${result.stderr ? result.stderr : ''}\n`;
163
+ } else {
164
+ output += `${result.stdout ? result.stdout : ''}\n`;
165
+ }
166
+
167
+ if (result.exitCode !== 0) {
168
+ combinedExitCode = result.exitCode;
169
+ }
170
+ });
171
+
172
+ finalResult.push(output);
173
+ });
174
+ });
175
+ }
176
+ }
177
+
178
+ const finalResultEndTime = Date.now();
179
+ timeTaken = convertMsToSec(finalResultEndTime - startTime);
180
+ const cpuEnd = getCPUUsageSnapshot();
181
+
182
+ const ciLogResult = {
183
+ output: finalResult.join(''),
184
+ timestamp: String(new Date().toString()),
185
+ fileSys: parseDirectoryToJson(tempDir),
186
+ ci_duration: timeTaken,
187
+ exitCode: combinedExitCode,
188
+ cpu_usage: calculateCPUUsage(cpuStart, cpuEnd),
189
+ };
190
+
191
+ cleanUpAndRespond(tempDir);
192
+
193
+ extensionSystem.runEnd(ciLogResult, stages.map(stage => stage.name), finalResult); // <-- end hook
194
+
195
+ return ciLogResult;
196
+ },
197
+
198
+ dirToJson: parseDirectoryToJson
199
+ };