neex 0.1.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/README.md +161 -0
- package/bin/neex.ts +38 -0
- package/dist/bin/neex.js +4 -0
- package/dist/src/cli.js +145 -0
- package/dist/src/index.js +70 -0
- package/dist/src/logger.js +237 -0
- package/dist/src/runner.js +320 -0
- package/dist/src/types.js +2 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<a href="https://github.com/Nextpress-cc">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://foshati.storage.c2.liara.space/Nextpress.png">
|
|
5
|
+
<img alt="Nextpress logo" src="https://foshati.storage.c2.liara.space/Nextpress.png" height="150" style="border-radius: 12px;">
|
|
6
|
+
</picture>
|
|
7
|
+
</a>
|
|
8
|
+
|
|
9
|
+
# Neex v0.1.0
|
|
10
|
+
|
|
11
|
+
### 🚀 Neex: The Modern Build System for Polyrepo-in-Monorepo Architecture
|
|
12
|
+
|
|
13
|
+
[](https://www.npmjs.com/package/neex)
|
|
14
|
+
[](https://www.npmjs.com/package/neex)
|
|
15
|
+
[](https://github.com/Nextpress-cc/neex/blob/main/LICENSE)
|
|
16
|
+
[](https://github.com/Nextpress-cc)
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
## 🎯 Overview
|
|
20
|
+
|
|
21
|
+
neex is a modern build system designed to bridge the gap between polyrepo and monorepo architectures. It provides powerful script execution capabilities with features like parallel processing, colored output, and intelligent build orchestration. Whether you're managing a complex monorepo or coordinating multiple repositories, neex makes your development workflow more efficient and visually organized.
|
|
22
|
+
|
|
23
|
+
## ✨ Key Features
|
|
24
|
+
|
|
25
|
+
- 🎨 **Colored Output** - Distinguish commands with unique colors
|
|
26
|
+
- ⚡ **Dual Execution Modes** - Run commands in parallel (`p`, `runx`) or sequence (`s`, `run`)
|
|
27
|
+
- ⏱️ **Smart Timing** - Track execution time for each command
|
|
28
|
+
- 🛑 **Error Control** - Stop on first error (perfect for CI/CD)
|
|
29
|
+
- 🔢 **Parallel Control** - Limit concurrent processes with `--max-parallel`
|
|
30
|
+
- 💻 **Clean Output** - Structured and readable command output
|
|
31
|
+
- 🛡️ **Safe Shutdown** - Graceful process termination on interrupt
|
|
32
|
+
- 🤫 **Flexible Display** - Control prefixes, timing, and output visibility
|
|
33
|
+
- 🧰 **Node.js API** - Programmatic usage in your applications
|
|
34
|
+
|
|
35
|
+
## ⚡ Quick Start
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Global install
|
|
39
|
+
npm i -g neex
|
|
40
|
+
|
|
41
|
+
# Local install
|
|
42
|
+
npm i -D neex # npm
|
|
43
|
+
yarn add -D neex # yarn
|
|
44
|
+
pnpm add -D neex # pnpm
|
|
45
|
+
bun add -D neex # bun
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 🖥️ Usage
|
|
49
|
+
|
|
50
|
+
### Commands
|
|
51
|
+
|
|
52
|
+
- `runx` (alias: `p`) - Run in **parallel** (default)
|
|
53
|
+
- `run` (alias: `s`) - Run **sequentially**
|
|
54
|
+
|
|
55
|
+
### Examples
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Parallel execution (default)
|
|
59
|
+
neex p "echo Task 1" "echo Task 2" "echo Task 3"
|
|
60
|
+
|
|
61
|
+
# Sequential execution
|
|
62
|
+
neex s "echo Step 1" "echo Step 2" "echo Step 3"
|
|
63
|
+
|
|
64
|
+
# Parallel with sequential flag
|
|
65
|
+
neex p -q "echo Step 1" "echo Step 2" "echo Step 3"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 🛠️ Options
|
|
69
|
+
|
|
70
|
+
| Flag | Alias | Description | Default |
|
|
71
|
+
|------|-------|-------------|----------|
|
|
72
|
+
| `--no-color` | `-c` | Disable colors | `false` |
|
|
73
|
+
| `--no-timing` | `-t` | Hide timing | `false` |
|
|
74
|
+
| `--no-prefix` | `-p` | Hide prefixes | `false` |
|
|
75
|
+
| `--stop-on-error` | `-s` | Stop on failure | `false` |
|
|
76
|
+
| `--no-output` | `-o` | Hide all output | `false` |
|
|
77
|
+
| `--max-parallel` | `-m` | Max parallel tasks | CPU count |
|
|
78
|
+
| `--sequential` | `-q` | Force sequential | `false` |
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
### Advanced Example
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Run tests & build with max 2 parallel tasks, stop on error
|
|
85
|
+
neex p -s -m 2 -t "npm test" "npm run build" "npm run lint"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 📦 Node.js API
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
import { run } from 'neex';
|
|
92
|
+
// or: const { run } = require('neex');
|
|
93
|
+
|
|
94
|
+
async function main() {
|
|
95
|
+
try {
|
|
96
|
+
// Sequential execution
|
|
97
|
+
await run(['echo Step 1', 'echo Step 2'], {
|
|
98
|
+
parallel: false,
|
|
99
|
+
stopOnError: true,
|
|
100
|
+
color: true
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Parallel execution (max 2)
|
|
104
|
+
await run(['npm test', 'npm run build'], {
|
|
105
|
+
parallel: true,
|
|
106
|
+
maxParallel: 2,
|
|
107
|
+
stopOnError: true
|
|
108
|
+
});
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error('Failed:', error);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### API Options
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
interface RunOptions {
|
|
120
|
+
parallel?: boolean; // Run in parallel (default: true)
|
|
121
|
+
maxParallel?: number; // Max parallel processes (default: CPU count)
|
|
122
|
+
color?: boolean; // Enable colors (default: true)
|
|
123
|
+
prefix?: boolean; // Show command prefix (default: true)
|
|
124
|
+
showTiming?: boolean; // Show timing info (default: true)
|
|
125
|
+
printOutput?: boolean; // Show command output (default: true)
|
|
126
|
+
stopOnError?: boolean; // Stop on failure (default: false)
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 🔄 CI/CD Integration
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
# GitHub Actions example
|
|
134
|
+
steps:
|
|
135
|
+
- name: Test & Build
|
|
136
|
+
run: neex s -s "npm test" "npm run build"
|
|
137
|
+
|
|
138
|
+
- name: Parallel Tasks
|
|
139
|
+
run: neex p -s -m 4 "npm run lint" "npm test" "npm run e2e"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## 💡 Real-world Examples
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Dev servers
|
|
146
|
+
neex p "cd frontend && npm dev" "cd api && npm dev"
|
|
147
|
+
|
|
148
|
+
# Monorepo build
|
|
149
|
+
neex p -m 2 "npm run build:ui" "npm run build:api"
|
|
150
|
+
|
|
151
|
+
# Deploy pipeline
|
|
152
|
+
neex s -s "npm test" "npm run build" "npm run deploy"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 🤝 Contributing
|
|
156
|
+
|
|
157
|
+
We welcome contributions! Check our [issues page](https://github.com/Nextpress-cc/neex/issues).
|
|
158
|
+
|
|
159
|
+
## 📄 License
|
|
160
|
+
|
|
161
|
+
MIT
|
package/bin/neex.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
require('../src/cli').default();
|
|
4
|
+
|
|
5
|
+
// src/types.ts
|
|
6
|
+
export interface RunOptions {
|
|
7
|
+
// اجرای موازی یا ترتیبی
|
|
8
|
+
parallel: boolean;
|
|
9
|
+
// حداکثر تعداد اجرای همزمان در حالت موازی
|
|
10
|
+
maxParallel?: number;
|
|
11
|
+
// نمایش خروجی هر دستور
|
|
12
|
+
printOutput: boolean;
|
|
13
|
+
// رنگی کردن خروجی
|
|
14
|
+
color: boolean;
|
|
15
|
+
// نمایش زمان اجرا
|
|
16
|
+
showTiming: boolean;
|
|
17
|
+
// نمایش نام اسکریپت در کنار خروجی
|
|
18
|
+
prefix: boolean;
|
|
19
|
+
// اگر خطا رخ دهد اجرای بقیه دستورات متوقف شود
|
|
20
|
+
stopOnError: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RunResult {
|
|
24
|
+
command: string;
|
|
25
|
+
success: boolean;
|
|
26
|
+
code: number | null;
|
|
27
|
+
startTime: Date;
|
|
28
|
+
endTime: Date | null;
|
|
29
|
+
duration?: number;
|
|
30
|
+
error?: Error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CommandOutput {
|
|
34
|
+
command: string;
|
|
35
|
+
type: 'stdout' | 'stderr';
|
|
36
|
+
data: string;
|
|
37
|
+
timestamp: Date;
|
|
38
|
+
}
|
package/dist/bin/neex.js
ADDED
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// src/cli.ts - Updated version
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const index_js_1 = require("./index.js");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const figures_1 = __importDefault(require("figures"));
|
|
11
|
+
const { version } = require('../../package.json');
|
|
12
|
+
function cli() {
|
|
13
|
+
const program = new commander_1.Command();
|
|
14
|
+
let cleanupRunner = null;
|
|
15
|
+
program
|
|
16
|
+
.name('neex')
|
|
17
|
+
.description('Professional script runner with beautiful colored output')
|
|
18
|
+
.version(version);
|
|
19
|
+
// Main command for sequential execution (similar to run-s)
|
|
20
|
+
program
|
|
21
|
+
.command('run <commands...>')
|
|
22
|
+
.alias('s')
|
|
23
|
+
.description('Run commands sequentially')
|
|
24
|
+
.option('-c, --no-color', 'Disable colored output')
|
|
25
|
+
.option('-t, --no-timing', 'Hide timing information')
|
|
26
|
+
.option('-p, --no-prefix', 'Hide command prefix')
|
|
27
|
+
.option('-s, --stop-on-error', 'Stop on first error')
|
|
28
|
+
.option('-o, --no-output', 'Hide command output')
|
|
29
|
+
.option('-m, --minimal', 'Use minimal output format')
|
|
30
|
+
.action(async (commands, options) => {
|
|
31
|
+
try {
|
|
32
|
+
await (0, index_js_1.run)(commands, {
|
|
33
|
+
parallel: false,
|
|
34
|
+
color: options.color,
|
|
35
|
+
showTiming: options.timing,
|
|
36
|
+
prefix: options.prefix,
|
|
37
|
+
stopOnError: options.stopOnError,
|
|
38
|
+
printOutput: options.output,
|
|
39
|
+
minimalOutput: options.minimal,
|
|
40
|
+
registerCleanup: (cleanup) => { cleanupRunner = cleanup; }
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof Error) {
|
|
45
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} Error: ${error.message}`));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown error occurred`));
|
|
49
|
+
}
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// runx command: parallel execution by default (with alias 'p'), can run sequentially with -q
|
|
54
|
+
program
|
|
55
|
+
.command('runx <commands...>', { isDefault: true })
|
|
56
|
+
.alias('p')
|
|
57
|
+
.description('Run commands in parallel (default) or sequentially with -q. Alias: p')
|
|
58
|
+
.option('-c, --no-color', 'Disable colored output')
|
|
59
|
+
.option('-t, --no-timing', 'Hide timing information')
|
|
60
|
+
.option('-p, --no-prefix', 'Hide command prefix')
|
|
61
|
+
.option('-s, --stop-on-error', 'Stop on first error')
|
|
62
|
+
.option('-o, --no-output', 'Hide command output')
|
|
63
|
+
.option('-m, --minimal', 'Use minimal output format')
|
|
64
|
+
.option('-x, --max-parallel <number>', 'Maximum number of parallel processes', parseInt)
|
|
65
|
+
.option('-q, --sequential', 'Run commands sequentially instead of in parallel')
|
|
66
|
+
.action(async (commands, options) => {
|
|
67
|
+
try {
|
|
68
|
+
await (0, index_js_1.run)(commands, {
|
|
69
|
+
parallel: !options.sequential,
|
|
70
|
+
maxParallel: options.maxParallel,
|
|
71
|
+
color: options.color,
|
|
72
|
+
showTiming: options.timing,
|
|
73
|
+
prefix: options.prefix,
|
|
74
|
+
stopOnError: options.stopOnError,
|
|
75
|
+
printOutput: options.output,
|
|
76
|
+
minimalOutput: options.minimal,
|
|
77
|
+
registerCleanup: (cleanup) => { cleanupRunner = cleanup; }
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof Error) {
|
|
82
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} Error: ${error.message}`));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown error occurred`));
|
|
86
|
+
}
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Add a new servers command specifically optimized for running web servers
|
|
91
|
+
program
|
|
92
|
+
.command('servers <commands...>')
|
|
93
|
+
.alias('srv')
|
|
94
|
+
.description('Run multiple servers with optimized output for API, frontend, etc.')
|
|
95
|
+
.option('-c, --no-color', 'Disable colored output')
|
|
96
|
+
.option('-t, --no-timing', 'Hide timing information')
|
|
97
|
+
.option('-p, --no-prefix', 'Hide command prefix')
|
|
98
|
+
.option('-s, --stop-on-error', 'Stop when any server crashes')
|
|
99
|
+
.option('-x, --max-parallel <number>', 'Maximum number of parallel servers', parseInt)
|
|
100
|
+
.option('-g, --group-output', 'Group outputs by server')
|
|
101
|
+
.action(async (commands, options) => {
|
|
102
|
+
try {
|
|
103
|
+
console.log(chalk_1.default.blue(`${figures_1.default.info} Starting servers in parallel mode...`));
|
|
104
|
+
await (0, index_js_1.run)(commands, {
|
|
105
|
+
parallel: true,
|
|
106
|
+
maxParallel: options.maxParallel,
|
|
107
|
+
color: options.color,
|
|
108
|
+
showTiming: options.timing,
|
|
109
|
+
prefix: options.prefix,
|
|
110
|
+
stopOnError: options.stopOnError,
|
|
111
|
+
printOutput: true,
|
|
112
|
+
registerCleanup: (cleanup) => { cleanupRunner = cleanup; },
|
|
113
|
+
groupOutput: options.groupOutput,
|
|
114
|
+
isServerMode: true // Special flag for server mode formatting
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (error instanceof Error) {
|
|
119
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} Server Error: ${error.message}`));
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} An unknown server error occurred`));
|
|
123
|
+
}
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
program.parse(process.argv);
|
|
128
|
+
// Show help if no commands specified
|
|
129
|
+
if (program.args.length === 0) {
|
|
130
|
+
program.help();
|
|
131
|
+
}
|
|
132
|
+
// Graceful shutdown handling
|
|
133
|
+
const handleSignal = (signal) => {
|
|
134
|
+
console.log(`\n${chalk_1.default.yellow(`${figures_1.default.warning} Received ${signal}. Cleaning up...`)}`);
|
|
135
|
+
if (cleanupRunner) {
|
|
136
|
+
cleanupRunner();
|
|
137
|
+
}
|
|
138
|
+
// Give cleanup a moment, then exit
|
|
139
|
+
setTimeout(() => process.exit(0), 500);
|
|
140
|
+
};
|
|
141
|
+
process.on('SIGINT', () => handleSignal('SIGINT')); // Ctrl+C
|
|
142
|
+
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
143
|
+
process.on('SIGQUIT', () => handleSignal('SIGQUIT'));
|
|
144
|
+
}
|
|
145
|
+
exports.default = cli;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runServers = exports.runSequential = exports.runParallel = exports.run = void 0;
|
|
7
|
+
// src/index.ts - Updated version
|
|
8
|
+
const runner_1 = require("./runner");
|
|
9
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
10
|
+
/**
|
|
11
|
+
* Run one or more commands in parallel or sequentially
|
|
12
|
+
*/
|
|
13
|
+
async function run(commands, options) {
|
|
14
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
15
|
+
const cmdArray = Array.isArray(commands) ? commands : [commands];
|
|
16
|
+
const runOptions = {
|
|
17
|
+
parallel: (_a = options === null || options === void 0 ? void 0 : options.parallel) !== null && _a !== void 0 ? _a : false,
|
|
18
|
+
maxParallel: options === null || options === void 0 ? void 0 : options.maxParallel,
|
|
19
|
+
printOutput: (_b = options === null || options === void 0 ? void 0 : options.printOutput) !== null && _b !== void 0 ? _b : true,
|
|
20
|
+
color: (_c = options === null || options === void 0 ? void 0 : options.color) !== null && _c !== void 0 ? _c : true,
|
|
21
|
+
showTiming: (_d = options === null || options === void 0 ? void 0 : options.showTiming) !== null && _d !== void 0 ? _d : true,
|
|
22
|
+
prefix: (_e = options === null || options === void 0 ? void 0 : options.prefix) !== null && _e !== void 0 ? _e : true,
|
|
23
|
+
stopOnError: (_f = options === null || options === void 0 ? void 0 : options.stopOnError) !== null && _f !== void 0 ? _f : false,
|
|
24
|
+
minimalOutput: (_g = options === null || options === void 0 ? void 0 : options.minimalOutput) !== null && _g !== void 0 ? _g : false,
|
|
25
|
+
groupOutput: (_h = options === null || options === void 0 ? void 0 : options.groupOutput) !== null && _h !== void 0 ? _h : false,
|
|
26
|
+
isServerMode: (_j = options === null || options === void 0 ? void 0 : options.isServerMode) !== null && _j !== void 0 ? _j : false
|
|
27
|
+
};
|
|
28
|
+
const runner = new runner_1.Runner(runOptions);
|
|
29
|
+
if (options === null || options === void 0 ? void 0 : options.registerCleanup) {
|
|
30
|
+
options.registerCleanup(() => runner.cleanup());
|
|
31
|
+
}
|
|
32
|
+
const results = await runner.run(cmdArray);
|
|
33
|
+
if (runOptions.printOutput && cmdArray.length > 1) {
|
|
34
|
+
logger_1.default.printSummary(results);
|
|
35
|
+
}
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
exports.run = run;
|
|
39
|
+
/**
|
|
40
|
+
* Run multiple commands in parallel
|
|
41
|
+
*/
|
|
42
|
+
async function runParallel(commands, options) {
|
|
43
|
+
return run(commands, { ...options, parallel: true });
|
|
44
|
+
}
|
|
45
|
+
exports.runParallel = runParallel;
|
|
46
|
+
/**
|
|
47
|
+
* Run multiple commands sequentially
|
|
48
|
+
*/
|
|
49
|
+
async function runSequential(commands, options) {
|
|
50
|
+
return run(commands, { ...options, parallel: false });
|
|
51
|
+
}
|
|
52
|
+
exports.runSequential = runSequential;
|
|
53
|
+
/**
|
|
54
|
+
* Run multiple servers with optimized output
|
|
55
|
+
*/
|
|
56
|
+
async function runServers(commands, options) {
|
|
57
|
+
return run(commands, {
|
|
58
|
+
...options,
|
|
59
|
+
parallel: true,
|
|
60
|
+
isServerMode: true,
|
|
61
|
+
printOutput: true
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
exports.runServers = runServers;
|
|
65
|
+
exports.default = {
|
|
66
|
+
run,
|
|
67
|
+
runParallel,
|
|
68
|
+
runSequential,
|
|
69
|
+
runServers
|
|
70
|
+
};
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// src/logger.ts
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const figures_1 = __importDefault(require("figures"));
|
|
9
|
+
const string_width_1 = __importDefault(require("string-width"));
|
|
10
|
+
class Logger {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.prefixLength = 0;
|
|
13
|
+
this.outputBuffer = new Map();
|
|
14
|
+
this.commandColors = new Map();
|
|
15
|
+
this.startTimes = new Map();
|
|
16
|
+
this.spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
17
|
+
this.spinnerIndex = 0;
|
|
18
|
+
this.spinnerIntervals = new Map();
|
|
19
|
+
this.isSpinnerActive = false;
|
|
20
|
+
}
|
|
21
|
+
static getInstance() {
|
|
22
|
+
if (!Logger.instance) {
|
|
23
|
+
Logger.instance = new Logger();
|
|
24
|
+
}
|
|
25
|
+
return Logger.instance;
|
|
26
|
+
}
|
|
27
|
+
getSpinnerFrame() {
|
|
28
|
+
const frame = this.spinnerFrames[this.spinnerIndex];
|
|
29
|
+
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
30
|
+
return frame;
|
|
31
|
+
}
|
|
32
|
+
showBanner() {
|
|
33
|
+
console.log('\n' + chalk_1.default.bgHex('#0066FF').black(' Nextpress ') + '\n');
|
|
34
|
+
}
|
|
35
|
+
setCommands(commands) {
|
|
36
|
+
// Clear any existing spinner intervals
|
|
37
|
+
this.stopAllSpinners();
|
|
38
|
+
// Show NextPress banner
|
|
39
|
+
this.showBanner();
|
|
40
|
+
// Calculate prefix length for aligning output
|
|
41
|
+
this.prefixLength = Math.max(...commands.map(cmd => (0, string_width_1.default)(cmd))) + 3;
|
|
42
|
+
// Initialize buffers and colors for each command
|
|
43
|
+
commands.forEach(cmd => {
|
|
44
|
+
this.outputBuffer.set(cmd, []);
|
|
45
|
+
this.commandColors.set(cmd, this.generateColor(cmd));
|
|
46
|
+
});
|
|
47
|
+
// Log commands that will be executed
|
|
48
|
+
console.log(chalk_1.default.dim('» Commands to execute:'));
|
|
49
|
+
commands.forEach(cmd => {
|
|
50
|
+
const color = this.commandColors.get(cmd) || chalk_1.default.white;
|
|
51
|
+
console.log(chalk_1.default.dim(' ┌') + color(` ${cmd}`));
|
|
52
|
+
});
|
|
53
|
+
console.log(''); // Add a blank line after commands list
|
|
54
|
+
}
|
|
55
|
+
generateColor(command) {
|
|
56
|
+
// Generate distinct colors for commands based on the command string
|
|
57
|
+
const vibrantColors = [
|
|
58
|
+
'#00BFFF',
|
|
59
|
+
'#32CD32',
|
|
60
|
+
'#FF6347',
|
|
61
|
+
'#9370DB',
|
|
62
|
+
'#FF8C00',
|
|
63
|
+
'#20B2AA',
|
|
64
|
+
'#0066FF',
|
|
65
|
+
'#4169E1',
|
|
66
|
+
'#FFD700',
|
|
67
|
+
'#8A2BE2' // Blue Violet
|
|
68
|
+
];
|
|
69
|
+
let hash = 0;
|
|
70
|
+
for (let i = 0; i < command.length; i++) {
|
|
71
|
+
hash = (hash << 5) - hash + command.charCodeAt(i);
|
|
72
|
+
hash |= 0; // Convert to 32bit integer
|
|
73
|
+
}
|
|
74
|
+
const colorIndex = Math.abs(hash) % vibrantColors.length;
|
|
75
|
+
return chalk_1.default.hex(vibrantColors[colorIndex]);
|
|
76
|
+
}
|
|
77
|
+
formatPrefix(command) {
|
|
78
|
+
const color = this.commandColors.get(command) || chalk_1.default.white;
|
|
79
|
+
const prefix = `${command}:`.padEnd(this.prefixLength);
|
|
80
|
+
return color(prefix);
|
|
81
|
+
}
|
|
82
|
+
bufferOutput(output) {
|
|
83
|
+
const currentBuffer = this.outputBuffer.get(output.command) || [];
|
|
84
|
+
currentBuffer.push(output);
|
|
85
|
+
this.outputBuffer.set(output.command, currentBuffer);
|
|
86
|
+
}
|
|
87
|
+
printBuffer(command) {
|
|
88
|
+
const buffer = this.outputBuffer.get(command) || [];
|
|
89
|
+
const color = this.commandColors.get(command) || chalk_1.default.white;
|
|
90
|
+
// Stop spinner for this command if running
|
|
91
|
+
this.stopSpinner(command);
|
|
92
|
+
buffer.forEach(output => {
|
|
93
|
+
const prefix = this.formatPrefix(output.command);
|
|
94
|
+
const content = output.data.trim();
|
|
95
|
+
if (content) {
|
|
96
|
+
const lines = content.split('\n');
|
|
97
|
+
lines.forEach(line => {
|
|
98
|
+
if (line.trim()) {
|
|
99
|
+
const outputLine = `${prefix} ${line}`;
|
|
100
|
+
// Show stderr in appropriate colors
|
|
101
|
+
if (output.type === 'stderr') {
|
|
102
|
+
// Not all stderr is an error, check for warning or info patterns
|
|
103
|
+
if (line.toLowerCase().includes('warn') || line.toLowerCase().includes('warning')) {
|
|
104
|
+
console.log(`${prefix} ${chalk_1.default.yellow(line)}`);
|
|
105
|
+
}
|
|
106
|
+
else if (line.toLowerCase().includes('error')) {
|
|
107
|
+
console.log(`${prefix} ${chalk_1.default.red(line)}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log(`${prefix} ${line}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log(outputLine);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
// Clear buffer after printing
|
|
121
|
+
this.outputBuffer.set(command, []);
|
|
122
|
+
}
|
|
123
|
+
printLine(message, level = 'info') {
|
|
124
|
+
if (level === 'error') {
|
|
125
|
+
console.error(chalk_1.default.red(`${figures_1.default.cross} ${message}`));
|
|
126
|
+
}
|
|
127
|
+
else if (level === 'warn') {
|
|
128
|
+
console.warn(chalk_1.default.yellow(`${figures_1.default.warning} ${message}`));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log(chalk_1.default.blue(`${figures_1.default.info} ${message}`));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
printStart(command) {
|
|
135
|
+
// Record start time
|
|
136
|
+
this.startTimes.set(command, new Date());
|
|
137
|
+
const prefix = this.formatPrefix(command);
|
|
138
|
+
const color = this.commandColors.get(command) || chalk_1.default.white;
|
|
139
|
+
console.log(`${prefix} ${color('Starting...')}`);
|
|
140
|
+
// Start spinner for this command
|
|
141
|
+
this.startSpinner(command);
|
|
142
|
+
}
|
|
143
|
+
startSpinner(command) {
|
|
144
|
+
// Only create a spinner if one doesn't already exist for this command
|
|
145
|
+
if (this.spinnerIntervals.has(command)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
this.isSpinnerActive = true;
|
|
149
|
+
const color = this.commandColors.get(command) || chalk_1.default.white;
|
|
150
|
+
const prefix = this.formatPrefix(command);
|
|
151
|
+
const interval = setInterval(() => {
|
|
152
|
+
const frame = this.getSpinnerFrame();
|
|
153
|
+
process.stdout.write(`\r${prefix} ${color(frame)} ${chalk_1.default.dim('Running...')}`);
|
|
154
|
+
}, 80);
|
|
155
|
+
this.spinnerIntervals.set(command, interval);
|
|
156
|
+
}
|
|
157
|
+
stopSpinner(command) {
|
|
158
|
+
const interval = this.spinnerIntervals.get(command);
|
|
159
|
+
if (interval) {
|
|
160
|
+
clearInterval(interval);
|
|
161
|
+
this.spinnerIntervals.delete(command);
|
|
162
|
+
// Clear the spinner line
|
|
163
|
+
if (this.isSpinnerActive) {
|
|
164
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
stopAllSpinners() {
|
|
169
|
+
this.spinnerIntervals.forEach((interval, command) => {
|
|
170
|
+
clearInterval(interval);
|
|
171
|
+
});
|
|
172
|
+
this.spinnerIntervals.clear();
|
|
173
|
+
this.isSpinnerActive = false;
|
|
174
|
+
// Clear the spinner line if any spinner was active
|
|
175
|
+
if (this.isSpinnerActive) {
|
|
176
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
printSuccess(result) {
|
|
180
|
+
const { command, duration } = result;
|
|
181
|
+
this.stopSpinner(command);
|
|
182
|
+
const prefix = this.formatPrefix(command);
|
|
183
|
+
const color = this.commandColors.get(command) || chalk_1.default.white;
|
|
184
|
+
const durationStr = duration
|
|
185
|
+
? ` ${chalk_1.default.dim(`(${(duration / 1000).toFixed(2)}s)`)}`
|
|
186
|
+
: '';
|
|
187
|
+
console.log(`${prefix} ${chalk_1.default.green(figures_1.default.tick)} ${chalk_1.default.green('Completed')}${durationStr}`);
|
|
188
|
+
}
|
|
189
|
+
printError(result) {
|
|
190
|
+
const { command, error, code, duration } = result;
|
|
191
|
+
this.stopSpinner(command);
|
|
192
|
+
const prefix = this.formatPrefix(command);
|
|
193
|
+
const durationStr = duration ? ` ${chalk_1.default.dim(`(${(duration / 1000).toFixed(2)}s)`)}` : '';
|
|
194
|
+
const errorCode = code !== null ? ` ${chalk_1.default.red(`[code: ${code}]`)}` : '';
|
|
195
|
+
console.error(`${prefix} ${chalk_1.default.red(figures_1.default.cross)} ${chalk_1.default.red('Failed')}${errorCode}${durationStr}`);
|
|
196
|
+
if (error) {
|
|
197
|
+
console.error(`${prefix} ${chalk_1.default.red(error.message)}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
printSummary(results) {
|
|
201
|
+
// Stop any remaining spinners
|
|
202
|
+
this.stopAllSpinners();
|
|
203
|
+
const successful = results.filter(r => r.success).length;
|
|
204
|
+
const failed = results.length - successful;
|
|
205
|
+
const totalDuration = results.reduce((sum, result) => sum + (result.duration || 0), 0);
|
|
206
|
+
const totalSeconds = (totalDuration / 1000).toFixed(2);
|
|
207
|
+
console.log('\n' + chalk_1.default.bgHex('#0066FF').black(' Execution Summary ') + '\n');
|
|
208
|
+
console.log(`${chalk_1.default.green(`${figures_1.default.tick} ${successful} succeeded`)}, ${chalk_1.default.red(`${figures_1.default.cross} ${failed} failed`)}`);
|
|
209
|
+
console.log(`${chalk_1.default.blue(figures_1.default.info)} ${chalk_1.default.dim(`Total execution time: ${totalSeconds}s`)}`);
|
|
210
|
+
if (successful > 0) {
|
|
211
|
+
console.log('\n' + chalk_1.default.green.bold('Successful commands:'));
|
|
212
|
+
results
|
|
213
|
+
.filter(r => r.success)
|
|
214
|
+
.forEach(result => {
|
|
215
|
+
const color = this.commandColors.get(result.command) || chalk_1.default.white;
|
|
216
|
+
const duration = result.duration
|
|
217
|
+
? chalk_1.default.dim(` (${(result.duration / 1000).toFixed(2)}s)`)
|
|
218
|
+
: '';
|
|
219
|
+
console.log(` ${chalk_1.default.green(figures_1.default.tick)} ${color(result.command)}${duration}`);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (failed > 0) {
|
|
223
|
+
console.log('\n' + chalk_1.default.red.bold('Failed commands:'));
|
|
224
|
+
results
|
|
225
|
+
.filter(r => !r.success)
|
|
226
|
+
.forEach(result => {
|
|
227
|
+
const color = this.commandColors.get(result.command) || chalk_1.default.white;
|
|
228
|
+
const duration = result.duration
|
|
229
|
+
? chalk_1.default.dim(` (${(result.duration / 1000).toFixed(2)}s)`)
|
|
230
|
+
: '';
|
|
231
|
+
const code = result.code !== null ? chalk_1.default.red(` [code: ${result.code}]`) : '';
|
|
232
|
+
console.log(` ${chalk_1.default.red(figures_1.default.cross)} ${color(result.command)}${code}${duration}`);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
exports.default = Logger.getInstance();
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.Runner = void 0;
|
|
30
|
+
// src/runner.ts - Updated version
|
|
31
|
+
const child_process_1 = require("child_process");
|
|
32
|
+
const fsPromises = __importStar(require("fs/promises"));
|
|
33
|
+
const path = __importStar(require("path"));
|
|
34
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
35
|
+
const logger_1 = __importDefault(require("./logger"));
|
|
36
|
+
const p_map_1 = __importDefault(require("p-map"));
|
|
37
|
+
const npm_run_path_1 = __importDefault(require("npm-run-path"));
|
|
38
|
+
class Runner {
|
|
39
|
+
constructor(options) {
|
|
40
|
+
this.activeProcesses = new Map();
|
|
41
|
+
this.serverInfo = new Map();
|
|
42
|
+
this.portRegex = /listening on (?:port |http:\/\/localhost:|https:\/\/localhost:)(\d+)/i;
|
|
43
|
+
this.urlRegex = /(https?:\/\/localhost:[0-9]+(?:\/[^\s]*)?)/i;
|
|
44
|
+
this.options = options;
|
|
45
|
+
this.activeProcesses = new Map();
|
|
46
|
+
}
|
|
47
|
+
async resolveScriptAndCwd(scriptNameOrCommand, baseDir) {
|
|
48
|
+
try {
|
|
49
|
+
const packageJsonPath = path.join(baseDir, 'package.json');
|
|
50
|
+
const packageJsonContent = await fsPromises.readFile(packageJsonPath, 'utf-8');
|
|
51
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
52
|
+
if (packageJson.scripts && packageJson.scripts[scriptNameOrCommand]) {
|
|
53
|
+
const scriptValue = packageJson.scripts[scriptNameOrCommand];
|
|
54
|
+
const cdMatch = scriptValue.match(/^cd\s+([^&]+)\s+&&\s+(.*)$/);
|
|
55
|
+
if (cdMatch) {
|
|
56
|
+
const dir = cdMatch[1];
|
|
57
|
+
const commandToExecute = cdMatch[2];
|
|
58
|
+
const targetCwd = path.resolve(baseDir, dir);
|
|
59
|
+
return { executableCommand: commandToExecute, executionCwd: targetCwd };
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// It's a script from package.json, but no 'cd ... && ...' pattern
|
|
63
|
+
return { executableCommand: scriptValue, executionCwd: baseDir };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// Errors like package.json not found, or script not in package.json
|
|
69
|
+
// Will treat as direct command
|
|
70
|
+
}
|
|
71
|
+
return { executableCommand: scriptNameOrCommand, executionCwd: undefined };
|
|
72
|
+
}
|
|
73
|
+
detectServerInfo(command, data) {
|
|
74
|
+
if (!this.options.isServerMode)
|
|
75
|
+
return;
|
|
76
|
+
// Get or create server info
|
|
77
|
+
let serverInfo = this.serverInfo.get(command);
|
|
78
|
+
if (!serverInfo) {
|
|
79
|
+
serverInfo = {
|
|
80
|
+
name: command,
|
|
81
|
+
status: 'starting'
|
|
82
|
+
};
|
|
83
|
+
this.serverInfo.set(command, serverInfo);
|
|
84
|
+
}
|
|
85
|
+
// Try to detect port from output
|
|
86
|
+
const portMatch = data.match(this.portRegex);
|
|
87
|
+
if (portMatch && portMatch[1]) {
|
|
88
|
+
serverInfo.port = parseInt(portMatch[1], 10);
|
|
89
|
+
serverInfo.status = 'running';
|
|
90
|
+
// Only log if we just discovered the port
|
|
91
|
+
if (!serverInfo.url) {
|
|
92
|
+
logger_1.default.printLine(`Server ${command} running on port ${serverInfo.port}`, 'info');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Try to detect full URL from output
|
|
96
|
+
const urlMatch = data.match(this.urlRegex);
|
|
97
|
+
if (urlMatch && urlMatch[1]) {
|
|
98
|
+
serverInfo.url = urlMatch[1];
|
|
99
|
+
serverInfo.status = 'running';
|
|
100
|
+
// Log the full URL once we detect it
|
|
101
|
+
logger_1.default.printLine(`Server ${command} available at ${chalk_1.default.cyan(serverInfo.url)}`, 'info');
|
|
102
|
+
}
|
|
103
|
+
// Update server info
|
|
104
|
+
this.serverInfo.set(command, serverInfo);
|
|
105
|
+
}
|
|
106
|
+
async runCommand(originalCommand) {
|
|
107
|
+
const { executableCommand: command, executionCwd: cwd } = await this.resolveScriptAndCwd(originalCommand, process.cwd());
|
|
108
|
+
const startTime = new Date();
|
|
109
|
+
const result = {
|
|
110
|
+
command: originalCommand,
|
|
111
|
+
success: false,
|
|
112
|
+
code: null,
|
|
113
|
+
startTime,
|
|
114
|
+
endTime: null,
|
|
115
|
+
output: []
|
|
116
|
+
};
|
|
117
|
+
if (this.options.printOutput) {
|
|
118
|
+
logger_1.default.printStart(originalCommand);
|
|
119
|
+
}
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const [cmd, ...args] = command.split(' ');
|
|
122
|
+
const env = {
|
|
123
|
+
...process.env,
|
|
124
|
+
...npm_run_path_1.default.env(),
|
|
125
|
+
FORCE_COLOR: this.options.color ? '1' : '0'
|
|
126
|
+
};
|
|
127
|
+
const proc = (0, child_process_1.spawn)(cmd, args, {
|
|
128
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
129
|
+
shell: true,
|
|
130
|
+
env,
|
|
131
|
+
detached: true,
|
|
132
|
+
cwd
|
|
133
|
+
});
|
|
134
|
+
this.activeProcesses.set(originalCommand, proc);
|
|
135
|
+
if (this.options.isServerMode) {
|
|
136
|
+
this.serverInfo.set(originalCommand, {
|
|
137
|
+
name: originalCommand,
|
|
138
|
+
status: 'starting',
|
|
139
|
+
pid: proc.pid,
|
|
140
|
+
startTime: new Date()
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Capture and display output
|
|
144
|
+
if (this.options.printOutput) {
|
|
145
|
+
proc.stdout.on('data', (data) => {
|
|
146
|
+
const output = {
|
|
147
|
+
command: originalCommand,
|
|
148
|
+
type: 'stdout',
|
|
149
|
+
data: data.toString(),
|
|
150
|
+
timestamp: new Date()
|
|
151
|
+
};
|
|
152
|
+
if (this.options.isServerMode) {
|
|
153
|
+
this.detectServerInfo(originalCommand, data.toString());
|
|
154
|
+
}
|
|
155
|
+
// Store output for logging
|
|
156
|
+
if (result.output)
|
|
157
|
+
result.output.push(output);
|
|
158
|
+
logger_1.default.bufferOutput(output);
|
|
159
|
+
// Print immediately unless we're in group mode
|
|
160
|
+
if (!this.options.groupOutput) {
|
|
161
|
+
logger_1.default.printBuffer(originalCommand);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
proc.stderr.on('data', (data) => {
|
|
165
|
+
const output = {
|
|
166
|
+
command: originalCommand,
|
|
167
|
+
type: 'stderr',
|
|
168
|
+
data: data.toString(),
|
|
169
|
+
timestamp: new Date()
|
|
170
|
+
};
|
|
171
|
+
// Store output for logging
|
|
172
|
+
if (result.output)
|
|
173
|
+
result.output.push(output);
|
|
174
|
+
logger_1.default.bufferOutput(output);
|
|
175
|
+
// Print immediately unless we're in group mode
|
|
176
|
+
if (!this.options.groupOutput) {
|
|
177
|
+
logger_1.default.printBuffer(originalCommand);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
proc.on('close', (code) => {
|
|
182
|
+
const endTime = new Date();
|
|
183
|
+
const duration = endTime.getTime() - startTime.getTime();
|
|
184
|
+
result.endTime = endTime;
|
|
185
|
+
result.duration = duration;
|
|
186
|
+
result.code = code;
|
|
187
|
+
result.success = code === 0;
|
|
188
|
+
this.activeProcesses.delete(originalCommand);
|
|
189
|
+
if (this.options.isServerMode) {
|
|
190
|
+
const serverInfo = this.serverInfo.get(originalCommand);
|
|
191
|
+
if (serverInfo) {
|
|
192
|
+
serverInfo.status = code === 0 ? 'stopped' : 'error';
|
|
193
|
+
this.serverInfo.set(originalCommand, serverInfo);
|
|
194
|
+
}
|
|
195
|
+
// If this is server mode and a server failed, print prominent error
|
|
196
|
+
if (code !== 0) {
|
|
197
|
+
logger_1.default.printLine(`Server ${originalCommand} crashed with code ${code}`, 'error');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Print grouped output at the end if enabled
|
|
201
|
+
if (this.options.groupOutput && result.output && result.output.length > 0) {
|
|
202
|
+
logger_1.default.printBuffer(originalCommand);
|
|
203
|
+
}
|
|
204
|
+
if (this.options.printOutput) {
|
|
205
|
+
if (result.success) {
|
|
206
|
+
logger_1.default.printSuccess(result);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
logger_1.default.printError(result);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
resolve(result);
|
|
213
|
+
});
|
|
214
|
+
proc.on('error', (error) => {
|
|
215
|
+
const endTime = new Date();
|
|
216
|
+
const duration = endTime.getTime() - startTime.getTime();
|
|
217
|
+
result.endTime = endTime;
|
|
218
|
+
result.duration = duration;
|
|
219
|
+
result.error = error;
|
|
220
|
+
result.success = false;
|
|
221
|
+
this.activeProcesses.delete(originalCommand);
|
|
222
|
+
if (this.options.isServerMode) {
|
|
223
|
+
const serverInfo = this.serverInfo.get(originalCommand);
|
|
224
|
+
if (serverInfo) {
|
|
225
|
+
serverInfo.status = 'error';
|
|
226
|
+
this.serverInfo.set(originalCommand, serverInfo);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (this.options.printOutput) {
|
|
230
|
+
logger_1.default.printError(result);
|
|
231
|
+
}
|
|
232
|
+
resolve(result);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async runSequential(commands) {
|
|
237
|
+
const results = [];
|
|
238
|
+
for (const cmd of commands) {
|
|
239
|
+
const result = await this.runCommand(cmd);
|
|
240
|
+
results.push(result);
|
|
241
|
+
// Stop on error if enabled
|
|
242
|
+
if (!result.success && this.options.stopOnError) {
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return results;
|
|
247
|
+
}
|
|
248
|
+
async runParallel(commands) {
|
|
249
|
+
const concurrency = this.options.maxParallel || commands.length;
|
|
250
|
+
const mapper = async (cmd) => {
|
|
251
|
+
return this.runCommand(cmd);
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
return await (0, p_map_1.default)(commands, mapper, {
|
|
255
|
+
concurrency,
|
|
256
|
+
stopOnError: this.options.stopOnError
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
// If pMap stops due to stopOnError
|
|
261
|
+
if (this.options.isServerMode) {
|
|
262
|
+
logger_1.default.printLine('One or more servers failed to start. Stopping all servers.', 'error');
|
|
263
|
+
}
|
|
264
|
+
return [];
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async run(commands) {
|
|
268
|
+
if (commands.length === 0) {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
// Set up logger with commands
|
|
272
|
+
logger_1.default.setCommands(commands);
|
|
273
|
+
// Run in parallel or sequential mode
|
|
274
|
+
if (this.options.parallel) {
|
|
275
|
+
if (this.options.isServerMode) {
|
|
276
|
+
logger_1.default.printLine('Starting servers in parallel mode', 'info');
|
|
277
|
+
}
|
|
278
|
+
return this.runParallel(commands);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
if (this.options.isServerMode) {
|
|
282
|
+
logger_1.default.printLine('Starting servers in sequential mode', 'info');
|
|
283
|
+
}
|
|
284
|
+
return this.runSequential(commands);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
cleanup(signal = 'SIGTERM') {
|
|
288
|
+
logger_1.default.printLine('Cleaning up child processes...', 'warn');
|
|
289
|
+
this.activeProcesses.forEach((proc, command) => {
|
|
290
|
+
if (proc.pid && !proc.killed) {
|
|
291
|
+
try {
|
|
292
|
+
// Kill process group
|
|
293
|
+
process.kill(-proc.pid, signal);
|
|
294
|
+
logger_1.default.printLine(`Sent ${signal} to process group ${proc.pid} (${command})`, 'info');
|
|
295
|
+
}
|
|
296
|
+
catch (e) {
|
|
297
|
+
// Fallback if killing group failed
|
|
298
|
+
try {
|
|
299
|
+
proc.kill(signal);
|
|
300
|
+
logger_1.default.printLine(`Sent ${signal} to process ${proc.pid} (${command})`, 'info');
|
|
301
|
+
}
|
|
302
|
+
catch (errInner) {
|
|
303
|
+
logger_1.default.printLine(`Failed to kill process ${proc.pid} (${command}): ${errInner.message}`, 'error');
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
this.activeProcesses.clear();
|
|
309
|
+
// Print server status summary if in server mode
|
|
310
|
+
if (this.options.isServerMode && this.serverInfo.size > 0) {
|
|
311
|
+
logger_1.default.printLine('Server shutdown summary:', 'info');
|
|
312
|
+
this.serverInfo.forEach((info, command) => {
|
|
313
|
+
const statusColor = info.status === 'running' ? chalk_1.default.green :
|
|
314
|
+
info.status === 'error' ? chalk_1.default.red : chalk_1.default.yellow;
|
|
315
|
+
logger_1.default.printLine(` ${command}: ${statusColor(info.status)}`, 'info');
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
exports.Runner = Runner;
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "neex",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Modern Build System for Polyrepo-in-Monorepo Architecture",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"neex": "./dist/bin/neex.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/bin/neex.js",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"test": "jest",
|
|
15
|
+
"test:dev": "node ./dist/bin/neex.js runx \"echo Starting frontend\" \"echo Starting backend\"",
|
|
16
|
+
"test:parallel": "node ./dist/src/cli.js parallel \"echo Building frontend\" \"echo Building backend\"",
|
|
17
|
+
"test:sequence": "node ./dist/src/cli.js run \"echo Step 1\" \"echo Step 2\" \"echo Step 3\""
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"npm",
|
|
21
|
+
"run",
|
|
22
|
+
"script",
|
|
23
|
+
"parallel",
|
|
24
|
+
"sequential",
|
|
25
|
+
"command",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "foshati",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"chalk": "^4.1.2",
|
|
32
|
+
"commander": "^9.4.0",
|
|
33
|
+
"figlet": "^1.8.1",
|
|
34
|
+
"figures": "^3.2.0",
|
|
35
|
+
"gradient-string": "^3.0.0",
|
|
36
|
+
"npm-run-path": "^4.0.1",
|
|
37
|
+
"p-map": "^4.0.0",
|
|
38
|
+
"string-width": "^4.2.3"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/figlet": "^1.7.0",
|
|
42
|
+
"@types/jest": "^29.2.3",
|
|
43
|
+
"@types/node": "^18.11.9",
|
|
44
|
+
"jest": "^29.3.1",
|
|
45
|
+
"ts-jest": "^29.0.3",
|
|
46
|
+
"typescript": "^4.9.3"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18.0.0"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/Nextpress-cc"
|
|
54
|
+
}
|
|
55
|
+
}
|