Compare commits

...
24 Commits
Author SHA1 Message Date
Benjamin Toby 62eef2f167 Updates 2025-05-07 22:18:55 +01:00
Benjamin Toby 7e320c87f5 Updates 2025-02-22 18:02:00 +01:00
Benjamin Toby 7bcddc9517 Updates 2025-02-22 14:55:26 +01:00
Benjamin Toby 85a64f85d6 Bugfix 2025-02-21 09:17:55 +01:00
Benjamin Toby 4540404522 Updates 2025-02-21 09:00:49 +01:00
Benjamin Toby 7d71316b2c Bugfix 2025-02-07 11:54:13 +01:00
Benjamin Toby 69eb6c1128 Updates 2025-02-04 07:43:18 +01:00
Benjamin Toby 0375c02931 Updates 2025-02-03 16:23:23 +01:00
Benjamin Toby 1e57cdbc1d Updates 2025-02-03 14:21:32 +01:00
Benjamin Toby 619afc8303 Updates 2025-02-03 13:41:13 +01:00
Benjamin Toby cee4bc0478 Updates 2025-01-22 12:31:32 +01:00
Benjamin Toby 1c26586478 Updates 2025-01-16 09:28:46 +01:00
Benjamin Toby 2cd83295bf Updates 2025-01-16 09:18:45 +01:00
Benjamin Toby 6867e8c17c Updates 2025-01-16 09:03:11 +01:00
Benjamin Toby a7b4e3f00b First Commit 2025-01-16 09:01:06 +01:00
Benjamin Toby cab7b131bf Updates 2025-01-16 08:16:23 +01:00
Benjamin Toby 702a62a914 Updates 2025-01-16 08:11:29 +01:00
Benjamin Toby 2264830d9f Updates 2025-01-16 08:06:35 +01:00
Benjamin Toby ae2a328f36 Updates 2025-01-16 07:57:10 +01:00
Benjamin Toby 1aaa257041 Updates 2025-01-16 07:52:57 +01:00
Benjamin Toby 918afc5d2c Updates 2025-01-16 07:48:00 +01:00
Benjamin Toby 2118f04b63 Updates 2025-01-16 06:32:50 +01:00
Benjamin Toby c09409e182 Updates 2025-01-16 06:31:09 +01:00
Benjamin Toby a3e20f15ef Updates 2025-01-16 06:25:16 +01:00
44 changed files with 1140 additions and 340 deletions
+80 -3
View File
@@ -1,9 +1,86 @@
{ {
"cursor.aipreview.enabled": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.tabWidth": 4,
"redhat.telemetry.enabled": false,
"vs-kubernetes": {
"vscode-kubernetes.kubectl-path-linux": "/home/archben/.local/state/vs-kubernetes/tools/kubectl/kubectl",
"vscode-kubernetes.helm-path-linux": "/home/archben/.local/state/vs-kubernetes/tools/helm/linux-amd64/helm",
"vscode-kubernetes.minikube-path-linux": "/home/archben/.local/state/vs-kubernetes/tools/minikube/linux-amd64/minikube"
},
"workbench.colorTheme": "Obsidian Dark",
"terminal.integrated.env.linux": {},
"console-ninja.featureSet": "Community",
"files.associations": { "files.associations": {
".env*": "dotenv",
".env": "properties",
"*.conf": "nginx",
"ostream": "cpp" "ostream": "cpp"
}, },
"[python]": { "editor.tokenColorCustomizations": {
"editor.defaultFormatter": "ms-python.autopep8" "[*Light*]": {
"textMateRules": [
{
"scope": "ref.matchtext",
"settings": {
"foreground": "#000"
}
}
]
},
"[*Dark*]": {
"textMateRules": [
{
"scope": "ref.matchtext",
"settings": {
"foreground": "#fff"
}
}
]
},
"textMateRules": []
}, },
"python.formatting.provider": "none" "dotenv.enableAutocloaking": false,
"git.openRepositoryInParentFolders": "never",
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"shellformat.effectLanguages": [
"shellscript",
"dockerfile",
"dotenv",
"hosts",
"jvmoptions",
"ignore",
"gitignore",
"properties",
"spring-boot-properties",
"azcli",
"bats"
],
"[dockerfile]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"[ignore]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"[dotenv]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"emulator.emulatorPath": "~/Android/Sdk/emulator",
"[properties]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
},
"[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml"
},
"terminal.integrated.defaultProfile.linux": "bash",
"diffEditor.ignoreTrimWhitespace": false,
"[sql]": {
"editor.defaultFormatter": "adpyke.vscode-sql-formatter"
},
"editor.fontFamily": "'Noto Sans Mono', 'monospace', monospace",
"stripe.telemetry.enabled": false,
"editor.linkedEditing": true
} }
+44 -18
View File
@@ -1,31 +1,28 @@
# Simple CI/CD package for any application # Simple CI/CD package for any application
[![package version](https://img.shields.io/npm/v/nodecid.svg?style=flat-square)](https://npmjs.org/package/nodecid) NOTE: This package is for Bun runtime
[![package downloads](https://img.shields.io/npm/dm/nodecid.svg?style=flat-square)](https://npmjs.org/package/nodecid)
[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![package license](https://img.shields.io/npm/l/nodecid.svg?style=flat-square)](https://npmjs.org/package/nodecid)
Integrate a simple CI/CD process into your application without the hassle. Integrate a simple CI/CD process into your application without the hassle.
_**NOTE:** This package needs `node` installed to work_ _**NOTE:** This package needs `bun` installed to work_
## Requirements ## Requirements
- **Node JS Runtime and NPM:** You need to have `NodeJS` and `npm` installed on the target machine for this package to work. - **Node JS Runtime and NPM:** You need to have `NodeJS` and `bun` 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. - **`buncid.config.json` file:** This package depends on a configuration file located in the root directory of your application.
## Installation ## Installation
To install this package globally just run: To install this package globally just run:
```shell ```shell
npm install -g nodecid bun install -g buncid
``` ```
To run the package directly run: To run the package directly run:
```shell ```shell
npx nodecid bun buncid
``` ```
This will download the package and run the binaries directly. After the first run it won't download the package again. This will download the package and run the binaries directly. After the first run it won't download the package again.
@@ -35,19 +32,19 @@ This will download the package and run the binaries directly. After the first ru
To run the package after installing it globally just run: To run the package after installing it globally just run:
```shell ```shell
nodecid buncid
``` ```
Remember you must have a `nodecid.config.json` file located in your root directory else this will throw an error. Remember you must have a `buncid.config.json` file located in your root directory else this will throw an error.
### Configuration ### Configuration
Your `nodecid.config.json` file should look like this: Your `buncid.config.json` file should look like this:
```json ```json
{ {
"start": "node index.js", "start": "bun index.ts",
"preflight": ["npm run test", "npm run build"] "preflight": ["bun run test", "bun run build"]
} }
``` ```
@@ -55,7 +52,7 @@ or
```json ```json
{ {
"start": "node index.js", "start": "bun index.ts",
"preflight": "./preflight.sh" "preflight": "./preflight.sh"
} }
``` ```
@@ -66,7 +63,7 @@ Optionally you could include a `redeploy_path` in your config file:
```json ```json
{ {
"start": "node index.js", "start": "bun index.ts",
"preflight": "./preflight.sh", "preflight": "./preflight.sh",
"redeploy_path": "./REDEPLOY" "redeploy_path": "./REDEPLOY"
} }
@@ -78,7 +75,7 @@ You can change the name and path of the `redeploy_path`, just make sure the path
```json ```json
{ {
"start": "node index.js", "start": "bun index.ts",
"preflight": "./preflight.sh", "preflight": "./preflight.sh",
"redeploy_path": "./deploy/trigger.txt" "redeploy_path": "./deploy/trigger.txt"
} }
@@ -95,7 +92,7 @@ _NOTE:_ This also works for other languages, example:
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. 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.
#### All Available options in `nodecid.config.json` file #### All Available options in `buncid.config.json` file
- **`start`**: _string_: The start Command - **`start`**: _string_: The start Command
- **`preflight`**: _string | Array_: Array of commands or shell script file to run before reloading application - **`preflight`**: _string | Array_: Array of commands or shell script file to run before reloading application
@@ -107,3 +104,32 @@ This app just runs whatever command you send it in an isolated child process, th
### Redeployment ### 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. 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.
## Rebuilds
`buncid` provides rebuild scripts for popular frameworks
### Next JS
use `bun buncid-builds-next` or simply `buncid-builds-next` if you installed globally, to rebuild a next js app incrementally. This has a few requirements though.
#### Update your `next-config.js` file
You need to update the distribution directory in your `next-config.ts`. Like this:
```javascript
import type { NextConfig } from "next";
import grabDist from "@moduletrace/buncid/dist/rebuilds/next-js/grabDist";
const distDir = grabDist();
const nextConfig: NextConfig = {
/* config options here */
reactStrictMode: true,
distDir,
};
export default nextConfig;
```
That's it. This dynamically handles your distribution directory for both `dev` and `start` scripts. Your `development` environment uses the `.next` directory, while your `production` environment uses the `.buncid-next-dist` (or any dist name of your chosing) directory.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Executable
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env bun
import fs from "fs";
import path from "path";
import colors from "./utils/console-colors";
import startProcess from "./utils/start";
import type { NodeCIConfig } from "./types";
const WORK_DIR = process.cwd();
declare global {
var REDEPLOYMENTS: number;
var DEPLOYING: 0 | 1;
}
global.REDEPLOYMENTS = 0;
global.DEPLOYING = 0;
function run() {
try {
const configText = fs.readFileSync(
path.join(WORK_DIR, "buncid.config.json"),
"utf-8"
);
const config: NodeCIConfig = JSON.parse(configText);
const {
start,
preflight,
postflight,
build,
redeploy_path,
first_run,
port,
debounce,
} = config;
let redeployFile: string | undefined;
if (!redeploy_path) {
const defaultRedeployPath = path.join(WORK_DIR, "REDEPLOY");
const checkExistingPath = fs.existsSync(defaultRedeployPath);
if (!checkExistingPath) {
fs.writeFileSync(
defaultRedeployPath,
Date.now().toString(),
"utf-8"
);
}
redeployFile = path.join(WORK_DIR, "REDEPLOY");
} else {
redeployFile = path.resolve(WORK_DIR, redeploy_path);
}
if (!redeployFile)
throw new Error("Redeploy file variable not provided!");
if (!fs.existsSync(redeployFile))
throw new Error("Redeploy file not found!");
startProcess({
command: start,
preflight,
redeploy_file: redeployFile,
first_run,
port,
postflight,
debounce,
});
} catch (error: any) {
console.log(
`${colors.FgRed}ERROR:${colors.Reset} CI process failed! => ${error.message}`
);
}
}
run();
process.on("exit", () => {
console.log("Process exiting ...");
});
process.on("beforeExit", () => {
console.log("Process Before exit ...");
});
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bun
declare global {
var REDEPLOYMENTS: number;
var DEPLOYING: 0 | 1;
}
export {};
Vendored Executable
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env bun
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const console_colors_1 = __importDefault(require("./utils/console-colors"));
const start_1 = __importDefault(require("./utils/start"));
const WORK_DIR = process.cwd();
global.REDEPLOYMENTS = 0;
global.DEPLOYING = 0;
function run() {
try {
const configText = fs_1.default.readFileSync(path_1.default.join(WORK_DIR, "buncid.config.json"), "utf-8");
const config = JSON.parse(configText);
const { start, preflight, postflight, build, redeploy_path, first_run, port, debounce, } = config;
let redeployFile;
if (!redeploy_path) {
const defaultRedeployPath = path_1.default.join(WORK_DIR, "REDEPLOY");
const checkExistingPath = fs_1.default.existsSync(defaultRedeployPath);
if (!checkExistingPath) {
fs_1.default.writeFileSync(defaultRedeployPath, Date.now().toString(), "utf-8");
}
redeployFile = path_1.default.join(WORK_DIR, "REDEPLOY");
}
else {
redeployFile = path_1.default.resolve(WORK_DIR, redeploy_path);
}
if (!redeployFile)
throw new Error("Redeploy file variable not provided!");
if (!fs_1.default.existsSync(redeployFile))
throw new Error("Redeploy file not found!");
(0, start_1.default)({
command: start,
preflight,
redeploy_file: redeployFile,
first_run,
port,
postflight,
debounce,
});
}
catch (error) {
console.log(`${console_colors_1.default.FgRed}ERROR:${console_colors_1.default.Reset} CI process failed! => ${error.message}`);
}
}
run();
process.on("exit", () => {
console.log("Process exiting ...");
});
process.on("beforeExit", () => {
console.log("Process Before exit ...");
});
+9 -1
View File
@@ -1 +1,9 @@
export {}; import grabDist from "./rebuilds/next-js/grabDist";
declare const buncid: {
builds: {
nextJs: {
grabDist: typeof grabDist;
};
};
};
export default buncid;
+9 -45
View File
@@ -3,48 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const fs_1 = __importDefault(require("fs")); const grabDist_1 = __importDefault(require("./rebuilds/next-js/grabDist"));
const path_1 = __importDefault(require("path")); const buncid = {
const console_colors_1 = __importDefault(require("./utils/console-colors")); builds: {
const start_1 = __importDefault(require("./utils/start")); nextJs: {
const WORK_DIR = process.cwd(); grabDist: grabDist_1.default,
function run() { },
try { },
const configText = fs_1.default.readFileSync(path_1.default.join(WORK_DIR, "nodecid.config.json"), "utf-8"); };
const config = JSON.parse(configText); exports.default = buncid;
const { start, preflight, postflight, build, redeploy_path, first_run, port, } = config;
/** @type {string | undefined} */
let redeployFile;
if (!redeploy_path) {
const defaultRedeployPath = path_1.default.join(WORK_DIR, "REDEPLOY");
const checkExistingPath = fs_1.default.existsSync(defaultRedeployPath);
if (!checkExistingPath) {
fs_1.default.writeFileSync(defaultRedeployPath, Date.now().toString(), "utf-8");
}
redeployFile = path_1.default.join(WORK_DIR, "REDEPLOY");
}
else {
redeployFile = path_1.default.resolve(WORK_DIR, redeploy_path);
}
if (!redeployFile)
throw new Error("Redeploy file not found!");
(0, start_1.default)({
command: start,
preflight,
redeploy_file: redeployFile,
first_run,
port,
postflight,
});
}
catch (error) {
console.log(`${console_colors_1.default.FgRed}ERROR:${console_colors_1.default.Reset} CI process failed! => ${error.message}`);
}
}
run();
process.on("exit", () => {
console.log("Process exiting ...");
});
process.on("beforeExit", () => {
console.log("Process Before exit ...");
});
@@ -0,0 +1 @@
export default function grabDefaultDistName(): string;
@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = grabDefaultDistName;
function grabDefaultDistName() {
return ".buncid-next-dist";
}
+10
View File
@@ -0,0 +1,10 @@
type Param = {
distDir?: string;
};
/**
* # Grab the current distribution directory
* @description This returns the relative path from the CWD. Eg `./.dist/build-1`
* @returns {string | undefined}
*/
export default function grabDist(params?: Param): string | undefined;
export {};
+47
View File
@@ -0,0 +1,47 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = grabDist;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const grab_default_dist_name_1 = __importDefault(require("./(utils)/grab-default-dist-name"));
const production = process.env.NODE_ENV == "production";
const isBuilding = process.env.BUILDING_APP;
/**
* # Grab the current distribution directory
* @description This returns the relative path from the CWD. Eg `./.dist/build-1`
* @returns {string | undefined}
*/
function grabDist(params) {
const distDirName = (params === null || params === void 0 ? void 0 : params.distDir) || (0, grab_default_dist_name_1.default)();
const DIST_DIR = path_1.default.join(process.cwd(), distDirName);
if (isBuilding) {
const distDir = (() => {
try {
const buildNumber = fs_1.default.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
return `${distDirName}/build-${buildNumber}`;
}
catch (error) {
console.log("Build Number Generation Error =>", error.message);
process.exit();
}
})();
return distDir;
}
if (production) {
const distDir = (() => {
try {
const buildNumber = fs_1.default.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
return `${distDirName}/build-${buildNumber}`;
}
catch (error) {
console.log("Build Number Parse Error =>", error.message);
process.exit();
}
})();
return distDir;
}
return undefined;
}
+2
View File
@@ -0,0 +1,2 @@
#!/usr/bin/env bun
export {};
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env bun
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = __importDefault(require("path"));
const util_1 = require("util");
const fs_1 = __importDefault(require("fs"));
const child_process_1 = require("child_process");
const grab_default_dist_name_1 = __importDefault(require("./(utils)/grab-default-dist-name"));
const args = (0, util_1.parseArgs)({
args: process.argv,
options: {
maxBuilds: {
type: "string",
alias: "m",
},
distDir: {
type: "string",
alias: "d",
},
},
allowPositionals: true,
});
const distDirName = args.values.distDir || (0, grab_default_dist_name_1.default)();
const DIST_DIR = path_1.default.join(process.cwd(), distDirName);
let PREV_BUILD_NO = "0";
const MAX_BUILDS = process.env.BUNCID_MAX_BUILDS
? Number(process.env.BUNCID_MAX_BUILDS)
: args.values.maxBuilds
? Number(args.values.maxBuilds)
: 10;
if (MAX_BUILDS < 1 ||
Number.isNaN(MAX_BUILDS) ||
typeof MAX_BUILDS !== "number") {
throw new Error("Invalid MAX_BUILDS");
}
if (!fs_1.default.existsSync(DIST_DIR))
fs_1.default.mkdirSync(DIST_DIR, { recursive: true });
if (fs_1.default.existsSync(`${DIST_DIR}/BUILD`)) {
PREV_BUILD_NO = fs_1.default.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
}
else {
fs_1.default.writeFileSync(`${DIST_DIR}/BUILD`, "0", "utf-8");
}
try {
const buildNumber = fs_1.default.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
const newBuildNumber = Number(buildNumber) + 1;
if (newBuildNumber < 0) {
throw new Error("Invalid Build Number");
}
fs_1.default.writeFileSync(`${DIST_DIR}/BUILD`, String(newBuildNumber));
if (newBuildNumber > MAX_BUILDS) {
const builds = fs_1.default.readdirSync(DIST_DIR);
const buildDirs = builds.filter((build) => build.match(/build-\d+/));
for (const buildDir of buildDirs) {
const buildDirPath = path_1.default.join(DIST_DIR, buildDir);
const buildDirStat = fs_1.default.statSync(buildDirPath);
if (buildDirStat.isDirectory()) {
const buildDirName = buildDir.split("-")[1];
const buildDirNumber = Number(buildDirName);
if (buildDirNumber <= newBuildNumber - MAX_BUILDS) {
fs_1.default.rmdirSync(buildDirPath, { recursive: true });
console.log("Deleted Build Directory =>", buildDirPath);
}
}
}
}
}
catch (error) {
console.log("Build Number Parse Error =>", error.message);
process.exit(1);
}
const spawnSyncOptions = {
stdio: "inherit",
encoding: "utf-8",
shell: ((_a = process.platform) === null || _a === void 0 ? void 0 : _a.match(/win32/i)) ? "bash.exe" : undefined,
env: Object.assign(Object.assign({}, process.env), { BUILDING_APP: "true" }),
};
const build = (0, child_process_1.spawnSync)("bun", ["next", "build"], spawnSyncOptions);
/**
* @returns {string}
*/
function grabNewDistDir() {
try {
const buildNumber = fs_1.default.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
return `${distDirName}/build-${buildNumber}`;
}
catch ( /** @type {*} */error) {
console.log("Build Number Parse Error =>", error.message);
process.exit();
}
}
const newDistDir = grabNewDistDir();
// if (!fs.existsSync(newDistDir)) {
// fs.mkdirSync(newDistDir, { recursive: true });
// }
// if (!fs.existsSync(`${newDistDir}/BUILD_ID`)) {
// fs.writeFileSync(`${newDistDir}/BUILD_ID`, "4");
// }
/**
* # Revert Directories
* @param {string} dir - New Build Directory Path
*/
function revert(dir) {
var _a;
console.log("Build Failed!", ((_a = build === null || build === void 0 ? void 0 : build.error) === null || _a === void 0 ? void 0 : _a.message) || build.stderr);
fs_1.default.writeFileSync(`${DIST_DIR}/BUILD`, PREV_BUILD_NO, "utf-8");
(0, child_process_1.execSync)(`rm -Rf ${dir}`, { cwd: process.cwd() });
const writeErr = build.error
? build.error.message
: build.stderr
? build.stderr.toString()
: "NO BUILD_ID found in New Build Folder";
fs_1.default.writeFileSync(`${DIST_DIR}/LAST_BUILD_FAIL`, Date() + "\n\n" + writeErr, "utf-8");
process.exit(1);
}
if (build.error ||
build.stderr ||
build.status != 0 ||
!fs_1.default.existsSync(`${newDistDir}/BUILD_ID`)) {
revert(newDistDir);
throw new Error("Build Failed!");
}
process.on("exit", () => {
const onExitDir = grabNewDistDir();
if (!fs_1.default.existsSync(`${onExitDir}/BUILD_ID`)) {
revert(onExitDir);
}
});
+6
View File
@@ -0,0 +1,6 @@
/**
* # Grab the current distribution directory
* @description This returns the relative path from the CWD. Eg `./.dist/build-1`
* @returns {string | undefined}
*/
export default function grabDist(): string | undefined;
+45
View File
@@ -0,0 +1,45 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = grabDist;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const production = process.env.NODE_ENV == "production";
const isBuilding = process.env.BUILDING_APP;
/**
* # Grab the current distribution directory
* @description This returns the relative path from the CWD. Eg `./.dist/build-1`
* @returns {string | undefined}
*/
function grabDist() {
const DIST_DIR = path_1.default.resolve(process.cwd(), "./.dist");
if (isBuilding) {
const distDir = (() => {
try {
const buildNumber = fs_1.default.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
return `.dist/build-${buildNumber}`;
}
catch ( /** @type {*} */error) {
console.log("Build Number Generation Error =>", error.message);
process.exit();
}
})();
return distDir;
}
if (production) {
const distDir = (() => {
try {
const buildNumber = fs_1.default.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
return `.dist/build-${buildNumber}`;
}
catch ( /** @type {*} */error) {
console.log("Build Number Parse Error =>", error.message);
process.exit();
}
})();
return distDir;
}
return undefined;
}
+3
View File
@@ -0,0 +1,3 @@
import type { NextConfig } from "next";
declare const nextConfig: NextConfig;
export default nextConfig;
+12
View File
@@ -0,0 +1,12 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const grabDist_1 = __importDefault(require("../../../rebuilds/next-js/grabDist"));
const distDir = (0, grabDist_1.default)();
const nextConfig = {
reactStrictMode: true,
distDir,
};
exports.default = nextConfig;
+6
View File
@@ -0,0 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next";
type Data = {
name: string;
};
export default function handler(req: NextApiRequest, res: NextApiResponse<Data>): void;
export {};
+6
View File
@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = handler;
function handler(req, res) {
res.status(200).json({ name: "John Doe" });
}
+13
View File
@@ -0,0 +1,13 @@
declare const _default: {
content: string[];
theme: {
extend: {
colors: {
background: string;
foreground: string;
};
};
};
plugins: never[];
};
export default _default;
+18
View File
@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "var(--background)",
foreground: "var(--foreground)",
},
},
},
plugins: [],
};
+1 -1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -6,6 +6,7 @@ export interface NodeCIConfig {
first_run?: boolean; first_run?: boolean;
port?: string | number | (string | number)[]; port?: string | number | (string | number)[];
build?: NodeCIBuild; build?: NodeCIBuild;
debounce?: number;
} }
export interface NodeCIBuild { export interface NodeCIBuild {
paradigm: "Next.JS" | "Remix"; paradigm: "Next.JS" | "Remix";
+10 -1
View File
@@ -1,6 +1,15 @@
import { ChildProcess } from "child_process";
/** /**
* ## Kill Child Process Function * ## Kill Child Process Function
* @param {string | number | (string | number)[]} [port] * @param {string | number | (string | number)[]} [port]
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
export default function killChild(port?: string | number | (string | number)[]): Promise<boolean>; export default function killChild(
/**
* Child Process to be killed
*/
childProcess?: ChildProcess,
/**
* Port/ports to be killed
*/
port?: string | number | (string | number)[]): Promise<boolean>;
+51 -14
View File
@@ -15,35 +15,72 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.default = killChild; exports.default = killChild;
const console_colors_1 = __importDefault(require("./console-colors")); const console_colors_1 = __importDefault(require("./console-colors"));
const kill_port_1 = __importDefault(require("kill-port")); const kill_port_1 = __importDefault(require("kill-port"));
let childProcess = null;
const pTitle = "nodecid";
process.title = pTitle;
/** /**
* ## Kill Child Process Function * ## Kill Child Process Function
* @param {string | number | (string | number)[]} [port] * @param {string | number | (string | number)[]} [port]
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
function killChild(port) { function killChild(
/**
* Child Process to be killed
*/
childProcess,
/**
* Port/ports to be killed
*/
port) {
return __awaiter(this, void 0, void 0, function* () { return __awaiter(this, void 0, void 0, function* () {
if (!childProcess) if (!childProcess)
return false; return false;
try { try {
const childProcessPID = childProcess.pid; const childKilled = childProcess.kill();
childProcess.kill(); if (childKilled) {
if (typeof port == "object" && (port === null || port === void 0 ? void 0 : port[0])) { return true;
for (let i = 0; i < port.length; i++) {
const singlePort = port[i];
yield (0, kill_port_1.default)(Number(singlePort));
}
} }
else if (port) { else {
yield (0, kill_port_1.default)(Number(port)); return yield killPortFn(childProcess, port);
} }
return true;
} }
catch (error) { catch (error) {
yield killPortFn(childProcess, port);
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} Child Process couldn't be killed! ${error.message}`); console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} Child Process couldn't be killed! ${error.message}`);
return false; return false;
} }
}); });
} }
function killPortFn(
/**
* Child Process to be killed
*/
childProcess,
/**
* Port/ports to be killed
*/
port) {
return __awaiter(this, void 0, void 0, function* () {
if (!childProcess)
return false;
try {
if (typeof port == "object" && (port === null || port === void 0 ? void 0 : port[0])) {
for (let i = 0; i < port.length; i++) {
const singlePort = port[i];
yield (0, kill_port_1.default)(Number(singlePort), "tcp");
}
childProcess.kill();
return true;
}
else if (port) {
yield (0, kill_port_1.default)(Number(port), "tcp");
childProcess.kill();
return true;
}
else {
return false;
}
}
catch (_err) {
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} Child Process PORT couldn't be killed! ${_err.message}`);
return false;
}
});
}
+3 -6
View File
@@ -6,10 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.default = run; exports.default = run;
const child_process_1 = require("child_process"); const child_process_1 = require("child_process");
const console_colors_1 = __importDefault(require("./console-colors")); const console_colors_1 = __importDefault(require("./console-colors"));
let redeployments = 0;
const KILL_SIGNAL = "SIGTERM"; const KILL_SIGNAL = "SIGTERM";
const pTitle = "nodecid";
process.title = pTitle;
/** /**
* ## Preflight Function * ## Preflight Function
* @param {string} command * @param {string} command
@@ -25,12 +22,12 @@ function run(command) {
if (!firstCommand) { if (!firstCommand) {
throw new Error("No Starting Command Found in command string!"); throw new Error("No Starting Command Found in command string!");
} }
let childProcess = (0, child_process_1.spawn)(firstCommand, startCommandArray, { let newChildProcess = (0, child_process_1.spawn)(firstCommand, startCommandArray, {
stdio: "inherit", stdio: "inherit",
killSignal: KILL_SIGNAL, killSignal: KILL_SIGNAL,
}); });
redeployments++; global.REDEPLOYMENTS = global.REDEPLOYMENTS + 1;
return childProcess; return newChildProcess;
} }
catch (error) { catch (error) {
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} running start command => ${error.message}`); console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} running start command => ${error.message}`);
+6 -13
View File
@@ -1,18 +1,11 @@
/** type Param = {
* # Start the process
* @param {object} param0
* @param {string} param0.command
* @param {string[] | string} param0.preflight
* @param {string[] | string} [param0.postflight]
* @param {string} param0.redeploy_file
* @param {string | number | (string | number)[]} [param0.port] - The port to kill on rebuild
* @param {boolean} [param0.first_run] - Whether to run the preflight on first run. Default `false`
*/
export default function startProcess({ command, preflight, postflight, redeploy_file, port, first_run, }: {
command: string; command: string;
preflight: string[] | string; preflight?: string[] | string;
postflight?: string[] | string; postflight?: string[] | string;
redeploy_file: string; redeploy_file: string;
port?: string | number | (string | number)[]; port?: string | number | (string | number)[];
first_run?: boolean; first_run?: boolean;
}): void; debounce?: number;
};
export default function startProcess({ command, preflight, postflight, redeploy_file, port, first_run, debounce, }: Param): void;
export {};
+47 -48
View File
@@ -9,29 +9,16 @@ const console_colors_1 = __importDefault(require("./console-colors"));
const preflight_1 = __importDefault(require("./preflight")); const preflight_1 = __importDefault(require("./preflight"));
const run_1 = __importDefault(require("./run")); const run_1 = __importDefault(require("./run"));
const kill_child_1 = __importDefault(require("./kill-child")); const kill_child_1 = __importDefault(require("./kill-child"));
let redeployments = 0;
let childProcess = null; let childProcess = null;
const pTitle = "nodecid"; const pTitle = "buncid";
process.title = pTitle; process.title = pTitle;
/** let timeout;
* # Start the process function startProcess({ command, preflight, postflight, redeploy_file, port, first_run, debounce, }) {
* @param {object} param0 const DEBOUNCE = debounce || 1000;
* @param {string} param0.command
* @param {string[] | string} param0.preflight
* @param {string[] | string} [param0.postflight]
* @param {string} param0.redeploy_file
* @param {string | number | (string | number)[]} [param0.port] - The port to kill on rebuild
* @param {boolean} [param0.first_run] - Whether to run the preflight on first run. Default `false`
*/
function startProcess({ command, preflight, postflight, redeploy_file, port, first_run, }) {
try { try {
if (first_run) { if (first_run) {
console.log("First Run ..."); console.log("First Run ...");
const runPreflight = (0, preflight_1.default)(preflight); const runFirstPreflight = (0, preflight_1.default)(preflight);
}
if (!preflight) {
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} No preflight included in config file. If you don't want to run any preflight command simply add an empty array.`);
process.exit();
} }
childProcess = (0, run_1.default)(command); childProcess = (0, run_1.default)(command);
if (!childProcess) { if (!childProcess) {
@@ -40,42 +27,54 @@ function startProcess({ command, preflight, postflight, redeploy_file, port, fir
} }
console.log("Watching", redeploy_file); console.log("Watching", redeploy_file);
fs_1.default.watchFile(redeploy_file, { interval: 100 }, (curr, prev) => { fs_1.default.watchFile(redeploy_file, { interval: 100 }, (curr, prev) => {
console.log(`${console_colors_1.default.BgBlue}File Changed${console_colors_1.default.Reset}`); if (global.DEPLOYING == 1)
if (redeployments == 0)
return; return;
if (childProcess) { clearTimeout(timeout);
console.log("******************************"); timeout = setTimeout(() => {
console.log(`******** ${console_colors_1.default.FgBlue}Rebuilding ${console_colors_1.default.FgMagenta}${redeployments}${console_colors_1.default.Reset} ********`); console.log(`${console_colors_1.default.BgBlue}File Changed${console_colors_1.default.Reset}`);
console.log("******************************"); if (global.REDEPLOYMENTS == 0) {
try { return;
const runPreflight = (0, preflight_1.default)(preflight); }
if (!runPreflight) { global.DEPLOYING = 1;
// TODO: Action to take if preflight fails if (childProcess) {
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} Preflight Failed.`); console.log("******************************");
} console.log(`******** ${console_colors_1.default.FgBlue}Rebuilding ${console_colors_1.default.FgMagenta}${global.REDEPLOYMENTS}${console_colors_1.default.Reset} ********`);
else { console.log("******************************");
(0, kill_child_1.default)(port).then((kill) => { try {
if (kill) { const runPreflight = (0, preflight_1.default)(preflight);
childProcess = (0, run_1.default)(command); if (!runPreflight) {
if (postflight) { console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} Preflight Failed.`);
const runPostflight = (0, preflight_1.default)(postflight, true); setTimeout(() => {
if (!runPostflight) { global.DEPLOYING = 0;
// TODO: Action to take if postflight fails }, 2000);
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} Postflight Failed.`); }
else {
(0, kill_child_1.default)(childProcess, port).then((kill) => {
if (kill) {
childProcess = (0, run_1.default)(command);
if (postflight) {
const runPostflight = (0, preflight_1.default)(postflight, true);
if (!runPostflight) {
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} Postflight Failed.`);
}
} }
global.DEPLOYING = 0;
} }
} else {
else { process.exit();
process.exit(); }
} });
}); }
}
catch (error) {
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} killing child processes => ${error.message}`);
process.exit();
} }
} }
catch (error) { else {
console.log(`${console_colors_1.default.FgRed}Error:${console_colors_1.default.Reset} killing child processes => ${error.message}`); global.DEPLOYING = 0;
process.exit();
} }
} }, DEBOUNCE);
}); });
} }
catch (error) { catch (error) {
+9 -71
View File
@@ -1,73 +1,11 @@
import fs from "fs"; import grabDist from "./rebuilds/next-js/grabDist";
import path from "path";
import colors from "./utils/console-colors";
import startProcess from "./utils/start";
import type { NodeCIConfig } from "./types";
const WORK_DIR = process.cwd(); const buncid = {
builds: {
nextJs: {
grabDist,
},
},
};
function run() { export default buncid;
try {
const configText = fs.readFileSync(
path.join(WORK_DIR, "nodecid.config.json"),
"utf-8"
);
const config: NodeCIConfig = JSON.parse(configText);
const {
start,
preflight,
postflight,
build,
redeploy_path,
first_run,
port,
} = config;
/** @type {string | undefined} */
let redeployFile;
if (!redeploy_path) {
const defaultRedeployPath = path.join(WORK_DIR, "REDEPLOY");
const checkExistingPath = fs.existsSync(defaultRedeployPath);
if (!checkExistingPath) {
fs.writeFileSync(
defaultRedeployPath,
Date.now().toString(),
"utf-8"
);
}
redeployFile = path.join(WORK_DIR, "REDEPLOY");
} else {
redeployFile = path.resolve(WORK_DIR, redeploy_path);
}
if (!redeployFile) throw new Error("Redeploy file not found!");
startProcess({
command: start,
preflight,
redeploy_file: redeployFile,
first_run,
port,
postflight,
});
} catch (error: any) {
console.log(
`${colors.FgRed}ERROR:${colors.Reset} CI process failed! => ${error.message}`
);
}
}
run();
process.on("exit", () => {
console.log("Process exiting ...");
});
process.on("beforeExit", () => {
console.log("Process Before exit ...");
});
+21 -16
View File
@@ -1,30 +1,35 @@
{ {
"name": "@moduletrace/nodecid", "name": "@moduletrace/buncid",
"version": "1.0.7", "version": "1.1.3",
"description": "Simple CI/CD process", "author": "Benjamin Toby",
"repository": {
"type": "git",
"url": "https://git.tben.me/Moduletrace/buncid.git"
},
"main": "dist/index.js", "main": "dist/index.js",
"devDependencies": {
"@types/bun": "latest",
"@types/kill-port": "^2.0.3",
"@types/node": "^22.10.7"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"bin": { "bin": {
"nodecid": "./dist/index.js", "buncid": "./dist/buncid.js",
"node-ci-cd": "./dist/index.js" "buncid-builds-next": "./dist/rebuilds/next-js/index.js"
},
"scripts": {
"compile": "bun build --compile --minify --sourcemap --bytecode index.ts --outfile bin/nodecid"
}, },
"description": "Simple CI/CD process For Bun runtime",
"keywords": [ "keywords": [
"CI/CD", "CI/CD",
"Continuous Integration", "Continuous Integration",
"Continous Deployment" "Continous Deployment"
], ],
"author": "Benjamin Toby",
"license": "MIT", "license": "MIT",
"scripts": {
"compile": "bun build --compile --minify --sourcemap --bytecode index.ts --outfile bin/buncid"
},
"dependencies": { "dependencies": {
"kill-port": "^2.0.1" "kill-port": "^2.0.1"
},
"devDependencies": {
"@types/bun": "latest",
"@types/node": "^22.10.7"
},
"peerDependencies": {
"typescript": "^5.0.0"
} }
} }
+6 -1
View File
@@ -1,9 +1,14 @@
#!/bin/bash #!/bin/bash
tsc
if [ -z "$1" ]; then if [ -z "$1" ]; then
msg="Updates" msg="Updates"
else else
msg="$1" msg="$1"
fi fi
git add . && git commit -m "$msg" && git push && npm publish git add .
git commit -m "$msg"
git push
bun publish
Executable
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
if [ -z "$1" ]; then
msg="Updates"
else
msg="$1"
fi
git add .
git commit -m "$msg"
git push
@@ -0,0 +1,3 @@
export default function grabDefaultDistName(): string {
return ".buncid-next-dist";
}
+28
View File
@@ -0,0 +1,28 @@
# Rebuild your next project incrementally.
Create builds incrementally without stopping your server.
## Requirements
There are a few requirements to get this to work
### Update your `next-config.js` file
You need to update the distribution directory in your `next-config.js`. Like this:
```javascript
import type { NextConfig } from "next";
import grabDist from "@moduletrace/buncid/dist/rebuilds/next-js/grabDist";
const distDir = grabDist();
const nextConfig: NextConfig = {
/* config options here */
reactStrictMode: true,
distDir,
};
export default nextConfig;
```
That's it. This dynamically handles your distribution directory for both `dev` and `start` scripts. Your `development` environment uses the `.next` directory, while your `production` environment uses the `.buncid-next-dist` (or any dist name of your chosing) directory.
+56
View File
@@ -0,0 +1,56 @@
import fs from "fs";
import path from "path";
import grabDefaultDistName from "./(utils)/grab-default-dist-name";
const production = process.env.NODE_ENV == "production";
const isBuilding = process.env.BUILDING_APP;
type Param = {
distDir?: string;
};
/**
* # Grab the current distribution directory
* @description This returns the relative path from the CWD. Eg `./.dist/build-1`
* @returns {string | undefined}
*/
export default function grabDist(params?: Param): string | undefined {
const distDirName = params?.distDir || grabDefaultDistName();
const DIST_DIR = path.join(process.cwd(), distDirName);
if (isBuilding) {
const distDir = (() => {
try {
const buildNumber = fs.readFileSync(
`${DIST_DIR}/BUILD`,
"utf-8"
);
return `${distDirName}/build-${buildNumber}`;
} catch (error: any) {
console.log("Build Number Generation Error =>", error.message);
process.exit();
}
})();
return distDir;
}
if (production) {
const distDir = (() => {
try {
const buildNumber = fs.readFileSync(
`${DIST_DIR}/BUILD`,
"utf-8"
);
return `${distDirName}/build-${buildNumber}`;
} catch (error: any) {
console.log("Build Number Parse Error =>", error.message);
process.exit();
}
})();
return distDir;
}
return undefined;
}
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env bun
import path from "path";
import { parseArgs } from "util";
import fs from "fs";
import {
execSync,
spawnSync,
SpawnSyncOptionsWithStringEncoding,
} from "child_process";
import grabDefaultDistName from "./(utils)/grab-default-dist-name";
const args = parseArgs({
args: process.argv,
options: {
maxBuilds: {
type: "string",
alias: "m",
},
distDir: {
type: "string",
alias: "d",
},
},
allowPositionals: true,
});
const distDirName = args.values.distDir || grabDefaultDistName();
const DIST_DIR = path.join(process.cwd(), distDirName);
let PREV_BUILD_NO = "0";
const MAX_BUILDS = process.env.BUNCID_MAX_BUILDS
? Number(process.env.BUNCID_MAX_BUILDS)
: args.values.maxBuilds
? Number(args.values.maxBuilds)
: 10;
if (
MAX_BUILDS < 1 ||
Number.isNaN(MAX_BUILDS) ||
typeof MAX_BUILDS !== "number"
) {
throw new Error("Invalid MAX_BUILDS");
}
if (!fs.existsSync(DIST_DIR)) fs.mkdirSync(DIST_DIR, { recursive: true });
if (fs.existsSync(`${DIST_DIR}/BUILD`)) {
PREV_BUILD_NO = fs.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
} else {
fs.writeFileSync(`${DIST_DIR}/BUILD`, "0", "utf-8");
}
try {
const buildNumber = fs.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
const newBuildNumber = Number(buildNumber) + 1;
if (newBuildNumber < 0) {
throw new Error("Invalid Build Number");
}
fs.writeFileSync(`${DIST_DIR}/BUILD`, String(newBuildNumber));
if (newBuildNumber > MAX_BUILDS) {
const builds = fs.readdirSync(DIST_DIR);
const buildDirs = builds.filter((build) => build.match(/build-\d+/));
for (const buildDir of buildDirs) {
const buildDirPath = path.join(DIST_DIR, buildDir);
const buildDirStat = fs.statSync(buildDirPath);
if (buildDirStat.isDirectory()) {
const buildDirName = buildDir.split("-")[1];
const buildDirNumber = Number(buildDirName);
if (buildDirNumber <= newBuildNumber - MAX_BUILDS) {
fs.rmdirSync(buildDirPath, { recursive: true });
console.log("Deleted Build Directory =>", buildDirPath);
}
}
}
}
} catch (error: any) {
console.log("Build Number Parse Error =>", error.message);
process.exit(1);
}
const spawnSyncOptions: SpawnSyncOptionsWithStringEncoding = {
stdio: "inherit",
encoding: "utf-8",
shell: process.platform?.match(/win32/i) ? "bash.exe" : undefined,
env: {
...process.env,
BUILDING_APP: "true",
},
};
const build = spawnSync("bun", ["next", "build"], spawnSyncOptions);
/**
* @returns {string}
*/
function grabNewDistDir(): string {
try {
const buildNumber = fs.readFileSync(`${DIST_DIR}/BUILD`, "utf-8");
return `${distDirName}/build-${buildNumber}`;
} catch (/** @type {*} */ error: any) {
console.log("Build Number Parse Error =>", error.message);
process.exit();
}
}
const newDistDir = grabNewDistDir();
// if (!fs.existsSync(newDistDir)) {
// fs.mkdirSync(newDistDir, { recursive: true });
// }
// if (!fs.existsSync(`${newDistDir}/BUILD_ID`)) {
// fs.writeFileSync(`${newDistDir}/BUILD_ID`, "4");
// }
/**
* # Revert Directories
* @param {string} dir - New Build Directory Path
*/
function revert(dir: string) {
console.log("Build Failed!", build?.error?.message || build.stderr);
fs.writeFileSync(`${DIST_DIR}/BUILD`, PREV_BUILD_NO, "utf-8");
execSync(`rm -Rf ${dir}`, { cwd: process.cwd() });
const writeErr = build.error
? build.error.message
: build.stderr
? build.stderr.toString()
: "NO BUILD_ID found in New Build Folder";
fs.writeFileSync(
`${DIST_DIR}/LAST_BUILD_FAIL`,
Date() + "\n\n" + writeErr,
"utf-8"
);
process.exit(1);
}
if (
build.error ||
build.stderr ||
build.status != 0 ||
!fs.existsSync(`${newDistDir}/BUILD_ID`)
) {
revert(newDistDir);
throw new Error("Build Failed!");
}
process.on("exit", () => {
const onExitDir = grabNewDistDir();
if (!fs.existsSync(`${onExitDir}/BUILD_ID`)) {
revert(onExitDir);
}
});
+1
View File
@@ -6,6 +6,7 @@ export interface NodeCIConfig {
first_run?: boolean; first_run?: boolean;
port?: string | number | (string | number)[]; port?: string | number | (string | number)[];
build?: NodeCIBuild; build?: NodeCIBuild;
debounce?: number;
} }
export interface NodeCIBuild { export interface NodeCIBuild {
+48 -14
View File
@@ -1,11 +1,6 @@
import { ChildProcess } from "child_process"; import { ChildProcess } from "child_process";
import colors from "./console-colors"; import colors from "./console-colors";
import kill from "kill-port"; import killPort from "kill-port";
let childProcess: ChildProcess | null = null;
const pTitle = "nodecid";
process.title = pTitle;
/** /**
* ## Kill Child Process Function * ## Kill Child Process Function
@@ -13,27 +8,66 @@ process.title = pTitle;
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
*/ */
export default async function killChild( export default async function killChild(
/**
* Child Process to be killed
*/
childProcess?: ChildProcess,
/**
* Port/ports to be killed
*/
port?: string | number | (string | number)[] port?: string | number | (string | number)[]
): Promise<boolean> { ): Promise<boolean> {
if (!childProcess) return false; if (!childProcess) return false;
try { try {
const childProcessPID = childProcess.pid; const childKilled = childProcess.kill();
childProcess.kill();
if (childKilled) {
return true;
} else {
return await killPortFn(childProcess, port);
}
} catch (error: any) {
await killPortFn(childProcess, port);
console.log(
`${colors.FgRed}Error:${colors.Reset} Child Process couldn't be killed! ${error.message}`
);
return false;
}
}
async function killPortFn(
/**
* Child Process to be killed
*/
childProcess?: ChildProcess,
/**
* Port/ports to be killed
*/
port?: string | number | (string | number)[]
): Promise<boolean> {
if (!childProcess) return false;
try {
if (typeof port == "object" && port?.[0]) { if (typeof port == "object" && port?.[0]) {
for (let i = 0; i < port.length; i++) { for (let i = 0; i < port.length; i++) {
const singlePort = port[i]; const singlePort = port[i];
await kill(Number(singlePort)); await killPort(Number(singlePort), "tcp");
} }
childProcess.kill();
return true;
} else if (port) { } else if (port) {
await kill(Number(port)); await killPort(Number(port), "tcp");
childProcess.kill();
return true;
} else {
return false;
} }
} catch (_err: any) {
return true;
} catch (error: any) {
console.log( console.log(
`${colors.FgRed}Error:${colors.Reset} Child Process couldn't be killed! ${error.message}` `${colors.FgRed}Error:${colors.Reset} Child Process PORT couldn't be killed! ${_err.message}`
); );
return false; return false;
} }
+3 -8
View File
@@ -1,13 +1,8 @@
import { spawn, ChildProcess } from "child_process"; import { spawn, ChildProcess } from "child_process";
import colors from "./console-colors"; import colors from "./console-colors";
let redeployments = 0;
const KILL_SIGNAL: NodeJS.Signals | number = "SIGTERM"; const KILL_SIGNAL: NodeJS.Signals | number = "SIGTERM";
const pTitle = "nodecid";
process.title = pTitle;
/** /**
* ## Preflight Function * ## Preflight Function
* @param {string} command * @param {string} command
@@ -29,14 +24,14 @@ export default function run(command: string): ChildProcess | null {
throw new Error("No Starting Command Found in command string!"); throw new Error("No Starting Command Found in command string!");
} }
let childProcess = spawn(firstCommand, startCommandArray, { let newChildProcess = spawn(firstCommand, startCommandArray, {
stdio: "inherit", stdio: "inherit",
killSignal: KILL_SIGNAL, killSignal: KILL_SIGNAL,
}); });
redeployments++; global.REDEPLOYMENTS = global.REDEPLOYMENTS + 1;
return childProcess; return newChildProcess;
} catch (error: any) { } catch (error: any) {
console.log( console.log(
`${colors.FgRed}Error:${colors.Reset} running start command => ${error.message}` `${colors.FgRed}Error:${colors.Reset} running start command => ${error.message}`
+78 -77
View File
@@ -1,28 +1,27 @@
import fs from "fs"; import fs from "fs";
import { ChildProcess } from "child_process"; import { ChildProcess } from "child_process";
import colors from "./console-colors"; import colors from "./console-colors";
import kill from "kill-port";
import preflightFn from "./preflight"; import preflightFn from "./preflight";
import run from "./run"; import run from "./run";
import killChild from "./kill-child"; import killChild from "./kill-child";
let redeployments = 0;
let childProcess: ChildProcess | null = null; let childProcess: ChildProcess | null = null;
const pTitle = "nodecid"; const pTitle = "buncid";
process.title = pTitle; process.title = pTitle;
/** let timeout: any;
* # Start the process
* @param {object} param0 type Param = {
* @param {string} param0.command command: string;
* @param {string[] | string} param0.preflight preflight?: string[] | string;
* @param {string[] | string} [param0.postflight] postflight?: string[] | string;
* @param {string} param0.redeploy_file redeploy_file: string;
* @param {string | number | (string | number)[]} [param0.port] - The port to kill on rebuild port?: string | number | (string | number)[];
* @param {boolean} [param0.first_run] - Whether to run the preflight on first run. Default `false` first_run?: boolean;
*/ debounce?: number;
};
export default function startProcess({ export default function startProcess({
command, command,
preflight, preflight,
@@ -30,25 +29,14 @@ export default function startProcess({
redeploy_file, redeploy_file,
port, port,
first_run, first_run,
}: { debounce,
command: string; }: Param) {
preflight: string[] | string; const DEBOUNCE = debounce || 1000;
postflight?: string[] | string;
redeploy_file: string;
port?: string | number | (string | number)[];
first_run?: boolean;
}) {
try { try {
if (first_run) { if (first_run) {
console.log("First Run ..."); console.log("First Run ...");
const runPreflight = preflightFn(preflight); const runFirstPreflight = preflightFn(preflight);
}
if (!preflight) {
console.log(
`${colors.FgRed}Error:${colors.Reset} No preflight included in config file. If you don't want to run any preflight command simply add an empty array.`
);
process.exit();
} }
childProcess = run(command); childProcess = run(command);
@@ -63,57 +51,70 @@ export default function startProcess({
console.log("Watching", redeploy_file); console.log("Watching", redeploy_file);
fs.watchFile(redeploy_file, { interval: 100 }, (curr, prev) => { fs.watchFile(redeploy_file, { interval: 100 }, (curr, prev) => {
console.log(`${colors.BgBlue}File Changed${colors.Reset}`); if (global.DEPLOYING == 1) return;
if (redeployments == 0) return; clearTimeout(timeout);
if (childProcess) { timeout = setTimeout(() => {
console.log("******************************"); console.log(`${colors.BgBlue}File Changed${colors.Reset}`);
console.log(
`******** ${colors.FgBlue}Rebuilding ${colors.FgMagenta}${redeployments}${colors.Reset} ********`
);
console.log("******************************");
try { if (global.REDEPLOYMENTS == 0) {
const runPreflight = preflightFn(preflight); return;
if (!runPreflight) {
// TODO: Action to take if preflight fails
console.log(
`${colors.FgRed}Error:${colors.Reset} Preflight Failed.`
);
} else {
killChild(port).then((kill) => {
if (kill) {
childProcess = run(command);
if (postflight) {
const runPostflight = preflightFn(
postflight,
true
);
if (!runPostflight) {
// TODO: Action to take if postflight fails
console.log(
`${colors.FgRed}Error:${colors.Reset} Postflight Failed.`
);
}
}
} else {
process.exit();
}
});
}
} catch (error: any) {
console.log(
`${colors.FgRed}Error:${colors.Reset} killing child processes => ${error.message}`
);
process.exit();
} }
}
global.DEPLOYING = 1;
if (childProcess) {
console.log("******************************");
console.log(
`******** ${colors.FgBlue}Rebuilding ${colors.FgMagenta}${global.REDEPLOYMENTS}${colors.Reset} ********`
);
console.log("******************************");
try {
const runPreflight = preflightFn(preflight);
if (!runPreflight) {
console.log(
`${colors.FgRed}Error:${colors.Reset} Preflight Failed.`
);
setTimeout(() => {
global.DEPLOYING = 0;
}, 2000);
} else {
killChild(childProcess, port).then((kill) => {
if (kill) {
childProcess = run(command);
if (postflight) {
const runPostflight = preflightFn(
postflight,
true
);
if (!runPostflight) {
console.log(
`${colors.FgRed}Error:${colors.Reset} Postflight Failed.`
);
}
}
global.DEPLOYING = 0;
} else {
process.exit();
}
});
}
} catch (error: any) {
console.log(
`${colors.FgRed}Error:${colors.Reset} killing child processes => ${error.message}`
);
process.exit();
}
} else {
global.DEPLOYING = 0;
}
}, DEBOUNCE);
}); });
} catch (error: any) { } catch (error: any) {
console.log( console.log(
-3
View File
@@ -1,3 +0,0 @@
function githubWebhook() {
return true;
}