mango-cms 0.2.48 → 0.2.49

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/cli.js CHANGED
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { Command } = require('commander');
4
- const { execSync } = require('child_process');
4
+ const { execSync, spawn } = require('child_process');
5
5
  const path = require('path');
6
6
  const inquirer = require('inquirer');
7
7
  const fs = require('fs-extra');
8
8
  const axios = require('axios');
9
9
  const AdmZip = require('adm-zip');
10
+ const { addHosts, removeHosts } = require('./lib/hosts-manager');
10
11
 
11
12
  const program = new Command();
12
13
 
@@ -783,4 +784,104 @@ program
783
784
  }
784
785
  });
785
786
 
787
+ // --- Dev Proxy Commands ---
788
+ const proxyCmd = program
789
+ .command('proxy <action>')
790
+ .description('Manage local dev proxy domains (start, stop, status)')
791
+ .action(async (action) => {
792
+ const allowed = ['start', 'stop', 'status'];
793
+ if (!allowed.includes(action)) {
794
+ console.error(`Unknown proxy action: ${action}. Use: ${allowed.join(', ')}`);
795
+ process.exit(1);
796
+ }
797
+
798
+ const pidPath = path.join(process.cwd(), '.mango-proxy.pid');
799
+ const settingsPath = path.join(process.cwd(), 'mango/config/settings.json');
800
+
801
+ if (action === 'start') {
802
+ if (!fs.existsSync(settingsPath)) {
803
+ console.error('Error: mango/config/settings.json not found. Run this from your project root.');
804
+ process.exit(1);
805
+ }
806
+
807
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
808
+ const { siteName } = settings;
809
+
810
+ if (!siteName) {
811
+ console.error('Error: siteName must be set in settings.json');
812
+ process.exit(1);
813
+ }
814
+
815
+ // Derive dev proxy domains from siteName
816
+ const slug = siteName.toLowerCase().replace(/[^a-z0-9]/g, '');
817
+ const frontDomain = `${slug}.mango`;
818
+ const apiDomain = `api.${slug}.mango`;
819
+
820
+ // Kill existing proxy if running
821
+ if (fs.existsSync(pidPath)) {
822
+ try {
823
+ const oldPid = fs.readFileSync(pidPath, 'utf8').trim();
824
+ execSync(`sudo kill ${oldPid} 2>/dev/null || true`);
825
+ } catch (e) { /* ignore */ }
826
+ fs.unlinkSync(pidPath);
827
+ }
828
+
829
+ // Add /etc/hosts entries
830
+ addHosts([frontDomain, apiDomain]);
831
+
832
+ const frontendPort = settings.frontPort || 5173;
833
+ const backendPort = settings.port || 3002;
834
+
835
+ // Start the proxy server as a detached background process
836
+ const proxyScript = path.join(__dirname, 'lib/dev-proxy.js');
837
+ const child = spawn('sudo', ['node', proxyScript, frontDomain, apiDomain, String(frontendPort), String(backendPort), pidPath], {
838
+ detached: true,
839
+ stdio: 'ignore',
840
+ });
841
+ child.unref();
842
+
843
+ console.log(`
844
+ Mango Dev Proxy started.
845
+
846
+ http://${frontDomain} -> localhost:${frontendPort} (frontend)
847
+ http://${apiDomain} -> localhost:${backendPort} (backend)
848
+
849
+ Start your dev servers with proxy support:
850
+ mango start (backend on port ${backendPort})
851
+ VITE_DEV_PROXY=${apiDomain} mango ui dev (frontend on port ${frontendPort})
852
+
853
+ To stop: mango proxy stop
854
+ `);
855
+ } else if (action === 'stop') {
856
+ if (fs.existsSync(pidPath)) {
857
+ try {
858
+ const pid = fs.readFileSync(pidPath, 'utf8').trim();
859
+ execSync(`sudo kill ${pid}`);
860
+ console.log('Proxy server stopped.');
861
+ } catch (e) {
862
+ console.log('Proxy process not found (may have already exited).');
863
+ }
864
+ fs.unlinkSync(pidPath);
865
+ } else {
866
+ console.log('No proxy PID file found. Proxy may not be running.');
867
+ }
868
+
869
+ removeHosts();
870
+ console.log('Dev proxy cleaned up.');
871
+
872
+ } else if (action === 'status') {
873
+ if (fs.existsSync(pidPath)) {
874
+ const pid = fs.readFileSync(pidPath, 'utf8').trim();
875
+ try {
876
+ execSync(`kill -0 ${pid} 2>/dev/null`);
877
+ console.log(`Proxy is running (PID: ${pid}).`);
878
+ } catch (e) {
879
+ console.log('Proxy PID file exists but process is not running.');
880
+ }
881
+ } else {
882
+ console.log('Proxy is not running.');
883
+ }
884
+ }
885
+ });
886
+
786
887
  program.parse(process.argv);
package/default/gitignore CHANGED
@@ -3,3 +3,4 @@ node_modules
3
3
  yarn.lock
4
4
  build
5
5
  dist
6
+ .mango-proxy.pid
@@ -25,7 +25,7 @@
25
25
  "dayjs": "^1.10.7",
26
26
  "express": "^4.18.1",
27
27
  "google-maps": "^4.3.3",
28
- "mango-cms": "^0.2.48",
28
+ "mango-cms": "^0.2.49",
29
29
  "mapbox-gl": "^2.7.0",
30
30
  "sweetalert2": "^11.4.0",
31
31
  "vite": "^6.2.2",
@@ -21,8 +21,14 @@ let api = `https://${mangoDomain}`
21
21
  let ws = `wss://${mangoDomain}/graphql`
22
22
 
23
23
  if (process.env.NODE_ENV != 'production' && useDevAPI) {
24
- api = `http://localhost:${port}`
25
- ws = `ws://localhost:${port}/graphql`
24
+ if (import.meta.env.VITE_DEV_PROXY) {
25
+ const proxyApi = import.meta.env.VITE_DEV_PROXY
26
+ api = `http://${proxyApi}`
27
+ ws = `ws://${proxyApi}/graphql`
28
+ } else {
29
+ api = `http://localhost:${port}`
30
+ ws = `ws://localhost:${port}/graphql`
31
+ }
26
32
  }
27
33
 
28
34
  function getQuery(params) {
@@ -69,6 +69,7 @@ app.provide('darkMode', darkMode().darkMode)
69
69
  const user = initUser()
70
70
 
71
71
  let api = useDevAPI ? `http://localhost:${port}` : `https://${mangoDomain}`
72
+ if (isDev && import.meta.env.VITE_DEV_PROXY) api = `http://${import.meta.env.VITE_DEV_PROXY}`
72
73
  if (!isDev) api = `https://${mangoDomain}`
73
74
 
74
75
  const store = reactive({
@@ -57,6 +57,11 @@ export default defineConfig({
57
57
  ],
58
58
  server: {
59
59
  host: '0.0.0.0',
60
+ // When running behind mango proxy, configure HMR to use the proxy domain
61
+ // VITE_DEV_PROXY is set to api.{slug}.mango; derive frontend domain by stripping "api." prefix
62
+ ...(process.env.VITE_DEV_PROXY?.startsWith('api.') ? {
63
+ hmr: { host: process.env.VITE_DEV_PROXY.replace(/^api\./, '') }
64
+ } : {}),
60
65
  },
61
66
  resolve: {
62
67
  alias: {
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Mango Dev Proxy Server
5
+ *
6
+ * Runs on port 80, routes requests by hostname to the correct local dev server.
7
+ * Supports WebSocket upgrade for Vite HMR and Socket.io.
8
+ *
9
+ * Usage: node lib/dev-proxy.js <frontDomain> <apiDomain> <frontPort> <backendPort> [pidPath]
10
+ */
11
+
12
+ const http = require('http');
13
+ const httpProxy = require('http-proxy');
14
+ const fs = require('fs');
15
+
16
+ const frontDomain = process.argv[2];
17
+ const apiDomain = process.argv[3];
18
+ const frontendPort = parseInt(process.argv[4], 10);
19
+ const backendPort = parseInt(process.argv[5], 10);
20
+ const pidPath = process.argv[6];
21
+
22
+ if (!frontDomain || !apiDomain || !frontendPort || !backendPort) {
23
+ console.error('Usage: node lib/dev-proxy.js <frontDomain> <apiDomain> <frontPort> <backendPort> [pidPath]');
24
+ process.exit(1);
25
+ }
26
+
27
+ const proxy = httpProxy.createProxyServer({
28
+ ws: true,
29
+ xfwd: true,
30
+ });
31
+
32
+ proxy.on('error', (err, req, res) => {
33
+ console.error(`Proxy error for ${req.headers.host}${req.url}:`, err.message);
34
+ if (res.writeHead) {
35
+ res.writeHead(502);
36
+ res.end('Bad Gateway - is your dev server running?');
37
+ }
38
+ });
39
+
40
+ function getTarget(host) {
41
+ const hostname = host?.split(':')[0];
42
+ if (hostname === apiDomain) {
43
+ return `http://127.0.0.1:${backendPort}`;
44
+ }
45
+ if (hostname === frontDomain) {
46
+ return `http://127.0.0.1:${frontendPort}`;
47
+ }
48
+ return null;
49
+ }
50
+
51
+ const server = http.createServer((req, res) => {
52
+ const target = getTarget(req.headers.host);
53
+ if (!target) {
54
+ res.writeHead(404);
55
+ res.end(`Unknown host: ${req.headers.host}`);
56
+ return;
57
+ }
58
+ proxy.web(req, res, { target });
59
+ });
60
+
61
+ server.on('upgrade', (req, socket, head) => {
62
+ const target = getTarget(req.headers.host);
63
+ if (target) {
64
+ proxy.ws(req, socket, head, { target });
65
+ } else {
66
+ socket.destroy();
67
+ }
68
+ });
69
+
70
+ server.listen(80, '0.0.0.0', () => {
71
+ console.log('Mango Dev Proxy running on port 80');
72
+ console.log(` ${frontDomain} -> http://127.0.0.1:${frontendPort} (frontend)`);
73
+ console.log(` ${apiDomain} -> http://127.0.0.1:${backendPort} (backend)`);
74
+ });
75
+
76
+ // Write PID for cleanup
77
+ if (pidPath) {
78
+ fs.writeFileSync(pidPath, String(process.pid));
79
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Manages /etc/hosts entries for Mango dev proxy domains.
3
+ * Lines are tagged with "# mango-proxy" for easy cleanup.
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const { execSync } = require('child_process');
8
+
9
+ const HOSTS_FILE = '/etc/hosts';
10
+ const TAG = '# mango-proxy';
11
+
12
+ function addHosts(domains) {
13
+ const hostsContent = fs.readFileSync(HOSTS_FILE, 'utf8');
14
+ const newDomains = domains.filter(d => !hostsContent.includes(d));
15
+
16
+ if (newDomains.length === 0) {
17
+ console.log('Hosts entries already exist, skipping.');
18
+ return;
19
+ }
20
+
21
+ const entry = `127.0.0.1 ${newDomains.join(' ')} ${TAG}`;
22
+
23
+ try {
24
+ execSync(`echo '${entry}' | sudo tee -a ${HOSTS_FILE} > /dev/null`, { stdio: 'inherit' });
25
+ console.log(`Added to /etc/hosts: ${newDomains.join(', ')}`);
26
+ } catch (err) {
27
+ console.error('Failed to update /etc/hosts. You may need to add this line manually:');
28
+ console.error(` ${entry}`);
29
+ }
30
+ }
31
+
32
+ function removeHosts() {
33
+ try {
34
+ const hostsContent = fs.readFileSync(HOSTS_FILE, 'utf8');
35
+ const lines = hostsContent.split('\n');
36
+ const filtered = lines.filter(line => !line.includes(TAG));
37
+
38
+ if (lines.length === filtered.length) {
39
+ console.log('No mango-proxy entries found in /etc/hosts.');
40
+ return;
41
+ }
42
+
43
+ const tmpFile = '/tmp/mango-hosts-clean';
44
+ fs.writeFileSync(tmpFile, filtered.join('\n'));
45
+ execSync(`sudo cp ${tmpFile} ${HOSTS_FILE}`, { stdio: 'inherit' });
46
+ fs.unlinkSync(tmpFile);
47
+ console.log('Removed mango-proxy entries from /etc/hosts.');
48
+ } catch (err) {
49
+ console.error('Failed to clean /etc/hosts:', err.message);
50
+ }
51
+ }
52
+
53
+ module.exports = { addHosts, removeHosts };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mango-cms",
3
- "version": "0.2.48",
3
+ "version": "0.2.49",
4
4
  "main": "./index.js",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -10,6 +10,7 @@
10
10
  "index.js",
11
11
  "cli.js",
12
12
  "webpack.config.js",
13
+ "lib/**/*",
13
14
  "default/**/*",
14
15
  "!default/node_modules/**"
15
16
  ],
@@ -70,6 +71,7 @@
70
71
  "graphql-type-datetime": "^0.2.4",
71
72
  "graphql-ws": "^5.9.0",
72
73
  "html-to-text": "^8.0.0",
74
+ "http-proxy": "^1.18.1",
73
75
  "inquirer": "^8.2.4",
74
76
  "json-fn": "^1.1.1",
75
77
  "lodash": "^4.17.21",