mango-cms 0.2.47 → 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
 
@@ -675,6 +676,60 @@ program
675
676
  }
676
677
  });
677
678
 
679
+ program
680
+ .command('pull')
681
+ .description('Pull the remote database to your local machine')
682
+ .action(async () => {
683
+ try {
684
+ const configPath = path.join(process.cwd(), 'mango/config/settings.json');
685
+
686
+ if (!fs.existsSync(configPath)) {
687
+ console.error('Error: settings.json not found at', configPath);
688
+ process.exit(1);
689
+ }
690
+
691
+ const settings = JSON.parse(fs.readFileSync(configPath, 'utf8'));
692
+ const serverIp = settings.serverIp;
693
+ const database = settings.database;
694
+
695
+ if (!serverIp) {
696
+ console.error('Error: serverIp is not configured in settings.json');
697
+ process.exit(1);
698
+ }
699
+
700
+ if (!database) {
701
+ console.error('Error: database is not configured in settings.json');
702
+ process.exit(1);
703
+ }
704
+
705
+ console.log(`Using server IP: ${serverIp}`);
706
+ console.log(`Using database: ${database}`);
707
+
708
+ const downloadsDir = path.join(require('os').homedir(), 'Downloads');
709
+ const dumpZip = path.join(downloadsDir, 'dump.zip');
710
+ const dumpDir = path.join(downloadsDir, 'dump');
711
+
712
+ console.log('Dumping remote database...');
713
+ execSync(`ssh root@${serverIp} "rm -rf dump dump.zip; mongodump --db ${database}; zip -r dump.zip dump/${database}"`, { stdio: 'inherit' });
714
+
715
+ console.log('Downloading dump...');
716
+ execSync(`rsync root@${serverIp}:~/dump.zip ${dumpZip}`, { stdio: 'inherit' });
717
+
718
+ console.log('Restoring locally...');
719
+ execSync(`unzip -o ${dumpZip}`, { cwd: downloadsDir, stdio: 'inherit' });
720
+ execSync(`mongorestore --drop --db ${database} ${path.join(dumpDir, database)}`, { stdio: 'inherit' });
721
+
722
+ // Clean up
723
+ fs.removeSync(dumpDir);
724
+ fs.removeSync(dumpZip);
725
+
726
+ console.log(`\n✨ Database "${database}" pulled and restored successfully!`);
727
+ } catch (error) {
728
+ console.error('Error pulling database:', error.message);
729
+ process.exit(1);
730
+ }
731
+ });
732
+
678
733
  program
679
734
  .command('update')
680
735
  .description('Update Mango CMS source files to the latest version')
@@ -729,4 +784,104 @@ program
729
784
  }
730
785
  });
731
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
+
732
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
@@ -33,9 +33,9 @@ echo "Using server IP: $SERVER_IP"
33
33
  echo "Using database: $DATABASE"
34
34
 
35
35
  cd ~/Downloads;
36
- ssh root@$SERVER_IP "rm -rf dump.zip; mongodump --db $DATABASE; zip -r dump.zip dump"
36
+ ssh root@$SERVER_IP "rm -rf dump dump.zip; mongodump --db $DATABASE; zip -r dump.zip dump/$DATABASE"
37
37
  rsync root@$SERVER_IP:~/dump.zip ~/Downloads/dump.zip;
38
38
  unzip -o dump.zip;
39
- mongorestore --drop dump;
39
+ mongorestore --drop --db $DATABASE dump/$DATABASE;
40
40
  rm -rf dump;
41
41
  rm -rf dump.zip;
@@ -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.46",
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) {
@@ -356,6 +362,11 @@ const Mango = collections.reduce((a, c) => {
356
362
  a[c.singular]['local']['delete'] = localDB.delete
357
363
  a[c.name]['sync'] = sync
358
364
 
365
+ a[c.name]['count'] = ({ search } = {}) => {
366
+ return runQuery({ search, fields: ['id'], verbose: true })
367
+ .then(data => data?.count || 0)
368
+ }
369
+
359
370
  a[c.name]['search'] = search
360
371
  a[c.name]['search']['init'] = (search, query, algoliaFilters) => {
361
372
  let loading = ref(true)
@@ -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.47",
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",