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 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
+ [![NPM version](https://img.shields.io/npm/v/neex.svg?style=for-the-badge&labelColor=000000&color=0066FF&borderRadius=8)](https://www.npmjs.com/package/neex)
14
+ [![Download Count](https://img.shields.io/npm/dt/neex.svg?style=for-the-badge&labelColor=000000&color=0066FF&borderRadius=8)](https://www.npmjs.com/package/neex)
15
+ [![MIT License](https://img.shields.io/badge/license-MIT-0066FF.svg?style=for-the-badge&labelColor=000000&borderRadius=8)](https://github.com/Nextpress-cc/neex/blob/main/LICENSE)
16
+ [![GitHub](https://img.shields.io/badge/GitHub-Nextpress-0066FF.svg?style=for-the-badge&logo=github&labelColor=000000&logoWidth=20&borderRadius=8)](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
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ require('../src/cli').default();
@@ -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;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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
+ }