Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"package-compass": "npm run package-compass --workspace=mongodb-compass --",
"package-compass-debug": "npm run package-compass-debug --workspace=mongodb-compass --",
"package-compass-nocompile": "npm run package-compass-nocompile --workspace=mongodb-compass --",
"start": "npm run start --workspace=mongodb-compass",
"start": "node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/start.mts",
"start-web": "npm run start --workspace=@mongodb-js/compass-web",
"test": "lerna run test --concurrency 1 --stream",
"test-changed": "lerna run test --stream --concurrency 1 --since origin/HEAD",
Expand Down
13 changes: 13 additions & 0 deletions packages/compass-web/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,17 @@ module.exports = {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
overrides: [
Copy link
Collaborator

@gribnoysup gribnoysup Sep 30, 2025

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

Copy link
Collaborator Author

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

{
files: ['**/*.mjs'],
parserOptions: {
ecmaVersion: 2023,
sourceType: 'module',
},
env: {
es2023: true,
node: true,
},
},
],
};
3 changes: 1 addition & 2 deletions packages/compass-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"start": "electron ./scripts/electron-proxy.js",
"analyze": "npm run webpack -- --mode production --analyze",
"watch": "npm run webpack -- --mode development --watch",
"sync": "node scripts/sync-dist-to-mms.js",
"sync": "node scripts/sync-dist-to-mms.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit",
"eslint": "eslint-compass",
"prettier": "prettier-compass",
Expand Down Expand Up @@ -127,7 +127,6 @@
"express": "^4.21.1",
"express-http-proxy": "^2.0.0",
"is-ip": "^5.0.1",
"lodash": "^4.17.21",
"mocha": "^10.2.0",
"mongodb": "^6.19.0",
"mongodb-build-info": "^1.7.2",
Expand Down
102 changes: 0 additions & 102 deletions packages/compass-web/scripts/sync-dist-to-mms.js

This file was deleted.

175 changes: 175 additions & 0 deletions packages/compass-web/scripts/sync-dist-to-mms.mjs
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be misreading the logic here, but I think we should rather have a trailing debounce, not a leading one, or both (a good thing about lodash was that it was doing both: first run is immediate, if there was a second one during the timeout, it will also happen when debounce is ready to resolve)

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);
32 changes: 32 additions & 0 deletions scripts/start.md
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 |
Loading
Loading