-
Notifications
You must be signed in to change notification settings - Fork 231
chore: enhance npm start command to run compass sync and sandbox COMPASS-9851 #7338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 9 commits
2b34462
aa26f8a
7570add
1e8debf
47bcf13
8068f7b
f242503
38271ef
4d5ee40
39c037b
9e545ab
c24e856
c8e8717
e47e12f
255706e
add4ce6
47d21c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import process from 'node:process'; | ||
import fs, { promises as asyncFs } from 'node:fs'; | ||
import path from 'node:path'; | ||
import child_process from 'node:child_process'; | ||
import os from 'node:os'; | ||
import util from 'node:util'; | ||
import net from 'node:net'; | ||
import timers from 'node:timers/promises'; | ||
|
||
if (!process.env.MMS_HOME) { | ||
throw new Error( | ||
'Missing required environment variable $MMS_HOME. Make sure you finished the "Cloud Developer Setup" process' | ||
); | ||
} | ||
|
||
function isDevServerRunning(port, host = '127.0.0.1') { | ||
return new Promise((resolve) => { | ||
const socket = new net.Socket(); | ||
socket | ||
.setTimeout(1000) | ||
.on('connect', () => { | ||
socket.destroy(); | ||
resolve(true); | ||
}) | ||
.on('error', () => { | ||
socket.destroy(); | ||
resolve(false); | ||
}) | ||
.on('timeout', () => { | ||
socket.destroy(); | ||
resolve(false); | ||
}) | ||
.connect(port, host); | ||
}); | ||
} | ||
|
||
let devServer; | ||
if (!(await isDevServerRunning(8081))) { | ||
console.log('mms dev server is not running... launching!'); | ||
|
||
const { engines } = JSON.parse( | ||
await asyncFs.readFile( | ||
path.join(process.env.MMS_HOME, 'package.json'), | ||
'utf8' | ||
) | ||
); | ||
const pnpmVersion = engines.pnpm ?? 'latest'; | ||
|
||
const halfRamMb = Math.min( | ||
Math.floor(os.totalmem() / 2 / 1024 / 1024), | ||
16384 | ||
); | ||
// Merge with existing NODE_OPTIONS if present | ||
const existingNodeOptions = process.env.NODE_OPTIONS ?? ''; | ||
const mergedNodeOptions = [ | ||
`--max_old_space_size=${halfRamMb}`, | ||
existingNodeOptions, | ||
] | ||
.filter(Boolean) | ||
.join(' '); | ||
|
||
devServer = child_process.spawn( | ||
'npx', | ||
[`pnpm@${pnpmVersion}`, 'run', 'start'], | ||
{ | ||
cwd: process.env.MMS_HOME, | ||
env: { | ||
...process.env, | ||
NODE_OPTIONS: mergedNodeOptions, | ||
}, | ||
stdio: 'inherit', | ||
} | ||
); | ||
|
||
// Wait for dev server to be ready before proceeding | ||
console.log('Waiting for dev server to start...'); | ||
let retries = 30; // 30 seconds max | ||
while (retries > 0 && !(await isDevServerRunning(8081))) { | ||
await timers.setTimeout(1000); | ||
retries--; | ||
} | ||
|
||
if (retries === 0) { | ||
console.warn('Dev server may not be fully ready, proceeding anyway...'); | ||
} else { | ||
console.log('Dev server is ready!'); | ||
} | ||
} else { | ||
console.log('Skipping running MMS dev server...'); | ||
} | ||
|
||
const srcDir = path.resolve(import.meta.dirname, '..', 'dist'); | ||
|
||
const destDir = path.dirname( | ||
child_process.execFileSync( | ||
'node', | ||
['-e', "console.log(require.resolve('@mongodb-js/compass-web'))"], | ||
{ cwd: process.env.MMS_HOME, encoding: 'utf-8' } | ||
) | ||
); | ||
|
||
const tmpDir = path.join( | ||
os.tmpdir(), | ||
`mongodb-js--compass-web-${Date.now().toString(36)}` | ||
); | ||
|
||
fs.mkdirSync(srcDir, { recursive: true }); | ||
|
||
// Create a copy of current dist that will be overriden by link, we'll restore | ||
// it when we are done | ||
fs.mkdirSync(tmpDir, { recursive: true }); | ||
fs.cpSync(destDir, tmpDir, { recursive: true }); | ||
|
||
let oneSec = null; | ||
async function copyDist() { | ||
// If a copy is already in progress, return early (debounce) | ||
if (oneSec) return; | ||
fs.cpSync(srcDir, destDir, { recursive: true }); | ||
oneSec = timers.setTimeout(1000); | ||
nbbeeken marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
nbbeeken marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
await oneSec; | ||
oneSec = null; | ||
} | ||
|
||
// The existing approach of using `npm / pnpm link` commands doesn't play well | ||
// with webpack that will start to resolve other modules relative to the imports | ||
// from compass-web inevitably causing some modules to resolve from the compass | ||
// monorepo instead of mms one. To work around that we are just watching for any | ||
// file changes in the dist folder and copying them as-is to whatever place | ||
// compass-web was installed in mms node_modules | ||
const distWatcher = fs.watch(srcDir, () => void copyDist()); | ||
|
||
const webpackWatchProcess = child_process.spawn('npm', ['run', 'watch'], { | ||
stdio: 'inherit', | ||
}); | ||
|
||
const failProofRunner = () => | ||
new (class FailProofRunner extends Array { | ||
append(...fns) { | ||
this.push(...fns); | ||
return this; | ||
} | ||
|
||
run() { | ||
const errors = this.map((f) => { | ||
try { | ||
f(); | ||
} catch (e) { | ||
return e; | ||
} | ||
}).filter((e) => e); | ||
|
||
if (errors.length) { | ||
fs.writeSync( | ||
process.stdout.fd, | ||
util.inspect(errors, { depth: 20 }) + '\n' | ||
); | ||
} | ||
|
||
return errors.length; | ||
} | ||
})(); | ||
|
||
function cleanup(signalName) { | ||
const errorCount = failProofRunner() | ||
.append(() => distWatcher.close()) | ||
.append(() => webpackWatchProcess.kill(signalName)) | ||
.append(() => devServer?.kill(signalName)) | ||
.append(() => fs.cpSync(tmpDir, destDir, { recursive: true })) | ||
.append(() => fs.rmSync(tmpDir, { recursive: true, force: true })) | ||
.run(); | ||
fs.writeSync(process.stdout.fd, 'Exit compass-web sync...\n'); | ||
process.exit(errorCount); | ||
} | ||
|
||
process.on('SIGINT', cleanup).on('SIGTERM', cleanup); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# Start Script Help | ||
|
||
Usage: start.mts [-h/--help] [targets... [targetOptions...]] | ||
|
||
## Options | ||
|
||
- `-h, --help` Show this help message | ||
|
||
## Targets | ||
|
||
- `desktop` Start MongoDB Compass Desktop (default if no targets specified) | ||
- `sandbox` Start MongoDB Compass Web Sandbox, useful for UI-only changes | ||
- `sync` Start Cloud Sync, in combination with redirector/redwood can be used to test data explorer changes | ||
- `--no-mms` can be passed to the sync subcommand to not run MMS's dev server. | ||
|
||
**Note:** `sandbox` must be run alone and cannot be combined with other targets. | ||
|
||
## Port Configuration | ||
|
||
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. | ||
|
||
Since `sync` must run alone, there are no port conflicts with other targets. | ||
|
||
### Well-Known Ports | ||
|
||
| Port | Service | Target | Description | | ||
| ---- | ---------------------- | -------------------- | ----------------------------------------------- | | ||
| 4242 | Webpack Dev Server | `desktop`, `sandbox` | Serves compiled frontend assets with hot reload | | ||
| 7777 | HTTP Proxy Server | `sandbox` | Express proxy server for Atlas API requests | | ||
| 1337 | WebSocket Proxy Server | `sandbox` | WebSocket proxy for MongoDB connections | | ||
| 8080 | Atlas Local Backend | `sync` | Local MMS backend server (when MMS_HOME is set) | | ||
| 8081 | MMS Dev Server | `sync` | Local MMS development server | |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we might want to extens our shared eslint config with this, not only compass web, but not a blocker
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree but this problem also goes away if we use .mts which is readily accessible now so we can use that until we're forced to use mjs for some reason