First Commit
This commit is contained in:
commit
05f380f691
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
/test
|
95
README.md
Normal file
95
README.md
Normal 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.
|
26
config/nodecid.schema.json
Normal file
26
config/nodecid.schema.json
Normal 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
143
deploy/start.js
Executable 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
56
index.js
Normal 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
17
package.json
Normal 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
8
types.d.js
Normal 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
31
utils/console-colors.js
Executable 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
3
utils/triggers/github.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function githubWebhook() {
|
||||||
|
return true;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user