pomitu 1.0.0
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.
- package/LICENSE +15 -0
- package/README.md +103 -0
- package/dist/app.js +23 -0
- package/dist/cli.js +5 -0
- package/dist/commands/flush.js +20 -0
- package/dist/commands/ls.js +25 -0
- package/dist/commands/start.js +56 -0
- package/dist/commands/stop.js +49 -0
- package/dist/helpers.js +46 -0
- package/dist/schema.js +8 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Flawid D'Souza
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Pomitu
|
|
2
|
+
|
|
3
|
+
Pomitu is a simple process manager inspired by PM2
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
To install Pomitu globally, run:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install -g pomitu
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Pomitu provides several commands to manage your applications:
|
|
16
|
+
|
|
17
|
+
### Start an application
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
pomitu start <config-file>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This command starts and daemonizes an app based on the configuration file provided.
|
|
24
|
+
|
|
25
|
+
### Stop an application
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
pomitu stop <name>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This command stops a running app. Use `pomitu stop all` to stop all running apps.
|
|
32
|
+
|
|
33
|
+
### List running applications
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
pomitu ls
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This command lists all currently running applications managed by Pomitu.
|
|
40
|
+
|
|
41
|
+
### Flush logs
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
pomitu flush [name]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This command flushes the logs for all apps or a specific app if a name is provided.
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
Pomitu uses a YAML configuration file to define the applications you want to manage. Here's an example structure:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
apps:
|
|
55
|
+
- name: "App 1"
|
|
56
|
+
cwd: "/path/to/app1"
|
|
57
|
+
run: "npm start"
|
|
58
|
+
- name: "App 2"
|
|
59
|
+
cwd: "/path/to/app2"
|
|
60
|
+
run: "node server.js"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
If you want to contribute to Pomitu or run it in development mode:
|
|
66
|
+
|
|
67
|
+
1. Clone the repository
|
|
68
|
+
2. Install dependencies:
|
|
69
|
+
```sh
|
|
70
|
+
npm install
|
|
71
|
+
```
|
|
72
|
+
3. Run in development mode:
|
|
73
|
+
```sh
|
|
74
|
+
npm run dev
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Linting
|
|
78
|
+
|
|
79
|
+
To lint the code:
|
|
80
|
+
```sh
|
|
81
|
+
npm run lint
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
To lint and auto-fix:
|
|
85
|
+
```sh
|
|
86
|
+
npm run lint-fix
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Building
|
|
90
|
+
|
|
91
|
+
To build the project:
|
|
92
|
+
```sh
|
|
93
|
+
npm run build
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
To build and run:
|
|
97
|
+
```sh
|
|
98
|
+
npm start
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
[ISC License](LICENSE)
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { start } from './commands/start.js';
|
|
4
|
+
import { flush } from './commands/flush.js';
|
|
5
|
+
import { stop } from './commands/stop.js';
|
|
6
|
+
import { mkdirSync } from 'node:fs';
|
|
7
|
+
import { ls } from './commands/ls.js';
|
|
8
|
+
import { getPomituDirectory, getPomituLogsDirectory, getPomituPidsDirectory } from './helpers.js';
|
|
9
|
+
function init() {
|
|
10
|
+
mkdirSync(getPomituDirectory(), { recursive: true });
|
|
11
|
+
mkdirSync(getPomituLogsDirectory(), { recursive: true });
|
|
12
|
+
mkdirSync(getPomituPidsDirectory(), { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
init();
|
|
15
|
+
const program = new Command();
|
|
16
|
+
program
|
|
17
|
+
.name('pomitu')
|
|
18
|
+
.description('Pomitu is a process manager inspired by PM2')
|
|
19
|
+
.addCommand(start)
|
|
20
|
+
.addCommand(flush)
|
|
21
|
+
.addCommand(stop)
|
|
22
|
+
.addCommand(ls)
|
|
23
|
+
.parse();
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getPomituLogsDirectory } from '../helpers.js';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
const pomituLogsDirectory = getPomituLogsDirectory();
|
|
6
|
+
export const flush = new Command('flush')
|
|
7
|
+
.description('flush logs')
|
|
8
|
+
.argument('[name]', 'name of the app whose logs you want to flush')
|
|
9
|
+
.action((name) => {
|
|
10
|
+
let logs = fs.readdirSync(pomituLogsDirectory);
|
|
11
|
+
if (name) {
|
|
12
|
+
logs = logs.filter((log) => log.startsWith(name));
|
|
13
|
+
}
|
|
14
|
+
const fullLogPaths = logs.map((log) => path.join(pomituLogsDirectory, log));
|
|
15
|
+
for (const logFilePath of fullLogPaths) {
|
|
16
|
+
console.log(`Flushing ${logFilePath}`);
|
|
17
|
+
fs.unlinkSync(logFilePath);
|
|
18
|
+
}
|
|
19
|
+
console.log('Logs flushed');
|
|
20
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { getPomituPidsDirectory, pidIsRunning } from '../helpers.js';
|
|
4
|
+
export const ls = new Command('ls')
|
|
5
|
+
.description('list all running apps')
|
|
6
|
+
.action(() => {
|
|
7
|
+
const pidsDirectory = getPomituPidsDirectory();
|
|
8
|
+
const pidFiles = fs.readdirSync(pidsDirectory);
|
|
9
|
+
if (pidFiles.length === 0) {
|
|
10
|
+
console.log('No running processes found');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
console.log('Running processes:');
|
|
14
|
+
for (const pidFile of pidFiles) {
|
|
15
|
+
const pidFilePath = `${pidsDirectory}/${pidFile}`;
|
|
16
|
+
const pid = parseInt(fs.readFileSync(pidFilePath, 'utf-8'));
|
|
17
|
+
const appName = pidFile.replace('.pid', '');
|
|
18
|
+
if (pidIsRunning(pid)) {
|
|
19
|
+
console.log(`- ${appName} (pid: ${pid})`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.warn(`- ${appName} (pid: ${pid}) is not running`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { parse } from 'shell-quote';
|
|
4
|
+
import { readConfig, getProcessLogOutFilePath, getProcessLogErrorFilePath, getProcessPidFilePath, pidIsRunning, getFileNameFriendlyName, } from '../helpers.js';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
export const start = new Command('start')
|
|
7
|
+
.description('start and daemonize an app')
|
|
8
|
+
.argument('<name>', '[name|namespace|file|ecosystem|id...]')
|
|
9
|
+
.action((name) => {
|
|
10
|
+
const config = readConfig(name);
|
|
11
|
+
for (const app of config.apps) {
|
|
12
|
+
console.log(`Starting: ${app.name} (${app.cwd})`);
|
|
13
|
+
if (!fs.existsSync(app.cwd)) {
|
|
14
|
+
console.error(`Directory ${app.cwd} does not exist`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const run = parse(app.run);
|
|
18
|
+
if (!run.length) {
|
|
19
|
+
console.error(`Invalid run command for ${app.name}: ${app.run}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const fileNameFriendAppName = getFileNameFriendlyName(app.name);
|
|
23
|
+
const existingPidFilePath = getProcessPidFilePath(fileNameFriendAppName);
|
|
24
|
+
if (fs.existsSync(existingPidFilePath)) {
|
|
25
|
+
const existingPid = parseInt(fs.readFileSync(existingPidFilePath, 'utf-8'));
|
|
26
|
+
if (pidIsRunning(existingPid)) {
|
|
27
|
+
console.warn(`Process ${app.name} is already running with pid ${existingPid}`);
|
|
28
|
+
console.log(`Stopping ${app.name} at pid ${existingPid}`);
|
|
29
|
+
try {
|
|
30
|
+
process.kill(existingPid);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
const error = e;
|
|
34
|
+
console.error(`Error stopping ${app.name}: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
fs.unlinkSync(existingPidFilePath);
|
|
38
|
+
}
|
|
39
|
+
const stdout = fs.openSync(getProcessLogOutFilePath(fileNameFriendAppName), 'a');
|
|
40
|
+
const stderr = fs.openSync(getProcessLogErrorFilePath(fileNameFriendAppName), 'a');
|
|
41
|
+
const startedProcess = spawn(run[0], run.slice(1), {
|
|
42
|
+
cwd: app.cwd,
|
|
43
|
+
stdio: ['ignore', stdout, stderr],
|
|
44
|
+
detached: true,
|
|
45
|
+
});
|
|
46
|
+
startedProcess.on('error', (error) => {
|
|
47
|
+
console.error(`Error starting ${app.name}: ${error.message}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
startedProcess.on('spawn', () => {
|
|
51
|
+
console.log(`Started: ${app.name} with pid ${startedProcess.pid}`);
|
|
52
|
+
});
|
|
53
|
+
startedProcess.unref();
|
|
54
|
+
fs.writeFileSync(getProcessPidFilePath(fileNameFriendAppName), startedProcess.pid.toString());
|
|
55
|
+
}
|
|
56
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import { getPomituPidsDirectory, getFileNameFriendlyName, pidIsRunning, } from '../helpers.js';
|
|
4
|
+
export const stop = new Command('stop')
|
|
5
|
+
.description('stop a running app or all apps')
|
|
6
|
+
.argument('<name>', 'name of the app to stop or "all" to stop all apps')
|
|
7
|
+
.action((name) => {
|
|
8
|
+
const pidsDirectory = getPomituPidsDirectory();
|
|
9
|
+
const pidFiles = fs.readdirSync(pidsDirectory);
|
|
10
|
+
if (name === 'all') {
|
|
11
|
+
if (pidFiles.length === 0) {
|
|
12
|
+
console.warn('No running processes found');
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
for (const pidFile of pidFiles) {
|
|
16
|
+
stopProcess(pidFile, name);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
const fileNameFriendlyName = getFileNameFriendlyName(name);
|
|
21
|
+
const pidFile = `${fileNameFriendlyName}.pid`;
|
|
22
|
+
if (pidFiles.includes(pidFile)) {
|
|
23
|
+
stopProcess(pidFile, name);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.warn(`No running process found for ${name}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
function stopProcess(pidFile, appName) {
|
|
31
|
+
const pidFilePath = `${getPomituPidsDirectory()}/${pidFile}`;
|
|
32
|
+
const pid = parseInt(fs.readFileSync(pidFilePath, 'utf-8'));
|
|
33
|
+
if (pidIsRunning(pid)) {
|
|
34
|
+
console.log(`Stopping ${appName} with pid ${pid}`);
|
|
35
|
+
try {
|
|
36
|
+
process.kill(pid);
|
|
37
|
+
fs.unlinkSync(pidFilePath);
|
|
38
|
+
console.log(`${appName} with pid ${pid} stopped`);
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
const error = e;
|
|
42
|
+
console.error(`Error stopping ${appName} with pid ${pid}: ${error.message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.warn(`${appName} with pid ${pid} is not running`);
|
|
47
|
+
fs.unlinkSync(pidFilePath);
|
|
48
|
+
}
|
|
49
|
+
}
|
package/dist/helpers.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as YAML from 'yaml';
|
|
3
|
+
import { configSchema } from './schema.js';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
export function readConfig(configFilePath) {
|
|
7
|
+
const configParsed = YAML.parse(fs.readFileSync(configFilePath, 'utf8'));
|
|
8
|
+
const { success, data: config } = configSchema.safeParse(configParsed);
|
|
9
|
+
if (!success) {
|
|
10
|
+
console.error('Invalid config file');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
return config;
|
|
14
|
+
}
|
|
15
|
+
export function getPomituDirectory() {
|
|
16
|
+
const homeDirectory = homedir();
|
|
17
|
+
return path.join(homeDirectory, '.pomitu');
|
|
18
|
+
}
|
|
19
|
+
export function getPomituLogsDirectory() {
|
|
20
|
+
return path.join(getPomituDirectory(), 'logs');
|
|
21
|
+
}
|
|
22
|
+
export function getPomituPidsDirectory() {
|
|
23
|
+
return path.join(getPomituDirectory(), 'pids');
|
|
24
|
+
}
|
|
25
|
+
export function getFileNameFriendlyName(name) {
|
|
26
|
+
return name.replaceAll(' ', '-').toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
export function getProcessLogOutFilePath(name) {
|
|
29
|
+
return path.join(getPomituLogsDirectory(), `${name}-out.log`);
|
|
30
|
+
}
|
|
31
|
+
export function getProcessLogErrorFilePath(name) {
|
|
32
|
+
return path.join(getPomituLogsDirectory(), `${name}-error.log`);
|
|
33
|
+
}
|
|
34
|
+
export function getProcessPidFilePath(name) {
|
|
35
|
+
return path.join(getPomituPidsDirectory(), `${name}.pid`);
|
|
36
|
+
}
|
|
37
|
+
// From: https://stackoverflow.com/a/21296291/4932305
|
|
38
|
+
export function pidIsRunning(pid) {
|
|
39
|
+
try {
|
|
40
|
+
process.kill(pid, 0);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/schema.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pomitu",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pomitu is a process manager inspired by PM2",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx src/app.ts",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/app.js",
|
|
10
|
+
"lint": "eslint",
|
|
11
|
+
"lint-fix": "eslint --fix",
|
|
12
|
+
"prepublish": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@eslint/js": "^9.12.0",
|
|
16
|
+
"@stylistic/eslint-plugin-js": "^2.9.0",
|
|
17
|
+
"@types/node": "^22.7.4",
|
|
18
|
+
"@types/shell-quote": "^1.7.5",
|
|
19
|
+
"eslint": "^9.12.0",
|
|
20
|
+
"globals": "^15.10.0",
|
|
21
|
+
"tsx": "^4.19.1",
|
|
22
|
+
"typescript": "^4.4.3",
|
|
23
|
+
"typescript-eslint": "^8.8.0"
|
|
24
|
+
},
|
|
25
|
+
"main": "dist/app.js",
|
|
26
|
+
"bin": {
|
|
27
|
+
"pomitu": "./dist/app.js"
|
|
28
|
+
},
|
|
29
|
+
"author": "flawiddsouza",
|
|
30
|
+
"license": "ISC",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"commander": "^12.1.0",
|
|
33
|
+
"shell-quote": "^1.8.1",
|
|
34
|
+
"yaml": "^2.5.1",
|
|
35
|
+
"zod": "^3.23.8"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
]
|
|
40
|
+
}
|