First Commit

This commit is contained in:
Benjamin Toby 2023-10-29 08:35:26 +01:00
commit 05f380f691
9 changed files with 381 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
/test

95
README.md Normal file
View File

@ -0,0 +1,95 @@
# Simple CI/CD package for any application
Integrate a simple CI/CD process into your application without the hassle.
_**NOTE:** This package needs `node` installed to work_
## Requirements
- **Node JS Runtime and NPM:** You need to have `NodeJS` and `npm` installed on the target machine for this package to work.
- **`nodecid.config.json` file:** This package depends on a configuration file located in the root directory of your application.
## Installation
To install this package globally just run:
```shell
npm install -g nodecid
```
To run the package directly run:
```shell
npx nodecid
```
This will download the package and run the binaries directly. After the first run it won't download the package again.
## Usage
To run the package after installing it globally just run:
```shell
nodecid
```
Remember you must have a `nodecid.config.json` file located in your root directory else this will throw an error.
### Configuration
Your `nodecid.config.json` file should look like this:
```json
{
"start": "node index.js",
"preflight": ["npm run test", "npm run build"]
}
```
or
```json
{
"start": "node index.js",
"preflight": "./preflight.sh"
}
```
Your `preflight` parameter can wither be an array of commands, or path to a shell script. This will run before every `start` commands.
Optionally you could include a `redeploy_path` in your config file:
```json
{
"start": "node index.js",
"preflight": "./preflight.sh",
"redeploy_path": "./REDEPLOY"
}
```
This will look for the file named `REDEPLOY` in your rood directory and watch that file. If the file is changed the application will be restarted, ie it will run the `preflight` command(s) and `start` command. If you ommit the `redeploy_path` a file named `REDEPLOY` will be created in your root directory.
You can change the name and path of the `redeploy_path`, just make sure the path is correct and the file name exists in the named path. Example:
```json
{
"start": "node index.js",
"preflight": "./preflight.sh",
"redeploy_path": "./deploy/trigger.txt"
}
```
_NOTE:_ This also works for other languages, example:
```json
{
"start": "python app.py",
"preflight": "./preflight.sh"
}
```
This app just runs whatever command you send it in an isolated child process, the command will be run as if being run in a terminal.
### Redeployment
For continuos deployment and integration there needs to be a text file located in your project which the application can watch. Any time the content of this file is changed the application will rebuild and rerun your `start` command.

View File

@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"start": {
"type": "string"
},
"preflight": {
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "string"
}
]
},
"redeploy_path": {
"type": "string"
}
},
"required": ["start", "preflight"]
}

143
deploy/start.js Executable file
View File

@ -0,0 +1,143 @@
// @ts-check
const path = require("path");
const fs = require("fs");
const {
execSync,
spawnSync,
spawn,
execFile,
execFileSync,
ChildProcess,
} = require("child_process");
const colors = require("../utils/console-colors");
////////////////////////////////////////////
////////////////////////////////////////////
////////////////////////////////////////////
/**
* # Start the process
* @param {object} param0
* @param {string} param0.command
* @param {string[] | string} param0.preflight
* @param {string} param0.redeploy_file
*/
function startProcess({ command, preflight, redeploy_file }) {
/** @type {ChildProcess | null} */
let childProcess = null;
try {
const runPreflight = preflightFn(preflight);
if (!preflight) {
process.exit();
}
childProcess = run(command);
if (!childProcess) {
console.log(
`${colors.FgRed}Error:${colors.Reset} Process couldn't start. Exiting...`
);
process.exit();
}
} catch (/** @type {*} */ error) {
console.log(
`${colors.FgRed}Error:${colors.Reset} First run failed! => ${error.message}`
);
}
fs.watchFile(redeploy_file, { interval: 1000 }, (curr, prev) => {
if (childProcess) {
console.log("Rebuilding ...");
try {
const runPreflight = preflightFn(preflight);
if (!preflight) {
process.exit();
}
childProcess.kill("SIGTERM");
} catch (/** @type {*} */ error) {
console.log(
`${colors.FgRed}Error:${colors.Reset} killing child processes => ${error.message}`
);
}
childProcess = run(command);
}
});
}
////////////////////////////////////////////
////////////////////////////////////////////
////////////////////////////////////////////
/**
* ## Preflight Function
* @param {string} command
* @returns {ChildProcess | null}
*/
function run(command) {
const startCommandArray = command.split(" ").filter((str) => str.trim());
try {
const firstCommand = startCommandArray.shift()?.[0];
if (!firstCommand) {
throw new Error("No Starting Command Found in command string!");
}
let childProcess = spawn(firstCommand, ["server.js"], {
cwd: process.cwd(),
stdio: "inherit",
});
return childProcess;
} catch (/** @type {*} */ error) {
console.log(
`${colors.FgRed}Error:${colors.Reset} running start command => ${error.message}`
);
return null;
}
}
////////////////////////////////////////////
////////////////////////////////////////////
////////////////////////////////////////////
/**
* ## Preflight Function
* @param {string[] | string} preflight
* @returns {boolean}
*/
function preflightFn(preflight) {
console.log("Preflight Running ...");
/** @type {import("child_process").ExecSyncOptions} */
const options = {
cwd: process.cwd(),
stdio: "inherit",
};
try {
if (typeof preflight == "string") {
execFileSync(preflight, options);
} else if (typeof preflight == "object" && preflight?.[0]) {
preflight.forEach((cmd) => execSync(cmd, options));
}
return true;
} catch (error) {
console.log(
`${colors.FgRed}Error:${colors.Reset} Preflight Failed! => ${error.message}`
);
return false;
}
}
////////////////////////////////////////////
////////////////////////////////////////////
////////////////////////////////////////////
module.exports = startProcess;

56
index.js Normal file
View File

@ -0,0 +1,56 @@
#! /usr/bin/env node
// @ts-check
const fs = require("fs");
const path = require("path");
const colors = require("./utils/console-colors");
const startProcess = require("./deploy/start");
///////////////////////////////////////////////
///////////////////////////////////////////////
///////////////////////////////////////////////
const WORK_DIR = process.cwd();
///////////////////////////////////////////////
///////////////////////////////////////////////
///////////////////////////////////////////////
try {
const configText = fs.readFileSync(
path.join(WORK_DIR, "nodecid.config.json"),
"utf-8"
);
/** @type {NodeCIConfig} */
const config = JSON.parse(configText);
const { start, preflight, redeploy_path } = config;
/** @type {string | undefined} */
let redeployFile;
if (!redeploy_path) {
fs.writeFileSync(
path.join(WORK_DIR, "REDEPLY"),
Date.now().toString(),
"utf-8"
);
redeployFile = path.join(WORK_DIR, "REDEPLY");
} else {
redeployFile = path.resolve(WORK_DIR, redeploy_path);
}
if (!redeployFile) throw new Error("Redeploy file not found!");
startProcess({
command: start,
preflight,
redeploy_file: redeployFile,
});
} catch (error) {
console.log(
`${colors.FgRed}ERROR:${colors.Reset} CI process failed! => ${error.message}`
);
}

17
package.json Normal file
View File

@ -0,0 +1,17 @@
{
"name": "nodecid",
"version": "1.0.0",
"description": "Simple CI/CD process",
"main": "index.js",
"bin": {
"nodecid": "./index.js",
"node-ci-cd": "./index.js"
},
"keywords": [
"CI/CD",
"Continuous Integration",
"Continous Deployment"
],
"author": "Benjamin Toby",
"license": "MIT"
}

8
types.d.js Normal file
View File

@ -0,0 +1,8 @@
/**
* @typedef {object} NodeCIConfig
* @property {string} start - Start command. Eg `node index.js`
* @property {string[] | string} preflight - And array of commands to run before
* the application starts, or a single `.sh` file path.
* @property {string} [redeploy_path] - The path to the file that will trigger a
* redeployment if content is changed. Default file path is `./REDEPLOY`
*/

31
utils/console-colors.js Executable file
View File

@ -0,0 +1,31 @@
const colors = {
Reset: "\x1b[0m",
Bright: "\x1b[1m",
Dim: "\x1b[2m",
Underscore: "\x1b[4m",
Blink: "\x1b[5m",
Reverse: "\x1b[7m",
Hidden: "\x1b[8m",
FgBlack: "\x1b[30m",
FgRed: "\x1b[31m",
FgGreen: "\x1b[32m",
FgYellow: "\x1b[33m",
FgBlue: "\x1b[34m",
FgMagenta: "\x1b[35m",
FgCyan: "\x1b[36m",
FgWhite: "\x1b[37m",
FgGray: "\x1b[90m",
BgBlack: "\x1b[40m",
BgRed: "\x1b[41m",
BgGreen: "\x1b[42m",
BgYellow: "\x1b[43m",
BgBlue: "\x1b[44m",
BgMagenta: "\x1b[45m",
BgCyan: "\x1b[46m",
BgWhite: "\x1b[47m",
BgGray: "\x1b[100m",
};
module.exports = colors;

3
utils/triggers/github.js Normal file
View File

@ -0,0 +1,3 @@
function githubWebhook() {
return true;
}