nginx-pretty 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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # ๐Ÿ›ก๏ธ nginx-pretty
2
+
3
+ A **standalone CLI utility** to safely format and apply NGINX configuration files using the embedded logic of [`nginxbeautifier`](https://www.npmjs.com/package/nginxbeautifier). Designed to work in secure environments **without modifying original files unless explicitly approved**, and **without needing global Node or NPM dependencies**.
4
+
5
+ ---
6
+
7
+ ## ๐Ÿš€ Features
8
+
9
+ - ๐Ÿ“„ Reads and formats: `/etc/nginx/sites-available/default` (or custom path via `--file`)
10
+ - ๐Ÿ›ก๏ธ Never modifies the original unless confirmed
11
+ - ๐Ÿ” Shows unified diff before applying
12
+ - ๐Ÿงช Runs `nginx -t` to validate before reload
13
+ - ๐Ÿงฐ Works with `sudo`, in disconnected or hardened servers
14
+ - ๐Ÿงฑ CLI structure is standalone, can be bundled with [`pkg`](https://github.com/vercel/pkg)
15
+
16
+ ---
17
+
18
+ ## ๐Ÿงฑ Directory Structure
19
+
20
+ ```
21
+ nginx-pretty/
22
+ โ”œโ”€โ”€ cli.js # Main script with embedded formatting logic
23
+ โ”œโ”€โ”€ beautifier/ # Vendored logic from nginxbeautifier
24
+ โ”‚ โ””โ”€โ”€ index.js # Main formatter engine
25
+ โ”œโ”€โ”€ bin/
26
+ โ”‚ โ””โ”€โ”€ nginx-pretty # Optional wrapper bash script
27
+ โ”œโ”€โ”€ README.md
28
+ โ”œโ”€โ”€ package.json
29
+ ```
30
+
31
+ ---
32
+
33
+ ## โš™๏ธ Usage
34
+
35
+ ### Global install (recommended)
36
+
37
+ Install globally with npm, then run with `sudo` when modifying system NGINX config:
38
+
39
+ ```bash
40
+ npm install -g nginx-pretty
41
+ sudo nginx-pretty
42
+ ```
43
+
44
+ ### As Bash Script (with embedded Node)
45
+
46
+ ```bash
47
+ sudo ./bin/nginx-pretty
48
+ ```
49
+
50
+ ### Via npx
51
+
52
+ ```bash
53
+ npx nginx-pretty
54
+ ```
55
+
56
+ ### If Bundled as a Binary (using pkg)
57
+
58
+ ```bash
59
+ sudo ./nginx-pretty
60
+ ```
61
+
62
+ ### Options
63
+
64
+ | Option | Description |
65
+ |--------|-------------|
66
+ | `--file`, `-f <path>` | Config file path (default: `/etc/nginx/sites-available/default`) |
67
+ | `--check-only` | Show diff only, do not prompt or apply |
68
+ | `--dry-run` | Same as `--check-only` |
69
+ | `--no-prompt` | Apply without prompting (for CI; use with caution) |
70
+ | `--help`, `-h` | Show help |
71
+
72
+ ### Help
73
+
74
+ To print usage and options to the terminal:
75
+
76
+ ```bash
77
+ nginx-pretty --help
78
+ # or
79
+ nginx-pretty -h
80
+ ```
81
+
82
+ Output:
83
+
84
+ ```
85
+ nginx-pretty - Safely format and apply NGINX config files
86
+
87
+ Usage: nginx-pretty [OPTIONS]
88
+
89
+ Options:
90
+ --file, -f <path> Config file path (default: /etc/nginx/sites-available/default)
91
+ --check-only Show diff only, do not prompt or apply
92
+ --dry-run Same as --check-only
93
+ --no-prompt Apply without prompting (for CI; use with caution)
94
+ --help, -h Show this help
95
+ ```
96
+
97
+ ---
98
+
99
+ ## ๐Ÿ“œ Example Flow
100
+
101
+ ```bash
102
+ ๐Ÿงช Running nginx-pretty...
103
+ ๐Ÿ“„ Copying original file to: /tmp/tmp_nginx_formatted.conf
104
+ ๐Ÿ†• Ensuring temp file is writable...
105
+ ๐ŸŽจ Formatting the copied config...
106
+
107
+ ๐Ÿ” Showing unified diff (ORIGINAL vs FORMATTED):
108
+ ...
109
+ diff output...
110
+
111
+ โš ๏ธ Do you want to apply the formatted config? (yes/no): yes
112
+ ๐Ÿš€ Config test passed. You can now reload NGINX
113
+ Run: sudo service nginx reload to apply the changes
114
+ ```
115
+
116
+ ---
117
+
118
+ ## ๐Ÿ› ๏ธ Build Instructions (Optional)
119
+
120
+ To package this into a true standalone binary:
121
+
122
+ ```bash
123
+ npm install
124
+ npm install -g pkg
125
+ pkg cli.js --output nginx-pretty
126
+ ```
127
+
128
+ Or use the npm script:
129
+
130
+ ```bash
131
+ npm run build:pkg
132
+ ```
133
+
134
+ > โœ… This produces a native binary: no Node required on the target machine.
135
+
136
+ ---
137
+
138
+ ## ๐Ÿงช How to test
139
+
140
+ From the project directory (no global install needed):
141
+
142
+ **1. Help**
143
+ ```bash
144
+ node cli.js --help
145
+ ```
146
+
147
+ **2. Dry-run (no nginx required)**
148
+ Use a temporary config so nothing is modified. You should see a unified diff and โ€œDry run / check-only: not applying.โ€
149
+ ```bash
150
+ echo 'server { listen 80; root /var/www; }' > /tmp/test-nginx.conf
151
+ node cli.js --file /tmp/test-nginx.conf --dry-run
152
+ ```
153
+
154
+ **3. Optional: real NGINX config**
155
+ If you have NGINX and a config (e.g. `/etc/nginx/sites-available/default`), run with `--dry-run` first to only view the diff:
156
+ ```bash
157
+ sudo node cli.js --file /etc/nginx/sites-available/default --dry-run
158
+ ```
159
+ Without `--dry-run`, the CLI will prompt to apply; answer `yes` only if you want to overwrite the file.
160
+
161
+ ---
162
+
163
+ ## ๐Ÿ” Security Notes
164
+
165
+ - This CLI never overwrites original config unless approved.
166
+ - Temporary files are cleaned and written to `/tmp`.
167
+ - Requires `sudo` for NGINX reload steps.
168
+ - Uses `spawn` with array arguments only; no user input is passed to shell.
169
+
170
+ ---
171
+
172
+ ## ๐Ÿ“ฆ Dependencies
173
+
174
+ Vendored internally:
175
+ - [`nginxbeautifier`](https://github.com/vasilevich/nginxbeautifier) (Apache 2.0)
176
+
177
+ Runtime:
178
+ - Node.js 14+ (or embedded via `pkg`)
179
+ - `diff` on PATH (for unified diff; standard on Linux)
180
+
181
+ ---
182
+
183
+ ## ๐Ÿ“œ License
184
+
185
+ MIT ยฉ You
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Vendored from nginxbeautifier (https://github.com/vasilevich/nginxbeautifier)
3
+ * Original: Ported by Yosef from https://github.com/1connect/nginx-config-formatter (nginxfmt.py)
4
+ * License: Apache-2.0
5
+ *
6
+ * Adapted for nginx-pretty: 4-space indent, format() export, polyfills removed (Node 14+).
7
+ */
8
+
9
+ /**
10
+ * Grabs text in between two separators
11
+ */
12
+ function extractTextBySeperator(input, seperator1, seperator2) {
13
+ if (seperator2 == undefined) seperator2 = seperator1;
14
+ const catchRegex = new RegExp(seperator1 + '(.*?)' + seperator2);
15
+ if (new RegExp(seperator1).test(input) && new RegExp(seperator2).test(input)) {
16
+ return input.match(catchRegex)[1];
17
+ }
18
+ return '';
19
+ }
20
+
21
+ function extractAllPossibleText(input, seperator1, seperator2) {
22
+ if (seperator2 == undefined) seperator2 = seperator1;
23
+ const extracted = {};
24
+ let textInBetween;
25
+ let cnt = 0;
26
+ const seperator1CharCode = seperator1.length > 0 ? seperator1.charCodeAt(0) : '';
27
+ const seperator2CharCode = seperator2.length > 0 ? seperator2.charCodeAt(0) : '';
28
+ while ((textInBetween = extractTextBySeperator(input, seperator1, seperator2)) !== '') {
29
+ const placeHolder =
30
+ '#$#%#$#placeholder' + cnt + '' + seperator1CharCode + '' + seperator2CharCode + '#$#%#$#';
31
+ extracted[placeHolder] = seperator1 + textInBetween + seperator2;
32
+ input = input.replace(extracted[placeHolder], placeHolder);
33
+ cnt++;
34
+ }
35
+ return {
36
+ filteredInput: input,
37
+ extracted: extracted,
38
+ getRestored: function () {
39
+ let textToFix = this.filteredInput;
40
+ for (const key in extracted) {
41
+ textToFix = textToFix.replace(key, extracted[key]);
42
+ }
43
+ return textToFix;
44
+ },
45
+ };
46
+ }
47
+
48
+ function strip_line(single_line) {
49
+ const trimmed = single_line.trim();
50
+ const removedDoubleQuatations = extractAllPossibleText(trimmed, '"', '"');
51
+ if (!removedDoubleQuatations.filteredInput.includes('sub_filter')) {
52
+ removedDoubleQuatations.filteredInput = removedDoubleQuatations.filteredInput.replace(
53
+ /\s\s+/g,
54
+ ' '
55
+ );
56
+ }
57
+ return removedDoubleQuatations.getRestored();
58
+ }
59
+
60
+ function clean_lines(configContents) {
61
+ const splittedByLines = configContents.split(/\r\n|\r|\n/g);
62
+
63
+ for (let index = 0, newline = 0; index < splittedByLines.length; index++) {
64
+ splittedByLines[index] = splittedByLines[index].trim();
65
+ if (
66
+ !splittedByLines[index].startsWith('#') &&
67
+ splittedByLines[index] !== ''
68
+ ) {
69
+ newline = 0;
70
+ let line = (splittedByLines[index] = strip_line(splittedByLines[index]));
71
+ if (
72
+ line !== '}' &&
73
+ line !== '{' &&
74
+ !(
75
+ line.includes("('{") ||
76
+ line.includes("}')") ||
77
+ line.includes("'{'") ||
78
+ line.includes("'}'")
79
+ )
80
+ ) {
81
+ const startOfComment = line.indexOf('#');
82
+ const code =
83
+ startOfComment >= 0 ? line.slice(0, startOfComment) : line;
84
+ const removedDoubleQuatations = extractAllPossibleText(code, '"', '"');
85
+ let codeProcessed = removedDoubleQuatations.filteredInput;
86
+
87
+ const startOfParanthesis = codeProcessed.indexOf('}');
88
+ if (startOfParanthesis >= 0) {
89
+ if (startOfParanthesis > 0) {
90
+ splittedByLines[index] = strip_line(
91
+ codeProcessed.slice(0, startOfParanthesis - 1)
92
+ );
93
+ }
94
+ splittedByLines.splice(index + 1, 0, '}');
95
+ const l2 = strip_line(codeProcessed.slice(startOfParanthesis + 1));
96
+ if (l2 !== '') splittedByLines.splice(index + 2, 0, l2);
97
+ codeProcessed = splittedByLines[index];
98
+ }
99
+ const endOfParanthesis = codeProcessed.indexOf('{');
100
+ if (endOfParanthesis >= 0) {
101
+ splittedByLines[index] = strip_line(
102
+ codeProcessed.slice(0, endOfParanthesis)
103
+ );
104
+ splittedByLines.splice(index + 1, 0, '{');
105
+ const l2 = strip_line(codeProcessed.slice(endOfParanthesis + 1));
106
+ if (l2 !== '') splittedByLines.splice(index + 2, 0, l2);
107
+ }
108
+ removedDoubleQuatations.filteredInput = splittedByLines[index];
109
+ line = removedDoubleQuatations.getRestored();
110
+ splittedByLines[index] = line;
111
+ }
112
+ } else if (splittedByLines[index] === '') {
113
+ if (newline++ >= 2) {
114
+ splittedByLines.splice(index, 1);
115
+ index--;
116
+ }
117
+ }
118
+ }
119
+ return splittedByLines;
120
+ }
121
+
122
+ function join_opening_bracket(lines) {
123
+ for (let i = 0; i < lines.length; i++) {
124
+ const line = lines[i];
125
+ if (line === '{' && i >= 1) {
126
+ lines[i] = lines[i - 1] + ' {';
127
+ lines.splice(i - 1, 1);
128
+ i--;
129
+ }
130
+ }
131
+ return lines;
132
+ }
133
+
134
+ const INDENTATION = ' '; // 4 spaces
135
+
136
+ function perform_indentation(lines) {
137
+ const indented_lines = [];
138
+ let current_indent = 0;
139
+ for (let index1 = 0; index1 < lines.length; index1++) {
140
+ const line = lines[index1];
141
+ if (
142
+ !line.startsWith('#') &&
143
+ /.*?\}(\s*#.*)?$/.test(line) &&
144
+ current_indent > 0
145
+ ) {
146
+ current_indent -= 1;
147
+ }
148
+ if (line !== '') {
149
+ indented_lines.push(INDENTATION.repeat(current_indent) + line);
150
+ } else {
151
+ indented_lines.push('');
152
+ }
153
+ if (!line.startsWith('#') && /.*?\{(\s*#.*)?$/.test(line)) {
154
+ current_indent += 1;
155
+ }
156
+ }
157
+ return indented_lines;
158
+ }
159
+
160
+ function perform_alignment(lines) {
161
+ const all_lines = [];
162
+ const attribute_lines = [];
163
+ let minAlignColumn = 0;
164
+
165
+ for (let index1 = 0; index1 < lines.length; index1++) {
166
+ const line = lines[index1];
167
+ if (
168
+ line !== '' &&
169
+ !/.*?\{(\s*#.*)?$/.test(line) &&
170
+ !line.startsWith('#') &&
171
+ !/.*?\}(\s*#.*)?$/.test(line) &&
172
+ !line.trim().startsWith('upstream') &&
173
+ !line.trim().includes('location')
174
+ ) {
175
+ const splitLine = line.match(/\S+/g);
176
+ if (splitLine && splitLine.length > 1) {
177
+ attribute_lines.push(line);
178
+ const columnAtAttrValue = line.indexOf(splitLine[1]) + 1;
179
+ if (minAlignColumn < columnAtAttrValue) {
180
+ minAlignColumn = columnAtAttrValue;
181
+ }
182
+ }
183
+ }
184
+ all_lines.push(line);
185
+ }
186
+
187
+ for (let index1 = 0; index1 < all_lines.length; index1++) {
188
+ let line = all_lines[index1];
189
+ if (attribute_lines.includes(line)) {
190
+ const split = line.match(/\S+/g);
191
+ const indentMatch = line.match(/\s+/g);
192
+ const indent = indentMatch ? indentMatch[0] : '';
193
+ line =
194
+ indent +
195
+ split[0] +
196
+ ' '.repeat(
197
+ minAlignColumn - split[0].length - indent.length
198
+ ) +
199
+ split.slice(1, split.length).join(' ');
200
+ all_lines[index1] = line;
201
+ }
202
+ }
203
+ return all_lines;
204
+ }
205
+
206
+ /**
207
+ * Format NGINX config contents into a readable format.
208
+ * Pipeline: clean_lines -> join_opening_bracket -> perform_indentation -> perform_alignment
209
+ *
210
+ * @param {string} contents - Raw NGINX config file contents
211
+ * @returns {string} Formatted config
212
+ */
213
+ function format(contents) {
214
+ let lines = clean_lines(contents);
215
+ lines = join_opening_bracket(lines);
216
+ lines = perform_indentation(lines);
217
+ lines = perform_alignment(lines);
218
+ return lines.join('\n');
219
+ }
220
+
221
+ module.exports = { format };
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env bash
2
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
3
+ exec node "$SCRIPT_DIR/../cli.js" "$@"
package/cli.js ADDED
@@ -0,0 +1,246 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * nginx-pretty - Safely format and apply NGINX config files
5
+ * Never modifies originals unless user confirms. Shows diff, validates with nginx -t.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const readline = require('readline');
11
+ const { spawn } = require('child_process');
12
+
13
+ const DEFAULT_CONFIG = '/etc/nginx/sites-available/default';
14
+ const FORMATTED_TMP = '/tmp/tmp_nginx_formatted.conf';
15
+
16
+ function loadBeautifier() {
17
+ const beautifier = require('./beautifier');
18
+ return beautifier.format.bind(beautifier);
19
+ }
20
+
21
+ function parseArgs() {
22
+ const args = process.argv.slice(2);
23
+ const opts = {
24
+ file: DEFAULT_CONFIG,
25
+ checkOnly: false,
26
+ dryRun: false,
27
+ noPrompt: false,
28
+ };
29
+ for (let i = 0; i < args.length; i++) {
30
+ if (args[i] === '--file' || args[i] === '-f') {
31
+ opts.file = args[++i] || DEFAULT_CONFIG;
32
+ } else if (args[i] === '--check-only' || args[i] === '--dry-run') {
33
+ opts.checkOnly = true;
34
+ opts.dryRun = true;
35
+ } else if (args[i] === '--no-prompt') {
36
+ opts.noPrompt = true;
37
+ } else if (args[i] === '--help' || args[i] === '-h') {
38
+ printHelp();
39
+ process.exit(0);
40
+ }
41
+ }
42
+ return opts;
43
+ }
44
+
45
+ function printHelp() {
46
+ console.log(`
47
+ nginx-pretty - Safely format and apply NGINX config files
48
+
49
+ Usage: nginx-pretty [OPTIONS]
50
+
51
+ Options:
52
+ --file, -f <path> Config file path (default: /etc/nginx/sites-available/default)
53
+ --check-only Show diff only, do not prompt or apply
54
+ --dry-run Same as --check-only
55
+ --no-prompt Apply without prompting (for CI; use with caution)
56
+ --help, -h Show this help
57
+ `);
58
+ }
59
+
60
+ function getTempPath() {
61
+ return FORMATTED_TMP;
62
+ }
63
+
64
+ function runDiff(fileA, fileB) {
65
+ return new Promise((resolve, reject) => {
66
+ const proc = spawn('diff', ['-u', fileA, fileB], {
67
+ stdio: ['ignore', 'pipe', 'pipe'],
68
+ });
69
+ let out = '';
70
+ let err = '';
71
+ proc.stdout.on('data', (d) => (out += d.toString()));
72
+ proc.stderr.on('data', (d) => (err += d.toString()));
73
+ proc.on('close', (code) => {
74
+ if (code === 0) {
75
+ resolve(null);
76
+ } else if (code === 1) {
77
+ resolve(out || '(files differ)');
78
+ } else {
79
+ reject(new Error(err || `diff exited ${code}`));
80
+ }
81
+ });
82
+ });
83
+ }
84
+
85
+ function runNginxTest() {
86
+ return new Promise((resolve, reject) => {
87
+ const proc = spawn('nginx', ['-t'], {
88
+ stdio: ['ignore', 'pipe', 'pipe'],
89
+ });
90
+ let out = '';
91
+ let err = '';
92
+ proc.stdout.on('data', (d) => (out += d.toString()));
93
+ proc.stderr.on('data', (d) => (err += d.toString()));
94
+ proc.on('error', (e) => {
95
+ reject(e);
96
+ });
97
+ proc.on('close', (code) => {
98
+ if (code === 0) {
99
+ resolve(true);
100
+ } else {
101
+ resolve(false);
102
+ }
103
+ });
104
+ });
105
+ }
106
+
107
+ function prompt(question) {
108
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
109
+ return new Promise((resolve) => {
110
+ rl.question(question, (answer) => {
111
+ rl.close();
112
+ resolve((answer || '').trim().toLowerCase());
113
+ });
114
+ });
115
+ }
116
+
117
+ function main() {
118
+ const opts = parseArgs();
119
+ const format = loadBeautifier();
120
+
121
+ const originalPath = path.resolve(opts.file);
122
+
123
+ console.log('๐Ÿงช Running nginx-pretty...');
124
+
125
+ const tempPath = getTempPath();
126
+ try {
127
+ fs.unlinkSync(tempPath);
128
+ } catch (_) {}
129
+
130
+ let originalContent;
131
+ try {
132
+ originalContent = fs.readFileSync(originalPath, 'utf8');
133
+ } catch (e) {
134
+ console.error('โŒ Cannot read file:', originalPath);
135
+ console.error(e.message);
136
+ process.exit(1);
137
+ }
138
+
139
+ console.log('๐Ÿ“„ Copying original file to:', tempPath);
140
+ try {
141
+ fs.writeFileSync(tempPath, originalContent, { mode: 0o644 });
142
+ } catch (e) {
143
+ console.error('โŒ Cannot write temp file:', tempPath);
144
+ console.error(e.message);
145
+ process.exit(1);
146
+ }
147
+
148
+ console.log('๐Ÿ†• Ensuring', tempPath, 'is writable...');
149
+ console.log('๐ŸŽจ Formatting the copied config...');
150
+
151
+ const formattedContent = format(originalContent);
152
+
153
+ fs.writeFileSync(tempPath, formattedContent, { mode: 0o644 });
154
+
155
+ runDiff(originalPath, tempPath)
156
+ .then((diffOutput) => {
157
+ if (diffOutput === null) {
158
+ console.log('โœ… No changes detected.');
159
+ try {
160
+ fs.unlinkSync(tempPath);
161
+ } catch (_) {}
162
+ process.exit(0);
163
+ return;
164
+ }
165
+
166
+ console.log('\n๐Ÿ” Showing unified diff (ORIGINAL vs FORMATTED):');
167
+ console.log(diffOutput);
168
+ console.log('โŒ Changes detected. Review diff and rerun if needed.');
169
+
170
+ if (opts.checkOnly || opts.dryRun) {
171
+ console.log('โ„น๏ธ Dry run / check-only: not applying.');
172
+ try {
173
+ fs.unlinkSync(tempPath);
174
+ } catch (_) {}
175
+ process.exit(0);
176
+ return;
177
+ }
178
+
179
+ const doApply = () => {
180
+ try {
181
+ fs.copyFileSync(tempPath, originalPath);
182
+ } catch (e) {
183
+ console.error('โŒ Failed to apply config:', e.message);
184
+ try {
185
+ fs.unlinkSync(tempPath);
186
+ } catch (_) {}
187
+ process.exit(1);
188
+ }
189
+
190
+ runNginxTest()
191
+ .then((ok) => {
192
+ try {
193
+ fs.unlinkSync(tempPath);
194
+ } catch (_) {}
195
+ if (ok) {
196
+ console.log('๐Ÿš€ Config test passed. You can now reload NGINX');
197
+ console.log(' Run: sudo service nginx reload to apply the changes');
198
+ } else {
199
+ console.log(
200
+ 'โš ๏ธ Config was applied but nginx -t reported issues. Review your config.'
201
+ );
202
+ }
203
+ })
204
+ .catch((e) => {
205
+ try {
206
+ fs.unlinkSync(tempPath);
207
+ } catch (_) {}
208
+ if (e.code === 'ENOENT') {
209
+ console.log('โœ… Config applied. (nginx not found on PATH; run nginx -t and reload on a server with NGINX.)');
210
+ } else {
211
+ console.error('โŒ nginx -t failed:', e.message);
212
+ }
213
+ });
214
+ };
215
+
216
+ if (opts.noPrompt) {
217
+ doApply();
218
+ } else {
219
+ console.log('');
220
+ prompt('โš ๏ธ Do you want to apply the formatted config? (yes/no): ')
221
+ .then((answer) => {
222
+ if (answer === 'yes' || answer === 'y') {
223
+ doApply();
224
+ } else {
225
+ console.log('โŒ Skipping apply step. Review diff and rerun if needed.');
226
+ try {
227
+ fs.unlinkSync(tempPath);
228
+ } catch (_) {}
229
+ process.exit(0);
230
+ }
231
+ });
232
+ }
233
+ })
234
+ .catch((err) => {
235
+ console.error('โš ๏ธ diff failed:', err.message);
236
+ console.log('\nFormatted output preview:\n');
237
+ console.log(formattedContent);
238
+ console.log('\nConsider applying manually if acceptable.');
239
+ try {
240
+ fs.unlinkSync(tempPath);
241
+ } catch (_) {}
242
+ process.exit(1);
243
+ });
244
+ }
245
+
246
+ main();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "nginx-pretty",
3
+ "version": "1.0.0",
4
+ "description": "Safely format and apply NGINX config files. Never modifies originals unless confirmed. Shows diff, validates with nginx -t.",
5
+ "main": "cli.js",
6
+ "bin": {
7
+ "nginx-pretty": "cli.js"
8
+ },
9
+ "keywords": [
10
+ "nginx",
11
+ "formatter",
12
+ "beautifier",
13
+ "config",
14
+ "cli",
15
+ "pretty"
16
+ ],
17
+ "author": "",
18
+ "license": "MIT",
19
+ "files": [
20
+ "cli.js",
21
+ "beautifier",
22
+ "bin",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "engines": {
27
+ "node": ">=14.0.0"
28
+ },
29
+ "scripts": {
30
+ "build:pkg": "pkg cli.js --output nginx-pretty --targets node18-linux-x64,node18-darwin-x64,node18-darwin-arm64"
31
+ },
32
+ "devDependencies": {
33
+ "pkg": "^5.8.1"
34
+ },
35
+ "pkg": {
36
+ "assets": ["beautifier/**"]
37
+ }
38
+ }