-
Notifications
You must be signed in to change notification settings - Fork 233
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
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
2b34462
chore: enhance npm start command to run compass sync and sandbox COMP…
nbbeeken aa26f8a
make desktop run normally
nbbeeken 7570add
remove init
nbbeeken 1e8debf
fix: to avoid port overlap prevent sandbox running with other commands
nbbeeken 47bcf13
fix: don't leave behind electron processes
nbbeeken 8068f7b
chore: rm lodash
nbbeeken f242503
fix pnpm version
nbbeeken 38271ef
fs fix
nbbeeken 4d5ee40
undo typecheck change
nbbeeken 39c037b
fix prefixing
nbbeeken 9e545ab
use fetch head instead
nbbeeken c24e856
fix shortcuts
nbbeeken c8e8717
improve signal handling
nbbeeken e47e12f
fix debouncer
nbbeeken 255706e
rm eslint change
nbbeeken add4ce6
chore: nits
nbbeeken 47d21c7
use node execPath
nbbeeken File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
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 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' | ||
); | ||
} | ||
|
||
// Set up early signal handling and cleanup | ||
let devServer: child_process.ChildProcess | undefined; | ||
let distWatcher: fs.FSWatcher | undefined; | ||
let webpackWatchProcess: child_process.ChildProcess | undefined; | ||
|
||
const tmpDir = path.join( | ||
os.tmpdir(), | ||
`mongodb-js--compass-web-${Date.now().toString(36)}` | ||
); | ||
const srcDir = path.resolve(import.meta.dirname, '..', 'dist'); | ||
const destDir = path.dirname( | ||
child_process.execFileSync( | ||
process.execPath, | ||
['-e', "console.log(require.resolve('@mongodb-js/compass-web'))"], | ||
{ cwd: process.env.MMS_HOME, encoding: 'utf-8' } | ||
) | ||
); | ||
|
||
fs.mkdirSync(srcDir, { recursive: true }); | ||
// Create a copy of current dist that will be overridden by link, we'll restore | ||
// it when we are done | ||
fs.mkdirSync(tmpDir, { recursive: true }); | ||
fs.cpSync(destDir, tmpDir, { recursive: true }); | ||
|
||
const failProofRunner = () => | ||
new (class FailProofRunner extends Array { | ||
append(...fns: any[]) { | ||
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: NodeJS.Signals): void { | ||
console.log(`\nReceived ${signalName}, cleaning up...`); | ||
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); | ||
} | ||
|
||
// Set up signal handlers immediately | ||
process.on('SIGINT', () => cleanup('SIGINT')); | ||
process.on('SIGTERM', () => cleanup('SIGTERM')); | ||
|
||
async function isDevServerRunning( | ||
port: number, | ||
host: string = '127.0.0.1' | ||
): Promise<boolean> { | ||
try { | ||
return ( | ||
await fetch(`http://${host}:${port}`, { | ||
method: 'HEAD', | ||
signal: AbortSignal.timeout(3000), | ||
}) | ||
).ok; | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
|
||
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, | ||
npm_config_engine_strict: `${false}`, | ||
}, | ||
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...'); | ||
} | ||
|
||
let oneSec: Promise<void> | null = null; | ||
let pendingCopy = false; | ||
|
||
async function copyDist(): Promise<void> { | ||
// If a copy is already in progress, mark that we need another copy | ||
if (oneSec) { | ||
pendingCopy = true; | ||
return; | ||
} | ||
// Keep copying until there are no more pending requests | ||
do { | ||
pendingCopy = false; | ||
fs.cpSync(srcDir, destDir, { recursive: true }); | ||
oneSec = timers.setTimeout(1000); | ||
await oneSec; | ||
} while (pendingCopy); | ||
|
||
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 | ||
distWatcher = fs.watch(srcDir, () => void copyDist()); | ||
|
||
webpackWatchProcess = child_process.spawn('npm', ['run', 'watch'], { | ||
stdio: 'inherit', | ||
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
If we're not adding some sort of explicit way to disable strict engine check here, would be nice to monitor for the error and print something along the lines that the check can be disabled with
NPM_CONFIG_ENGINE_STRICT=false
flag