Skip to content

Commit 981a602

Browse files
committed
Merge branch 'main' of github.com:mongodb-js/mongosh into gagik/options
2 parents 5cf92e8 + 3c88417 commit 981a602

File tree

23 files changed

+862
-22
lines changed

23 files changed

+862
-22
lines changed

packages/arg-parser/src/arg-parser.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { CliOptions } from '@mongosh/arg-parser';
21
import parser from 'yargs-parser';
32
import { z } from 'zod/v4';
43
import type { Options as YargsOptions } from 'yargs-parser';
54
import {
5+
type CliOptions,
66
CliOptionsSchema,
77
processPositionalCliOptions,
88
validateCliOptions,

packages/arg-parser/src/cli-options.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ export const CurrentCliOptionsSchema = z.object({
1414
awsSessionToken: z.string().optional(),
1515
csfleLibraryPath: z.string().optional(),
1616
cryptSharedLibPath: z.string().optional(),
17+
// TODO: This default doesn't do anything on its own but is used as documentation for now.
18+
deepInspect: z.boolean().default(true).optional(),
1719
db: z.string().optional(),
1820
gssapiServiceName: z.string().optional(),
1921
sspiHostnameCanonicalization: z.string().optional(),

packages/cli-repl/src/cli-repl.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import type {
5353
DevtoolsProxyOptions,
5454
} from '@mongodb-js/devtools-proxy-support';
5555
import { useOrCreateAgent } from '@mongodb-js/devtools-proxy-support';
56+
import { fullDepthInspectOptions } from './format-output';
5657

5758
/**
5859
* Connecting text key.
@@ -210,10 +211,11 @@ export class CliRepl implements MongoshIOProvider {
210211
if (jsContext === 'auto' || !jsContext) {
211212
jsContext = willEnterInteractiveMode ? 'repl' : 'plain-vm';
212213
}
214+
const deepInspect = this.cliOptions.deepInspect ?? willEnterInteractiveMode;
213215

214216
this.mongoshRepl = new MongoshNodeRepl({
215217
...options,
216-
shellCliOptions: { ...this.cliOptions, jsContext, quiet },
218+
shellCliOptions: { ...this.cliOptions, jsContext, quiet, deepInspect },
217219
nodeReplOptions: options.nodeReplOptions ?? {
218220
terminal: process.env.MONGOSH_FORCE_TERMINAL ? true : undefined,
219221
},
@@ -738,7 +740,12 @@ export class CliRepl implements MongoshIOProvider {
738740
formattedResult = formatForJSONOutput(e, this.cliOptions.json);
739741
}
740742
} else {
741-
formattedResult = this.mongoshRepl.writer(lastEvalResult);
743+
formattedResult = this.mongoshRepl.writer(
744+
lastEvalResult,
745+
this.cliOptions.deepInspect !== false
746+
? fullDepthInspectOptions
747+
: undefined
748+
);
742749
}
743750
this.output.write(formattedResult + '\n');
744751
}

packages/cli-repl/src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export const USAGE = `
4343
--retryWrites[=true|false] ${i18n.__(
4444
'cli-repl.args.retryWrites'
4545
)}
46+
--deep-inspect[=true|false] ${i18n.__(
47+
'cli-repl.args.deepInspect'
48+
)}
4649
4750
${clr(
4851
i18n.__('cli-repl.args.authenticationOptions'),

packages/cli-repl/src/format-output.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const CONTROL_CHAR_REGEXP_ALLOW_SIMPLE =
3636
// eslint-disable-next-line no-control-regex
3737
/[\x00-\x08\x0B-\x1F\x7F-\x9F]/;
3838

39-
const fullDepthInspectOptions = {
39+
export const fullDepthInspectOptions = {
4040
depth: Infinity,
4141
maxArrayLength: Infinity,
4242
maxStringLength: Infinity,
@@ -428,6 +428,7 @@ function dateInspect(
428428
function inspect(output: unknown, options: FormatOptions): string {
429429
// Set a custom inspection function for 'Date' objects. Since we only want this
430430
// to affect mongosh scripts, we unset it later.
431+
const originalDateInspect = (Date.prototype as any)[util.inspect.custom];
431432
(Date.prototype as any)[util.inspect.custom] = dateInspect;
432433
try {
433434
return util.inspect(
@@ -443,6 +444,8 @@ function inspect(output: unknown, options: FormatOptions): string {
443444
);
444445
} finally {
445446
delete (Date.prototype as any)[util.inspect.custom];
447+
if (originalDateInspect)
448+
(Date.prototype as any)[util.inspect.custom] = originalDateInspect;
446449
}
447450
}
448451

packages/cli-repl/src/mongosh-repl.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import path from 'path';
1212
import type { Duplex } from 'stream';
1313
import { PassThrough } from 'stream';
1414
import type { StubbedInstance } from 'ts-sinon';
15-
import { stubInterface } from 'ts-sinon';
15+
import sinon, { stubInterface } from 'ts-sinon';
1616
import { inspect, promisify } from 'util';
1717
import {
1818
expect,
@@ -95,6 +95,7 @@ describe('MongoshNodeRepl', function () {
9595
},
9696
});
9797
sp.runCommandWithCheck.resolves({ ok: 1 });
98+
sp.find.resolves(sinon.stub());
9899

99100
if (process.env.USE_NEW_AUTOCOMPLETE) {
100101
sp.listCollections.resolves([{ name: 'coll' }]);

packages/cli-repl/src/mongosh-repl.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -956,7 +956,7 @@ class MongoshNodeRepl implements EvaluationListener {
956956
/**
957957
* Format the result to a string so it can be written to the output stream.
958958
*/
959-
writer(result: any): string {
959+
writer(result: any, extraFormatOptions?: Partial<FormatOptions>): string {
960960
// This checks for error instances.
961961
// The writer gets called immediately by the internal `repl.eval`
962962
// in case of errors.
@@ -976,7 +976,8 @@ class MongoshNodeRepl implements EvaluationListener {
976976
this.rawValueToShellResult.get(result) ?? {
977977
type: null,
978978
printable: result,
979-
}
979+
},
980+
extraFormatOptions
980981
);
981982
}
982983

packages/e2e-tests/test/e2e-bson.spec.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,4 +588,140 @@ describe('BSON e2e', function () {
588588
shell.assertNoErrors();
589589
});
590590
});
591+
describe('inspect nesting depth', function () {
592+
const deepAndNestedDefinition = `({
593+
a: { b: { c: { d: { e: { f: { g: { h: "foundme" } } } } } } },
594+
array: [...Array(100000).keys()].map(i => ({ num: i })),
595+
str: 'All work and no playmakes Jack a dull boy'.repeat(4096) + 'The End'
596+
})`;
597+
const checkForDeepOutput = (output: string, wantFullOutput: boolean) => {
598+
if (wantFullOutput) {
599+
expect(output).not.to.include('[Object');
600+
expect(output).not.to.include('more items');
601+
expect(output).to.include('foundme');
602+
expect(output).to.include('num: 99999');
603+
expect(output).to.include('The End');
604+
} else {
605+
expect(output).to.include('[Object');
606+
expect(output).to.include('more items');
607+
expect(output).not.to.include('foundme');
608+
expect(output).not.to.include('num: 99999');
609+
expect(output).not.to.include('The End');
610+
}
611+
};
612+
613+
beforeEach(async function () {
614+
await shell.executeLine(`use ${dbName}`);
615+
await shell.executeLine(`deepAndNested = ${deepAndNestedDefinition}`);
616+
await shell.executeLine(`db.coll.insertOne(deepAndNested)`);
617+
});
618+
619+
it('inspects a full bson document when it is read from the server (interactive mode)', async function () {
620+
// Deeply nested object from the server should be fully printed
621+
const output = await shell.executeLine('db.coll.findOne()');
622+
checkForDeepOutput(output, true);
623+
// Same object doesn't need to be fully printed if created by the user
624+
const output2 = await shell.executeLine('deepAndNested');
625+
checkForDeepOutput(output2, false);
626+
shell.assertNoErrors();
627+
});
628+
629+
it('can explicitly disable full-depth nesting (interactive mode)', async function () {
630+
shell.kill();
631+
shell = this.startTestShell({
632+
args: [await testServer.connectionString(), '--deepInspect=false'],
633+
});
634+
await shell.waitForPrompt();
635+
await shell.executeLine(`use ${dbName}`);
636+
const output = await shell.executeLine('db.coll.findOne()');
637+
checkForDeepOutput(output, false);
638+
shell.assertNoErrors();
639+
});
640+
641+
it('does not deeply inspect objects in non-interactive mode for intermediate output', async function () {
642+
shell.kill();
643+
shell = this.startTestShell({
644+
args: [
645+
await testServer.connectionString(),
646+
'--eval',
647+
`use(${JSON.stringify(dbName)}); print(db.coll.findOne()); 0`,
648+
],
649+
});
650+
checkForDeepOutput(await shell.waitForCleanOutput(), false);
651+
shell = this.startTestShell({
652+
args: [
653+
await testServer.connectionString(),
654+
'--eval',
655+
`print(${deepAndNestedDefinition}); 0`,
656+
],
657+
});
658+
checkForDeepOutput(await shell.waitForCleanOutput(), false);
659+
});
660+
661+
it('inspect full objects in non-interactive mode for final output', async function () {
662+
shell.kill();
663+
shell = this.startTestShell({
664+
args: [
665+
await testServer.connectionString(),
666+
'--eval',
667+
`use(${JSON.stringify(dbName)}); db.coll.findOne();`,
668+
],
669+
});
670+
checkForDeepOutput(await shell.waitForCleanOutput(), true);
671+
shell = this.startTestShell({
672+
args: [
673+
await testServer.connectionString(),
674+
'--eval',
675+
deepAndNestedDefinition,
676+
],
677+
});
678+
checkForDeepOutput(await shell.waitForCleanOutput(), true);
679+
});
680+
681+
it('can explicitly disable full-depth nesting (non-interactive mode)', async function () {
682+
shell.kill();
683+
shell = this.startTestShell({
684+
args: [
685+
await testServer.connectionString(),
686+
'--deepInspect=false',
687+
'--eval',
688+
`use(${JSON.stringify(dbName)}); db.coll.findOne();`,
689+
],
690+
});
691+
checkForDeepOutput(await shell.waitForCleanOutput(), false);
692+
shell = this.startTestShell({
693+
args: [
694+
await testServer.connectionString(),
695+
'--deepInspect=false',
696+
'--eval',
697+
deepAndNestedDefinition,
698+
],
699+
});
700+
checkForDeepOutput(await shell.waitForCleanOutput(), false);
701+
});
702+
703+
it('can parse serverStatus back to its original form', async function () {
704+
// Dates get special treatment but that doesn't currently apply
705+
// to mongosh's util.inspect that's available to users
706+
// (although maybe it should?).
707+
await shell.executeLine(
708+
`Date.prototype[Symbol.for('nodejs.util.inspect.custom')] = function(){ return 'ISODate("' + this.toISOString() + '")'; };`
709+
);
710+
// 'void 0' to avoid large output in the shell from serverStatus
711+
await shell.executeLine(
712+
'A = db.adminCommand({ serverStatus: 1 }); void 0'
713+
);
714+
await shell.executeLine('util.inspect(A)');
715+
await shell.executeLine(`B = eval('(' + util.inspect(A) + ')'); void 0`);
716+
shell.assertNoErrors();
717+
const output1 = await shell.executeLineWithJSONResult('A', {
718+
parseAsEJSON: false,
719+
});
720+
const output2 = await shell.executeLineWithJSONResult('B', {
721+
parseAsEJSON: false,
722+
});
723+
expect(output1).to.deep.equal(output2);
724+
shell.assertNoErrors();
725+
});
726+
});
591727
});

packages/e2e-tests/test/e2e-oidc.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ describe('OIDC auth e2e', function () {
374374

375375
// Internal hack to get a state-share server as e.g. Compass or the VSCode extension would
376376
let handle = await shell.executeLine(
377-
'db.getMongo()._serviceProvider.currentClientOptions.parentState.getStateShareServer()'
377+
'db.getMongo()._serviceProvider[Symbol.for("@@mongosh.originalServiceProvider")].currentClientOptions.parentState.getStateShareServer()'
378378
);
379379
// `handle` can include the next prompt when returned by `shell.executeLine()`,
380380
// so look for the longest prefix of it that is valid JSON.

packages/e2e-tests/test/test-shell.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,17 @@ export class TestShell {
302302
return this._output.slice(previousOutputLength);
303303
}
304304

305-
async executeLineWithJSONResult(line: string): Promise<any> {
305+
async executeLineWithJSONResult(
306+
line: string,
307+
{ parseAsEJSON = true } = {}
308+
): Promise<any> {
306309
const output = await this.executeLine(
307310
`">>>>>>" + EJSON.stringify(${line}, {relaxed:false}) + "<<<<<<"`
308311
);
309312
const matching = output.match(/>>>>>>(.+)<<<<<</)?.[1];
310313
if (!matching)
311314
throw new Error(`Could not parse output from line: '${output}'`);
312-
return EJSON.parse(matching);
315+
return (parseAsEJSON ? EJSON : JSON).parse(matching);
313316
}
314317

315318
assertNoErrors(): void {

0 commit comments

Comments
 (0)