Skip to content

Commit

Permalink
Merge branch 'cli-initial' of https://github.com/momentumframework/mo…
Browse files Browse the repository at this point in the history
…mentum into main
  • Loading branch information
lennykean committed Feb 15, 2021
2 parents 542c871 + a8d8026 commit f3ebd0f
Show file tree
Hide file tree
Showing 49 changed files with 1,770 additions and 1 deletion.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
Momentum is an open-source framework for building enterprise server-side Deno
applications in TypeScript. It provides the paradigms and design patterns to
guide developers to create robust, scalable, and enterprise-grade applications.

By focusing on a batteries-optional approach, Momentum provides a strong core
that is easily extendable into a rich developer experience via dependency
injection modules. While the framework is opinionated by design, this modular
Expand Down
11 changes: 11 additions & 0 deletions cli/cli/cli.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CommandsModule } from "../commands/commands.module.ts";
import { MvModule } from "../deps.ts";
import { CliService } from "./cli.service.ts";

@MvModule({
imports: [CommandsModule],
providers: [CliService],
exports: [CliService],
})
export class CliModule {
}
34 changes: 34 additions & 0 deletions cli/cli/cli.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Command, Inject, Injectable } from "../deps.ts";
import { FileIOService, MvfFile, MvfManagerService } from "../global/mod.ts";
import { MVF_COMMANDS } from "../tokens.ts";

@Injectable({ global: false })
export class CliService {
constructor(
@Inject(MVF_COMMANDS) private readonly commands: Command[],
private readonly mvfManager: MvfManagerService,
private readonly fileIOService: FileIOService,
) {
}

async startProgram() {
const program = new Command("mvf");

program.version(await this.getCurrentVersion());

program
.option("-v, --verbose", "enable verbose mode");

this.commands.forEach((c) => program.addCommand(c));

program.parse(Deno.args);
}

private async getCurrentVersion() {
const { mvfFileAbsolutePath } = await this.mvfManager
.getMvInstallationPaths();
const mvfFileContents = this.fileIOService.readFile(mvfFileAbsolutePath);
const mvfFile: MvfFile = JSON.parse(mvfFileContents);
return mvfFile.version;
}
}
5 changes: 5 additions & 0 deletions cli/commands/command-controller.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Command } from "../deps.ts";

export interface CommandController {
createCommand(): Command;
}
3 changes: 3 additions & 0 deletions cli/commands/command-handler.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface CommandHandler<T> {
handle(commandParameters: T): void | Promise<void>;
}
58 changes: 58 additions & 0 deletions cli/commands/commands.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { MvModule } from "../deps.ts";
import { MVF_COMMANDS } from "../tokens.ts";
import { CommandController } from "./command-controller.interface.ts";
import {
GenerateFileCommandController,
GenerateFileCommandModule,
} from "./generate-file/mod.ts";
import {
NewProjectCommandController,
NewProjectCommandModule,
} from "./new-project/mod.ts";
import {
ToolCommandController,
ToolCommandModule,
ToolManagerService,
} from "./tool/mod.ts";
import {
UpgradeCommandController,
UpgradeCommandModule,
} from "./upgrade/mod.ts";

@MvModule({
imports: [
GenerateFileCommandModule,
NewProjectCommandModule,
ToolCommandModule,
UpgradeCommandModule,
],
providers: [
{
provide: MVF_COMMANDS,
deps: [
ToolManagerService,
GenerateFileCommandController,
NewProjectCommandController,
ToolCommandController,
UpgradeCommandController,
],
useFactory: async (
toolManager: ToolManagerService,
...controllers: CommandController[]
) => {
const tools = await toolManager.createToolCommands();
const cliCommands = controllers.map((c) => c.createCommand());

return [
...cliCommands,
...tools,
];
},
},
],
exports: [
MVF_COMMANDS,
],
})
export class CommandsModule {
}
155 changes: 155 additions & 0 deletions cli/commands/generate-file/file-finder.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { Injectable } from "../../deps.ts";
import { FileInfo, FileIOService } from "../../global/mod.ts";
import { GenerateFileCommandParameters } from "./generate-file.command-parameters.ts";

@Injectable()
export class FileFinderService {
constructor(
private readonly fileIOService: FileIOService,
) {
}

getDestinationFile(
commandParameters: GenerateFileCommandParameters,
fileName: string,
): FileInfo {
if (commandParameters.schematicType === "module") {
let directory = this.fileIOService.getFileInfo(`src`);
if (!directory.exists) {
directory = this.fileIOService.getFileInfo(`../src`);
}
if (!directory.exists) {
directory = this.fileIOService.getFileInfo(`./`);
}
const moduleDirectory = this.fileIOService.getFileInfo(
this.fileIOService.joinPaths(
directory.pathRelativeToUserDirectory,
commandParameters.name,
),
);
if (!moduleDirectory.exists) {
this.fileIOService.createDirectory(
moduleDirectory.pathRelativeToUserDirectory,
);
}
return this.fileIOService.getFileInfo(
this.fileIOService.joinPaths(
moduleDirectory.pathRelativeToUserDirectory,
fileName,
),
);
}

let directory = this.fileIOService.getFileInfo(
`src/${commandParameters.name}`,
);
if (!directory.exists) {
directory = this.fileIOService.getFileInfo(`${commandParameters.name}`);
}
if (!directory.exists) {
directory = this.fileIOService.getFileInfo(`./`);
}
return this.fileIOService.getFileInfo(
this.fileIOService.joinPaths(
directory.pathRelativeToUserDirectory,
fileName,
),
);
}

getContainingModuleFile(
commandParameters: GenerateFileCommandParameters,
): FileInfo {
let moduleFile = this.searchForFileInfoByExactName(
commandParameters,
`${commandParameters.name}.module.ts`,
);
if (!moduleFile.exists) {
moduleFile = this.findFileInDirectoryEndingWith(
`${commandParameters.name}`,
".module.ts",
);
}
if (!moduleFile.exists) {
moduleFile = this.findFileInDirectoryEndingWith(`./`, ".module.ts");
}
if (!moduleFile.exists) {
moduleFile = this.findFileInDirectoryEndingWith(`../`, ".module.ts");
}
if (!moduleFile.exists) {
moduleFile = this.findFileInDirectoryEndingWith(`../../`, ".module.ts");
}
return moduleFile;
}

getDepsFile(commandParameters: GenerateFileCommandParameters): FileInfo {
return this.searchForFileInfoByExactName(commandParameters, "deps.ts");
}

getAppModuleFile(commandParameters: GenerateFileCommandParameters): FileInfo {
return this.searchForFileInfoByExactName(
commandParameters,
"app.module.ts",
);
}

private searchForFileInfoByExactName(
commandParameters: GenerateFileCommandParameters,
fileOrDirectoryName: string,
) {
let file = this.downwardFileInfoSearch(
commandParameters,
fileOrDirectoryName,
);
if (!file.exists) {
file = this.upwardFileInfoSearch(fileOrDirectoryName, 5);
}
return file;
}

private downwardFileInfoSearch(
commandParameters: GenerateFileCommandParameters,
fileOrDirectoryName: string,
) {
let file = this.fileIOService.getFileInfo(
`src/${commandParameters.name}/${fileOrDirectoryName}`,
);
if (!file.exists) {
file = this.fileIOService.getFileInfo(
`${commandParameters.name}/${fileOrDirectoryName}`,
);
}
if (!file.exists) {
file = this.fileIOService.getFileInfo(`src/${fileOrDirectoryName}`);
}
if (!file.exists) {
file = this.fileIOService.getFileInfo(`${fileOrDirectoryName}`);
}
return file;
}

private upwardFileInfoSearch(
fileOrDirectoryName: string,
maxIterations: number,
) {
const tree = ["./"];

for (let i = 0; i < maxIterations; i++) {
const file = this.fileIOService.getFileInfo(
this.fileIOService.joinPaths(...tree, fileOrDirectoryName),
);
if (file.exists) {
return file;
}
tree.push("../");
}

return new FileInfo();
}

private findFileInDirectoryEndingWith(directory: string, endsWith: string) {
const files = this.fileIOService.getDirectoryContents(directory);
const file = files.find((f) => f.name.endsWith(endsWith));
return file ? this.fileIOService.getFileInfo(file.name) : new FileInfo();
}
}
50 changes: 50 additions & 0 deletions cli/commands/generate-file/generate-file.command-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Command, Injectable } from "../../deps.ts";
import { CommandController } from "../command-controller.interface.ts";
import {
GenerateFileCommandParameters,
SchematicType,
} from "./generate-file.command-parameters.ts";
import { GenerateFileCommandHandler } from "./generate-file.command-handler.ts";
import { InjectableOptions } from "../../../di/mod.ts";

@Injectable({ global: false })
export class GenerateFileCommandController implements CommandController {
constructor(
private readonly commandHandler: GenerateFileCommandHandler,
) {
}

createCommand(): Command {
const command = new Command("generate");
command.description("Generates a new file from a schematic.");
command.alias("g");
command.arguments("<schematic> <name>");
command.option(
"-gl, --global [global]",
"Enable/disable global injection scope",
);
command.option(
"-si, --skip-import [skipImport]",
"Prevent auto-importing into the nearest module",
);
command.action(
(schematicType: string, name: string, command: Command) => {
const injectableOptions: InjectableOptions = {};
if (command.global === "false" || command.global === false) {
injectableOptions.global = false;
}

const commandParameters = new GenerateFileCommandParameters({
providedSchematic: schematicType,
providedName: name,
injectableOptions,
skipImport: command.skipImport === "true" ||
command.skipImport === true,
});

return this.commandHandler.handle(commandParameters);
},
);
return command;
}
}
67 changes: 67 additions & 0 deletions cli/commands/generate-file/generate-file.command-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Injectable } from "../../deps.ts";
import { FileIOService } from "../../global/mod.ts";
import { GenerateFileCommandParameters } from "./generate-file.command-parameters.ts";
import { SchematicsService } from "./schematics.service.ts";
import { TemplateApplicatorService } from "./template-applicator.service.ts";
import { CommandHandler } from "../command-handler.interface.ts";
import { FileFinderService } from "./file-finder.service.ts";

@Injectable({ global: false })
export class GenerateFileCommandHandler
implements CommandHandler<GenerateFileCommandParameters> {
constructor(
private readonly schematicsService: SchematicsService,
private readonly templateApplicator: TemplateApplicatorService,
private readonly fileFinderService: FileFinderService,
) {
}

async handle(commandParameters: GenerateFileCommandParameters) {
const { schematicFileContents, schematicFileName } = this.schematicsService
.getSchematicDetails(commandParameters.schematicType);

const generatedFileName = this.templateApplicator
.applySchematicNameTemplating(commandParameters, schematicFileName);

commandParameters.files.destinationFile = this.fileFinderService
.getDestinationFile(commandParameters, generatedFileName);

if (commandParameters.files.destinationFile.exists) {
throw new Error(
`Could not generate: File found at ${commandParameters.files.destinationFile.pathAbsolute}`,
);
}

commandParameters.files.depsFile = this.fileFinderService.getDepsFile(
commandParameters,
);
commandParameters.files.appModuleFile = this.fileFinderService
.getAppModuleFile(commandParameters);
commandParameters.files.containingModuleFile = this.fileFinderService
.getContainingModuleFile(commandParameters);

const generatedFileContents = this.templateApplicator
.applySchematicTemplating(commandParameters, schematicFileContents);

await this.templateApplicator.writeGeneratedFile(
commandParameters,
generatedFileContents,
);

if (commandParameters.schematicType === "controller") {
await this.templateApplicator.addGeneratedFileToModule(
commandParameters,
);
} else if (commandParameters.schematicType === "service") {
if (!commandParameters.skipImport && !commandParameters.isGlobalService) {
await this.templateApplicator.addGeneratedFileToModule(
commandParameters,
);
}
} else if (commandParameters.schematicType === "module") {
await this.templateApplicator.addGeneratedFileToModule(
commandParameters,
);
}
}
}
Loading

0 comments on commit f3ebd0f

Please sign in to comment.