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 +156 -1
- package/default/gitignore +1 -0
- package/default/mango/helpers/syncDbWithLocal.sh +2 -2
- package/default/package.json +1 -1
- package/default/src/helpers/mango.js +13 -2
- package/default/src/main.js +1 -0
- package/default/vite.config.js +5 -0
- package/lib/dev-proxy.js +79 -0
- package/lib/hosts-manager.js +53 -0
- package/package.json +3 -1
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
|
@@ -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;
|
package/default/package.json
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
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)
|
package/default/src/main.js
CHANGED
|
@@ -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({
|
package/default/vite.config.js
CHANGED
|
@@ -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: {
|
package/lib/dev-proxy.js
ADDED
|
@@ -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.
|
|
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",
|