Skip to content

Commit 6147765

Browse files
authored
chore: enhance npm start command to run compass sync and sandbox COMPASS-9851 (#7338)
1 parent 8feea32 commit 6147765

File tree

7 files changed

+486
-107
lines changed

7 files changed

+486
-107
lines changed

package-lock.json

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"package-compass": "npm run package-compass --workspace=mongodb-compass --",
3333
"package-compass-debug": "npm run package-compass-debug --workspace=mongodb-compass --",
3434
"package-compass-nocompile": "npm run package-compass-nocompile --workspace=mongodb-compass --",
35-
"start": "npm run start --workspace=mongodb-compass",
35+
"start": "node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/start.mts",
3636
"start-web": "npm run start --workspace=@mongodb-js/compass-web",
3737
"test": "lerna run test --concurrency 1 --stream",
3838
"test-changed": "lerna run test --stream --concurrency 1 --since origin/HEAD",

packages/compass-web/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"start": "electron ./scripts/electron-proxy.js",
5050
"analyze": "npm run webpack -- --mode production --analyze",
5151
"watch": "npm run webpack -- --mode development --watch",
52-
"sync": "node scripts/sync-dist-to-mms.js",
52+
"sync": "node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/sync-dist-to-mms.mts",
5353
"typecheck": "tsc -p tsconfig.json --noEmit",
5454
"eslint": "eslint-compass",
5555
"prettier": "prettier-compass",
@@ -127,7 +127,6 @@
127127
"express": "^4.21.1",
128128
"express-http-proxy": "^2.0.0",
129129
"is-ip": "^5.0.1",
130-
"lodash": "^4.17.21",
131130
"mocha": "^10.2.0",
132131
"mongodb": "^6.19.0",
133132
"mongodb-build-info": "^1.7.2",

packages/compass-web/scripts/sync-dist-to-mms.js

Lines changed: 0 additions & 102 deletions
This file was deleted.
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import process from 'node:process';
2+
import fs, { promises as asyncFs } from 'node:fs';
3+
import path from 'node:path';
4+
import child_process from 'node:child_process';
5+
import os from 'node:os';
6+
import util from 'node:util';
7+
import timers from 'node:timers/promises';
8+
9+
if (!process.env.MMS_HOME) {
10+
throw new Error(
11+
'Missing required environment variable $MMS_HOME. Make sure you finished the "Cloud Developer Setup" process'
12+
);
13+
}
14+
15+
// Set up early signal handling and cleanup
16+
let devServer: child_process.ChildProcess | undefined;
17+
let distWatcher: fs.FSWatcher | undefined;
18+
let webpackWatchProcess: child_process.ChildProcess | undefined;
19+
20+
const tmpDir = path.join(
21+
os.tmpdir(),
22+
`mongodb-js--compass-web-${Date.now().toString(36)}`
23+
);
24+
const srcDir = path.resolve(import.meta.dirname, '..', 'dist');
25+
const destDir = path.dirname(
26+
child_process.execFileSync(
27+
process.execPath,
28+
['-e', "console.log(require.resolve('@mongodb-js/compass-web'))"],
29+
{ cwd: process.env.MMS_HOME, encoding: 'utf-8' }
30+
)
31+
);
32+
33+
fs.mkdirSync(srcDir, { recursive: true });
34+
// Create a copy of current dist that will be overridden by link, we'll restore
35+
// it when we are done
36+
fs.mkdirSync(tmpDir, { recursive: true });
37+
fs.cpSync(destDir, tmpDir, { recursive: true });
38+
39+
const failProofRunner = () =>
40+
new (class FailProofRunner extends Array {
41+
append(...fns: any[]) {
42+
this.push(...fns);
43+
return this;
44+
}
45+
46+
run() {
47+
const errors = this.map((f) => {
48+
try {
49+
f();
50+
} catch (e) {
51+
return e;
52+
}
53+
}).filter((e) => e);
54+
55+
if (errors.length) {
56+
fs.writeSync(
57+
process.stdout.fd,
58+
util.inspect(errors, { depth: 20 }) + '\n'
59+
);
60+
}
61+
62+
return errors.length;
63+
}
64+
})();
65+
66+
function cleanup(signalName: NodeJS.Signals): void {
67+
console.log(`\nReceived ${signalName}, cleaning up...`);
68+
const errorCount = failProofRunner()
69+
.append(() => distWatcher?.close())
70+
.append(() => webpackWatchProcess?.kill(signalName))
71+
.append(() => devServer?.kill(signalName))
72+
.append(() => fs.cpSync(tmpDir, destDir, { recursive: true }))
73+
.append(() => fs.rmSync(tmpDir, { recursive: true, force: true }))
74+
.run();
75+
fs.writeSync(process.stdout.fd, 'Exit compass-web sync...\n');
76+
process.exit(errorCount);
77+
}
78+
79+
// Set up signal handlers immediately
80+
process.on('SIGINT', () => cleanup('SIGINT'));
81+
process.on('SIGTERM', () => cleanup('SIGTERM'));
82+
83+
async function isDevServerRunning(
84+
port: number,
85+
host: string = '127.0.0.1'
86+
): Promise<boolean> {
87+
try {
88+
return (
89+
await fetch(`http://${host}:${port}`, {
90+
method: 'HEAD',
91+
signal: AbortSignal.timeout(3000),
92+
})
93+
).ok;
94+
} catch (error) {
95+
return false;
96+
}
97+
}
98+
99+
if (!(await isDevServerRunning(8081))) {
100+
console.log('mms dev server is not running... launching!');
101+
102+
const { engines } = JSON.parse(
103+
await asyncFs.readFile(
104+
path.join(process.env.MMS_HOME, 'package.json'),
105+
'utf8'
106+
)
107+
);
108+
const pnpmVersion = engines.pnpm ?? 'latest';
109+
110+
const halfRamMb = Math.min(
111+
Math.floor(os.totalmem() / 2 / 1024 / 1024),
112+
16384
113+
);
114+
// Merge with existing NODE_OPTIONS if present
115+
const existingNodeOptions = process.env.NODE_OPTIONS ?? '';
116+
const mergedNodeOptions = [
117+
`--max_old_space_size=${halfRamMb}`,
118+
existingNodeOptions,
119+
]
120+
.filter(Boolean)
121+
.join(' ');
122+
123+
devServer = child_process.spawn(
124+
'npx',
125+
[`pnpm@${pnpmVersion}`, 'run', 'start'],
126+
{
127+
cwd: process.env.MMS_HOME,
128+
env: {
129+
...process.env,
130+
NODE_OPTIONS: mergedNodeOptions,
131+
npm_config_engine_strict: `${false}`,
132+
},
133+
stdio: 'inherit',
134+
}
135+
);
136+
137+
// Wait for dev server to be ready before proceeding
138+
console.log('Waiting for dev server to start...');
139+
let retries = 30; // 30 seconds max
140+
while (retries > 0 && !(await isDevServerRunning(8081))) {
141+
await timers.setTimeout(1000);
142+
retries--;
143+
}
144+
145+
if (retries === 0) {
146+
console.warn('Dev server may not be fully ready, proceeding anyway...');
147+
} else {
148+
console.log('Dev server is ready!');
149+
}
150+
} else {
151+
console.log('Skipping running MMS dev server...');
152+
}
153+
154+
let oneSec: Promise<void> | null = null;
155+
let pendingCopy = false;
156+
157+
async function copyDist(): Promise<void> {
158+
// If a copy is already in progress, mark that we need another copy
159+
if (oneSec) {
160+
pendingCopy = true;
161+
return;
162+
}
163+
// Keep copying until there are no more pending requests
164+
do {
165+
pendingCopy = false;
166+
fs.cpSync(srcDir, destDir, { recursive: true });
167+
oneSec = timers.setTimeout(1000);
168+
await oneSec;
169+
} while (pendingCopy);
170+
171+
oneSec = null;
172+
}
173+
174+
// The existing approach of using `npm / pnpm link` commands doesn't play well
175+
// with webpack that will start to resolve other modules relative to the imports
176+
// from compass-web inevitably causing some modules to resolve from the compass
177+
// monorepo instead of mms one. To work around that we are just watching for any
178+
// file changes in the dist folder and copying them as-is to whatever place
179+
// compass-web was installed in mms node_modules
180+
distWatcher = fs.watch(srcDir, () => void copyDist());
181+
182+
webpackWatchProcess = child_process.spawn('npm', ['run', 'watch'], {
183+
stdio: 'inherit',
184+
});

scripts/start.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Start Script Help
2+
3+
Usage: start.mts [-h/--help] [targets... [targetOptions...]]
4+
5+
## Options
6+
7+
- `-h, --help` Show this help message
8+
9+
## Targets
10+
11+
- `desktop` Start MongoDB Compass Desktop (default if no targets specified)
12+
- `sandbox` Start MongoDB Compass Web Sandbox, useful for UI-only changes
13+
- `sync` Start Cloud Sync, in combination with redirector/redwood can be used to test data explorer changes
14+
- `--no-mms` can be passed to the sync subcommand to not run MMS's dev server.
15+
16+
**Note:** `sandbox` must be run alone and cannot be combined with other targets.
17+
18+
## Port Configuration
19+
20+
The `desktop` and `sandbox` targets both use the same webpack dev server port (4242) by design. When running both targets simultaneously, the sandbox will automatically detect that the desktop target's webpack dev server is already running and will skip starting its own, sharing the same webpack dev server instance.
21+
22+
Since `sync` must run alone, there are no port conflicts with other targets.
23+
24+
### Well-Known Ports
25+
26+
| Port | Service | Target | Description |
27+
| ---- | ---------------------- | -------------------- | ----------------------------------------------- |
28+
| 4242 | Webpack Dev Server | `desktop`, `sandbox` | Serves compiled frontend assets with hot reload |
29+
| 7777 | HTTP Proxy Server | `sandbox` | Express proxy server for Atlas API requests |
30+
| 1337 | WebSocket Proxy Server | `sandbox` | WebSocket proxy for MongoDB connections |
31+
| 8080 | Atlas Local Backend | `sync` | Local MMS backend server (when MMS_HOME is set) |
32+
| 8081 | MMS Dev Server | `sync` | Local MMS development server |

0 commit comments

Comments
 (0)