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 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,5 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const app_js_1 = require("./app.js");
5
+ app_js_1.program.parse(process.argv);
@@ -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
+ }
@@ -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
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ export const configSchema = z.object({
3
+ apps: z.array(z.object({
4
+ name: z.string(),
5
+ cwd: z.string(),
6
+ run: z.string(),
7
+ })),
8
+ });
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
+ }