Skip to content

Commit a465032

Browse files
committed
feat(snippet-manager): add initial snippet manager implementation
1 parent 29764c3 commit a465032

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+2436
-579
lines changed

packages/cli-repl/package-lock.json

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

packages/cli-repl/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@mongosh/service-provider-server": "0.0.0-dev.0",
4848
"@mongosh/shell-api": "0.0.0-dev.0",
4949
"@mongosh/shell-evaluator": "0.0.0-dev.0",
50+
"@mongosh/snippet-manager": "0.0.0-dev.0",
5051
"@mongosh/types": "0.0.0-dev.0",
5152
"analytics-node": "^3.4.0-beta.1",
5253
"ansi-escape-sequences": "^5.1.2",

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Duplex, PassThrough } from 'stream';
77
import { promisify } from 'util';
88
import { MongodSetup, skipIfServerVersion, startTestServer } from '../../../testing/integration-testing-hooks';
99
import { expect, fakeTTYProps, readReplLogfile, tick, useTmpdir, waitBus, waitCompletion, waitEval } from '../test/repl-helpers';
10-
import { eventually } from '../test/helpers';
10+
import { eventually } from '../../../testing/eventually';
1111
import CliRepl, { CliReplOptions } from './cli-repl';
1212
import { CliReplErrors } from './error-codes';
1313

@@ -181,7 +181,16 @@ describe('CliRepl', () => {
181181

182182
it('returns the list of available config options when asked to', () => {
183183
expect(cliRepl.listConfigOptions()).to.deep.equal([
184-
'batchSize', 'enableTelemetry', 'inspectCompact', 'inspectDepth', 'historyLength', 'showStackTraces', 'redactHistory'
184+
'batchSize',
185+
'enableTelemetry',
186+
'snippetIndexSourceURLs',
187+
'snippetRegistryURL',
188+
'snippetAutoload',
189+
'inspectCompact',
190+
'inspectDepth',
191+
'historyLength',
192+
'showStackTraces',
193+
'redactHistory'
185194
]);
186195
});
187196

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { redactCredentials } from '@mongosh/history';
33
import i18n from '@mongosh/i18n';
44
import { bson, AutoEncryptionOptions } from '@mongosh/service-provider-core';
55
import { CliOptions, CliServiceProvider, MongoClientOptions } from '@mongosh/service-provider-server';
6+
import { SnippetManager } from '@mongosh/snippet-manager';
67
import Analytics from 'analytics-node';
78
import askpassword from 'askpassword';
89
import Nanobus from 'nanobus';
@@ -119,6 +120,7 @@ class CliRepl {
119120
* @param {string} driverUri - The driver URI.
120121
* @param {MongoClientOptions} driverOptions - The driver options.
121122
*/
123+
// eslint-disable-next-line complexity
122124
async start(driverUri: string, driverOptions: MongoClientOptions): Promise<void> {
123125
const { version } = require('../package.json');
124126
await this.verifyNodeVersion();
@@ -170,6 +172,15 @@ class CliRepl {
170172

171173
const initialServiceProvider = await this.connect(driverUri, driverOptions);
172174
const initialized = await this.mongoshRepl.initialize(initialServiceProvider);
175+
176+
let snippetManager: SnippetManager | undefined;
177+
if (this.config.snippetIndexSourceURLs !== '') {
178+
snippetManager = new SnippetManager({
179+
installdir: this.shellHomeDirectory.roamingPath('snippets'),
180+
internalState: this.mongoshRepl.runtimeState().internalState
181+
});
182+
}
183+
173184
const commandLineLoadFiles = this.cliOptions.fileNames ?? [];
174185
if (commandLineLoadFiles.length > 0 || this.cliOptions.eval !== undefined) {
175186
this.mongoshRepl.setIsInteractive(!!this.cliOptions.shell);
@@ -182,6 +193,9 @@ class CliRepl {
182193
} else {
183194
this.mongoshRepl.setIsInteractive(true);
184195
}
196+
if (!this.cliOptions.norc) {
197+
await snippetManager?.loadAllSnippets();
198+
}
185199
await this.loadRcFiles();
186200
this.bus.emit('mongosh:start-mongosh-repl', { version });
187201
await this.mongoshRepl.startRepl(initialized);

packages/cli-repl/src/mongocryptd-manager.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { getMongocryptdPaths, MongocryptdManager } from './mongocryptd-manager';
66
import type { MongoshBus } from '@mongosh/types';
77
import { ShellHomeDirectory } from './config-directory';
88
import { startTestServer } from '../../../testing/integration-testing-hooks';
9-
import { eventually } from '../test/helpers';
9+
import { eventually } from '../../../testing/eventually';
1010
import { expect } from 'chai';
1111

1212
describe('getMongocryptdPaths', () => {

packages/cli-repl/src/setup-logger-and-telemetry.spec.ts

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -64,44 +64,77 @@ describe('setupLoggerAndTelemetry', () => {
6464
bus.emit('mongosh:mongoshrc-mongorc-warn');
6565
bus.emit('mongosh:eval-cli-script');
6666

67-
expect(logOutput).to.have.lengthOf(23);
68-
expect(logOutput[0].msg).to.match(/^mongosh:start-logging \{"version":".+","execPath":".+","isCompiledBinary":.+\}$/);
69-
expect(logOutput[1].msg).to.equal('mongosh:update-user {"enableTelemetry":false}');
70-
expect(logOutput[2].msg).to.match(/^mongosh:connect/);
71-
expect(logOutput[2].msg).to.match(/"session_id":"5fb3c20ee1507e894e5340f3"/);
72-
expect(logOutput[2].msg).to.match(/"userId":"53defe995fa47e6c13102d9d"/);
73-
expect(logOutput[2].msg).to.match(/"connectionUri":"mongodb:\/\/localhost\/"/);
74-
expect(logOutput[2].msg).to.match(/"is_localhost":true/);
75-
expect(logOutput[2].msg).to.match(/"is_atlas":false/);
76-
expect(logOutput[2].msg).to.match(/"node_version":"v12\.19\.0"/);
77-
expect(logOutput[3].type).to.equal('Error');
78-
expect(logOutput[3].msg).to.match(/meow/);
79-
expect(logOutput[4].msg).to.equal('mongosh:use {"db":"admin"}');
80-
expect(logOutput[5].msg).to.equal('mongosh:show {"method":"dbs"}');
81-
expect(logOutput[6].msg).to.equal('mongosh:update-user {"enableTelemetry":true}');
82-
expect(logOutput[7].msg).to.match(/^mongosh:connect/);
83-
expect(logOutput[8].type).to.equal('Error');
84-
expect(logOutput[8].msg).to.match(/meow/);
85-
expect(logOutput[9].msg).to.equal('mongosh:use {"db":"admin"}');
86-
expect(logOutput[10].msg).to.equal('mongosh:show {"method":"dbs"}');
87-
expect(logOutput[11].msg).to.equal('mongosh:setCtx {"method":"setCtx"}');
88-
expect(logOutput[12].msg).to.match(/^mongosh:api-call/);
89-
expect(logOutput[12].msg).to.match(/"db":"test-1603986682000"/);
90-
expect(logOutput[13].msg).to.match(/^mongosh:api-call/);
91-
expect(logOutput[13].msg).to.match(/"email":"<email>"/);
92-
expect(logOutput[14].msg).to.match(/^mongosh:evaluate-input/);
93-
expect(logOutput[14].msg).to.match(/"input":"1\+1"/);
94-
expect(logOutput[15].msg).to.match(/"version":"3.6.1"/);
95-
expect(logOutput[16].msg).to.equal('mongosh:start-loading-cli-scripts');
96-
expect(logOutput[17].msg).to.match(/^mongosh:api-load-file/);
97-
expect(logOutput[17].msg).to.match(/"nested":true/);
98-
expect(logOutput[17].msg).to.match(/"filename":"foobar.js"/);
99-
expect(logOutput[18].msg).to.equal('mongosh:start-mongosh-repl {"version":"1.0.0"}');
100-
expect(logOutput[19].msg).to.match(/"nested":false/);
101-
expect(logOutput[19].msg).to.match(/"filename":"foobar.js"/);
102-
expect(logOutput[20].msg).to.equal('mongosh:mongoshrc-load');
103-
expect(logOutput[21].msg).to.equal('mongosh:mongoshrc-mongorc-warn');
104-
expect(logOutput[22].msg).to.equal('mongosh:eval-cli-script');
67+
bus.emit('mongosh-snippets:loaded', { installdir: '/' });
68+
bus.emit('mongosh-snippets:npm-lookup', { existingVersion: 'v1.2.3' });
69+
bus.emit('mongosh-snippets:npm-lookup-stopped');
70+
bus.emit('mongosh-snippets:npm-download-failed', { npmMetadataURL: 'https://example.com' });
71+
bus.emit('mongosh-snippets:npm-download-active', { npmMetadataURL: 'https://example.com', npmTarballURL: 'https://example.net' });
72+
bus.emit('mongosh-snippets:fetch-index', { refreshMode: 'always' });
73+
bus.emit('mongosh-snippets:fetch-cache-invalid');
74+
bus.emit('mongosh-snippets:fetch-index-error', { action: 'fetch', url: 'https://localhost' });
75+
bus.emit('mongosh-snippets:fetch-index-done');
76+
bus.emit('mongosh-snippets:package-json-edit-error', { error: 'failed' });
77+
bus.emit('mongosh-snippets:spawn-child', { args: ['npm', 'install'] });
78+
bus.emit('mongosh-snippets:load-snippet', { source: 'load-all', name: 'foo' });
79+
bus.emit('mongosh-snippets:snippet-command', { args: ['install', 'foo'] });
80+
bus.emit('mongosh-snippets:transform-error', { error: 'failed', addition: 'oh no', name: 'foo' });
81+
82+
let i = 0;
83+
expect(logOutput[i++].msg).to.match(/^mongosh:start-logging \{"version":".+","execPath":".+","isCompiledBinary":.+\}$/);
84+
expect(logOutput[i++].msg).to.equal('mongosh:update-user {"enableTelemetry":false}');
85+
expect(logOutput[i].msg).to.match(/^mongosh:connect/);
86+
expect(logOutput[i].msg).to.match(/"session_id":"5fb3c20ee1507e894e5340f3"/);
87+
expect(logOutput[i].msg).to.match(/"userId":"53defe995fa47e6c13102d9d"/);
88+
expect(logOutput[i].msg).to.match(/"connectionUri":"mongodb:\/\/localhost\/"/);
89+
expect(logOutput[i].msg).to.match(/"is_localhost":true/);
90+
expect(logOutput[i].msg).to.match(/"is_atlas":false/);
91+
expect(logOutput[i++].msg).to.match(/"node_version":"v12\.19\.0"/);
92+
expect(logOutput[i].type).to.equal('Error');
93+
expect(logOutput[i++].msg).to.match(/meow/);
94+
expect(logOutput[i++].msg).to.equal('mongosh:use {"db":"admin"}');
95+
expect(logOutput[i++].msg).to.equal('mongosh:show {"method":"dbs"}');
96+
expect(logOutput[i++].msg).to.equal('mongosh:update-user {"enableTelemetry":true}');
97+
expect(logOutput[i++].msg).to.match(/^mongosh:connect/);
98+
expect(logOutput[i].type).to.equal('Error');
99+
expect(logOutput[i++].msg).to.match(/meow/);
100+
expect(logOutput[i++].msg).to.equal('mongosh:use {"db":"admin"}');
101+
expect(logOutput[i++].msg).to.equal('mongosh:show {"method":"dbs"}');
102+
expect(logOutput[i++].msg).to.equal('mongosh:setCtx {"method":"setCtx"}');
103+
expect(logOutput[i].msg).to.match(/^mongosh:api-call/);
104+
expect(logOutput[i++].msg).to.match(/"db":"test-1603986682000"/);
105+
expect(logOutput[i].msg).to.match(/^mongosh:api-call/);
106+
expect(logOutput[i++].msg).to.match(/"email":"<email>"/);
107+
expect(logOutput[i].msg).to.match(/^mongosh:evaluate-input/);
108+
expect(logOutput[i++].msg).to.match(/"input":"1\+1"/);
109+
expect(logOutput[i++].msg).to.match(/"version":"3.6.1"/);
110+
expect(logOutput[i++].msg).to.equal('mongosh:start-loading-cli-scripts');
111+
expect(logOutput[i].msg).to.match(/^mongosh:api-load-file/);
112+
expect(logOutput[i].msg).to.match(/"nested":true/);
113+
expect(logOutput[i++].msg).to.match(/"filename":"foobar.js"/);
114+
expect(logOutput[i++].msg).to.equal('mongosh:start-mongosh-repl {"version":"1.0.0"}');
115+
expect(logOutput[i].msg).to.match(/"nested":false/);
116+
expect(logOutput[i++].msg).to.match(/"filename":"foobar.js"/);
117+
expect(logOutput[i++].msg).to.equal('mongosh:mongoshrc-load');
118+
expect(logOutput[i++].msg).to.equal('mongosh:mongoshrc-mongorc-warn');
119+
expect(logOutput[i++].msg).to.equal('mongosh:eval-cli-script');
120+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:loaded {"installdir":"/"}');
121+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:npm-lookup {"existingVersion":"v1.2.3"}');
122+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:npm-lookup-stopped');
123+
expect(logOutput[i].msg).to.match(/^mongosh-snippets:npm-download-failed/);
124+
expect(logOutput[i++].msg).to.match(/"npmMetadataURL":"https:\/\/example.com"/);
125+
expect(logOutput[i].msg).to.match(/^mongosh-snippets:npm-download-active/);
126+
expect(logOutput[i].msg).to.match(/"npmMetadataURL":"https:\/\/example.com"/);
127+
expect(logOutput[i++].msg).to.match(/"npmTarballURL":"https:\/\/example.net"/);
128+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:fetch-index {"refreshMode":"always"}');
129+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:fetch-cache-invalid');
130+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:fetch-index-error {"action":"fetch","url":"https://localhost"}');
131+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:fetch-index-done');
132+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:package-json-edit-error {"error":"failed"}');
133+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:spawn-child {"args":["npm","install"]}');
134+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:load-snippet {"source":"load-all","name":"foo"}');
135+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:snippet-command {"args":["install","foo"]}');
136+
expect(logOutput[i++].msg).to.equal('mongosh-snippets:transform-error {"error":"failed","addition":"oh no","name":"foo"}');
137+
expect(i).to.equal(logOutput.length);
105138

106139

107140
const mongosh_version = require('../package.json').version;
@@ -226,6 +259,16 @@ describe('setupLoggerAndTelemetry', () => {
226259
},
227260
userId: '53defe995fa47e6c13102d9d'
228261
}
262+
],
263+
[
264+
'track',
265+
{
266+
userId: '53defe995fa47e6c13102d9d',
267+
event: 'Snippet Install',
268+
properties: {
269+
mongosh_version
270+
}
271+
}
229272
]
230273
]);
231274
});

packages/cli-repl/src/setup-logger-and-telemetry.ts

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,18 @@ import type {
1414
StartMongoshReplEvent,
1515
MongocryptdTrySpawnEvent,
1616
MongocryptdLogEvent,
17-
MongocryptdErrorEvent
17+
MongocryptdErrorEvent,
18+
SnippetsCommandEvent,
19+
SnippetsErrorEvent,
20+
SnippetsFetchIndexErrorEvent,
21+
SnippetsFetchIndexEvent,
22+
SnippetsLoadedEvent,
23+
SnippetsLoadSnippetEvent,
24+
SnippetsNpmDownloadActiveEvent,
25+
SnippetsNpmDownloadFailedEvent,
26+
SnippetsNpmLookupEvent,
27+
SnippetsRunNpmEvent,
28+
SnippetsTransformErrorEvent
1829
} from '@mongosh/types';
1930

2031
interface MongoshAnalytics {
@@ -241,13 +252,79 @@ export default function setupLoggerAndTelemetry(
241252
});
242253

243254
bus.on('mongosh:mongocryptd-error', function(ev: MongocryptdErrorEvent) {
244-
log.info('mongosh:mongocryptd-error', ev);
255+
log.error('mongosh:mongocryptd-error', ev);
245256
});
246257

247258
bus.on('mongosh:mongocryptd-log', function(ev: MongocryptdLogEvent) {
248259
log.info('mongosh:mongocryptd-log', ev);
249260
});
250261

262+
bus.on('mongosh-snippets:loaded', function(ev: SnippetsLoadedEvent) {
263+
log.info('mongosh-snippets:loaded', ev);
264+
});
265+
266+
bus.on('mongosh-snippets:npm-lookup', function(ev: SnippetsNpmLookupEvent) {
267+
log.info('mongosh-snippets:npm-lookup', ev);
268+
});
269+
270+
bus.on('mongosh-snippets:npm-lookup-stopped', function() {
271+
log.info('mongosh-snippets:npm-lookup-stopped');
272+
});
273+
274+
bus.on('mongosh-snippets:npm-download-failed', function(ev: SnippetsNpmDownloadFailedEvent) {
275+
log.error('mongosh-snippets:npm-download-failed', ev);
276+
});
277+
278+
bus.on('mongosh-snippets:npm-download-active', function(ev: SnippetsNpmDownloadActiveEvent) {
279+
log.info('mongosh-snippets:npm-download-active', ev);
280+
});
281+
282+
bus.on('mongosh-snippets:fetch-index', function(ev: SnippetsFetchIndexEvent) {
283+
log.info('mongosh-snippets:fetch-index', ev);
284+
});
285+
286+
bus.on('mongosh-snippets:fetch-cache-invalid', function() {
287+
log.info('mongosh-snippets:fetch-cache-invalid');
288+
});
289+
290+
bus.on('mongosh-snippets:fetch-index-error', function(ev: SnippetsFetchIndexErrorEvent) {
291+
log.error('mongosh-snippets:fetch-index-error', ev);
292+
});
293+
294+
bus.on('mongosh-snippets:fetch-index-done', function() {
295+
log.info('mongosh-snippets:fetch-index-done');
296+
});
297+
298+
bus.on('mongosh-snippets:package-json-edit-error', function(ev: SnippetsErrorEvent) {
299+
log.error('mongosh-snippets:package-json-edit-error', ev);
300+
});
301+
302+
bus.on('mongosh-snippets:spawn-child', function(ev: SnippetsRunNpmEvent) {
303+
log.info('mongosh-snippets:spawn-child', ev);
304+
});
305+
306+
bus.on('mongosh-snippets:load-snippet', function(ev: SnippetsLoadSnippetEvent) {
307+
log.info('mongosh-snippets:load-snippet', ev);
308+
});
309+
310+
bus.on('mongosh-snippets:snippet-command', function(ev: SnippetsCommandEvent) {
311+
log.info('mongosh-snippets:snippet-command', ev);
312+
313+
if (telemetry && ev.args[0] === 'install') {
314+
analytics.track({
315+
userId,
316+
event: 'Snippet Install',
317+
properties: {
318+
mongosh_version
319+
}
320+
});
321+
}
322+
});
323+
324+
bus.on('mongosh-snippets:transform-error', function(ev: SnippetsTransformErrorEvent) {
325+
log.info('mongosh-snippets:transform-error', ev);
326+
});
327+
251328
const deprecatedApiCalls = new Set<string>();
252329
bus.on('mongosh:deprecated-api-call', function(ev: ApiEvent) {
253330
deprecatedApiCalls.add(`${ev.class}#${ev.method}`);

packages/cli-repl/test/e2e-analytics.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from 'chai';
22
import { startTestCluster } from '../../../testing/integration-testing-hooks';
3-
import { eventually } from './helpers';
3+
import { eventually } from '../../../testing/eventually';
44
import { TestShell } from './test-shell';
55

66
describe('e2e Analytics', () => {

packages/cli-repl/test/e2e-auth.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { expect } from 'chai';
22
import {
33
MongoClient, MongoClientOptions
44
} from 'mongodb';
5-
import { eventually } from './helpers';
5+
import { eventually } from '../../../testing/eventually';
66
import { TestShell } from './test-shell';
77
import {
88
startTestServer

packages/cli-repl/test/e2e-bson.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import {
33
MongoClient
44
} from 'mongodb';
55
import { bson } from '@mongosh/service-provider-core';
6-
import { eventually } from './helpers';
76
import { TestShell } from './test-shell';
7+
import { eventually } from '../../../testing/eventually';
88
import {
99
startTestServer
1010
} from '../../../testing/integration-testing-hooks';

0 commit comments

Comments
 (0)