Skip to content

Commit

Permalink
feat(core): drop globalThis usage from ReplContext
Browse files Browse the repository at this point in the history
  • Loading branch information
micalevisk committed Jun 4, 2022
1 parent a2732a4 commit f988bd9
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 62 deletions.
5 changes: 1 addition & 4 deletions integration/repl/e2e/repl.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import { expect } from 'chai';
import * as sinon from 'sinon';
import { AppModule } from '../src/app.module';
import { UsersModule } from '../src/users/users.module';

const PROMPT = '\u001b[1G\u001b[0J> \u001b[3G';

Expand All @@ -28,13 +27,11 @@ describe('REPL', () => {
});
afterEach(() => {
sinon.restore();
delete globalThis[AppModule.name];
delete globalThis[UsersModule.name];
});

it('get()', async () => {
const server = await repl(AppModule);

server.context
let outputText = '';
sinon.stub(process.stdout, 'write').callsFake(text => {
outputText += text;
Expand Down
33 changes: 0 additions & 33 deletions packages/core/repl/load-native-functions-into-context.ts

This file was deleted.

76 changes: 54 additions & 22 deletions packages/core/repl/repl-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
ResolveReplFn,
SelectReplFn,
} from './native-functions';
import { ReplFunction } from './repl-function';
import type { ReplFunctionClass } from './repl.interfaces';

type ModuleKey = string;
Expand All @@ -19,9 +20,12 @@ export type ModuleDebugEntry = {
providers: Record<string, InjectionToken>;
};

type ReplScope = Record<string, any>;

export class ReplContext {
public readonly logger = new Logger(ReplContext.name);
public debugRegistry: Record<ModuleKey, ModuleDebugEntry> = {};
public readonly globalScope: ReplScope = Object.create(null);
public readonly nativeFunctions = new Map<
string,
InstanceType<ReplFunctionClass>
Expand All @@ -33,6 +37,7 @@ export class ReplContext {
nativeFunctionsClassRefs?: ReplFunctionClass[],
) {
this.container = (app as any).container; // Using `any` because `app.container` is not public.

this.initializeContext();
this.initializeNativeFunctions(nativeFunctionsClassRefs || []);
}
Expand All @@ -41,23 +46,6 @@ export class ReplContext {
process.stdout.write(text);
}

public addNativeFunction(NativeFunction: ReplFunctionClass): void {
const nativeFunction = new NativeFunction(this);

this.nativeFunctions.set(nativeFunction.fnDefinition.name, nativeFunction);

nativeFunction.fnDefinition.aliases?.forEach(aliaseName => {
const aliasNativeFunction: InstanceType<ReplFunctionClass> =
Object.create(nativeFunction);
aliasNativeFunction.fnDefinition = {
name: aliaseName,
description: aliasNativeFunction.fnDefinition.description,
signature: aliasNativeFunction.fnDefinition.signature,
};
this.nativeFunctions.set(aliaseName, aliasNativeFunction);
});
}

private initializeContext() {
const modules = this.container.getModules();

Expand All @@ -66,17 +54,18 @@ export class ReplContext {
if (moduleName === InternalCoreModule.name) {
return;
}
if (globalThis[moduleName]) {
if (this.globalScope[moduleName]) {
moduleName += ` (${moduleRef.token})`;
}

this.introspectCollection(moduleRef, moduleName, 'providers');
this.introspectCollection(moduleRef, moduleName, 'controllers');

// For in REPL auto-complete functionality
Object.defineProperty(globalThis, moduleName, {
Object.defineProperty(this.globalScope, moduleName, {
value: moduleRef.metatype,
configurable: false,
enumerable: true,
});
});
}
Expand All @@ -92,14 +81,15 @@ export class ReplContext {
if (
stringifiedToken === ApplicationConfig.name ||
stringifiedToken === moduleRef.metatype.name ||
globalThis[stringifiedToken]
this.globalScope[stringifiedToken]
) {
return;
}
// For in REPL auto-complete functionality
Object.defineProperty(globalThis, stringifiedToken, {
Object.defineProperty(this.globalScope, stringifiedToken, {
value: token,
configurable: false,
enumerable: false,
});

if (stringifiedToken === ModuleRef.name) {
Expand All @@ -122,6 +112,47 @@ export class ReplContext {
: token;
}

private addNativeFunction(
NativeFunctionRef: ReplFunctionClass,
): InstanceType<ReplFunctionClass> {
const nativeFunction = new NativeFunctionRef(this);

this.nativeFunctions.set(nativeFunction.fnDefinition.name, nativeFunction);

nativeFunction.fnDefinition.aliases?.forEach(aliaseName => {
const aliasNativeFunction: InstanceType<ReplFunctionClass> =
Object.create(nativeFunction);
aliasNativeFunction.fnDefinition = {
name: aliaseName,
description: aliasNativeFunction.fnDefinition.description,
signature: aliasNativeFunction.fnDefinition.signature,
};
this.nativeFunctions.set(aliaseName, aliasNativeFunction);
});

return nativeFunction;
}

private registerFunctionIntoGlobalScope(
nativeFunction: InstanceType<ReplFunctionClass>,
) {
// Bind the method to REPL's context:
this.globalScope[nativeFunction.fnDefinition.name] =
nativeFunction.action.bind(nativeFunction);

// Load the help trigger as a `help` getter on each native function:
const functionBoundRef: ReplFunction['action'] =
this.globalScope[nativeFunction.fnDefinition.name];
Object.defineProperty(functionBoundRef, 'help', {
enumerable: false,
configurable: false,
get: () =>
// Dynamically builds the help message as will unlikely to be called
// several times.
this.writeToStdout(nativeFunction.makeHelpMessage()),
});
}

private initializeNativeFunctions(
nativeFunctionsClassRefs: ReplFunctionClass[],
): void {
Expand All @@ -137,7 +168,8 @@ export class ReplContext {
builtInFunctionsClassRefs
.concat(nativeFunctionsClassRefs)
.forEach(NativeFunction => {
this.addNativeFunction(NativeFunction);
const nativeFunction = this.addNativeFunction(NativeFunction);
this.registerFunctionIntoGlobalScope(nativeFunction);
});
}
}
14 changes: 11 additions & 3 deletions packages/core/repl/repl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import * as _repl from 'repl';
import { clc } from '@nestjs/common/utils/cli-colors.util';
import { NestFactory } from '../nest-factory';
import { REPL_INITIALIZED_MESSAGE } from './constants';
import { loadNativeFunctionsIntoContext } from './load-native-functions-into-context';
import { ReplContext } from './repl-context';
import { ReplLogger } from './repl-logger';

function copyInto(target, source): void {
Object.defineProperties(
target,
Object.keys(source).reduce((descriptors, key) => {
descriptors[key] = Object.getOwnPropertyDescriptor(source, key);
return descriptors;
}, Object.create(null)),
);
}

export async function repl(module: Type) {
const app = await NestFactory.create(module, {
abortOnError: false,
Expand All @@ -21,8 +30,7 @@ export async function repl(module: Type) {
prompt: clc.green('> '),
ignoreUndefined: true,
});

loadNativeFunctionsIntoContext(replServer.context, replContext);
copyInto(replServer.context, replContext.globalScope);

return replServer;
}

0 comments on commit f988bd9

Please sign in to comment.