-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'cli-initial' of https://github.com/momentumframework/mo…
…mentum into main
- Loading branch information
Showing
49 changed files
with
1,770 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { Command } from "../deps.ts"; | ||
|
||
export interface CommandController { | ||
createCommand(): Command; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export interface CommandHandler<T> { | ||
handle(commandParameters: T): void | Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
50
cli/commands/generate-file/generate-file.command-controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
67
cli/commands/generate-file/generate-file.command-handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
); | ||
} | ||
} | ||
} |
Oops, something went wrong.