├── .gitignore ├── src ├── commands │ ├── dir │ │ ├── dir.types.ts │ │ ├── dir.builder.ts │ │ └── dir.executor.ts │ └── ffmpeg │ │ ├── ffmpeg.types.ts │ │ ├── ffmpeg.builder.ts │ │ └── ffmpeg.executor.ts ├── core │ ├── prompt │ │ ├── prompt.types.ts │ │ └── prompt.service.ts │ ├── executor │ │ ├── command.types.ts │ │ └── command.executor.ts │ ├── handlers │ │ ├── stream-logger.interface.ts │ │ └── stream.handler.ts │ └── files │ │ └── file.service.ts ├── app.ts └── out │ └── console-logger │ └── console-logger.ts ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules -------------------------------------------------------------------------------- /src/commands/dir/dir.types.ts: -------------------------------------------------------------------------------- 1 | export interface DirInput { 2 | path: string; 3 | } -------------------------------------------------------------------------------- /src/core/prompt/prompt.types.ts: -------------------------------------------------------------------------------- 1 | export type PromptType = 'input' | 'number' | 'password'; -------------------------------------------------------------------------------- /src/core/executor/command.types.ts: -------------------------------------------------------------------------------- 1 | export interface ICommandExec { 2 | command: string; 3 | args: string[]; 4 | } -------------------------------------------------------------------------------- /src/core/handlers/stream-logger.interface.ts: -------------------------------------------------------------------------------- 1 | export interface IStreamLogger { 2 | log(...args: any[]): void; 3 | error(...args: any[]): void; 4 | end(): void; 5 | } -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { DirExecuter } from './commands/dir/dir.executor'; 2 | import { ConsoleLogger } from './out/console-logger/console-logger'; 3 | 4 | export class App { 5 | async run() { 6 | new DirExecuter(ConsoleLogger.getInstance()).execute(); 7 | } 8 | } 9 | 10 | const app = new App(); 11 | app.run(); -------------------------------------------------------------------------------- /src/commands/ffmpeg/ffmpeg.types.ts: -------------------------------------------------------------------------------- 1 | import { ICommandExec } from '../../core/executor/command.types'; 2 | 3 | export interface IFfmpegInput { 4 | width: number; 5 | height: number; 6 | path: string; 7 | name: string; 8 | } 9 | 10 | export interface ICommandExecFfmpeg extends ICommandExec { 11 | output: string; 12 | } -------------------------------------------------------------------------------- /src/core/prompt/prompt.service.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import { PromptType } from './prompt.types'; 3 | 4 | export class PromptService { 5 | public async input(message: string, type: PromptType) { 6 | const { result } = await inquirer.prompt<{ result: T }>([ 7 | { 8 | type, 9 | name: 'result', 10 | message 11 | } 12 | ]); 13 | return result; 14 | } 15 | } -------------------------------------------------------------------------------- /src/commands/dir/dir.builder.ts: -------------------------------------------------------------------------------- 1 | export class DirBuilder { 2 | private options: Map = new Map(); 3 | 4 | detailedOutput() { 5 | this.options.set('-l', ''); 6 | return this; 7 | } 8 | 9 | output(): string[] { 10 | const args: string[] = []; 11 | this.options.forEach((value, key) => { 12 | args.push(key); 13 | args.push(value); 14 | }) 15 | return args; 16 | } 17 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "command-executer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Anton Larichev", 10 | "license": "ISC", 11 | "dependencies": { 12 | "inquirer": "^8.2.0" 13 | }, 14 | "devDependencies": { 15 | "@types/inquirer": "^8.2.0", 16 | "@types/node": "^17.0.14" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/out/console-logger/console-logger.ts: -------------------------------------------------------------------------------- 1 | import { IStreamLogger } from '../../core/handlers/stream-logger.interface'; 2 | 3 | export class ConsoleLogger implements IStreamLogger { 4 | private static logger: ConsoleLogger; 5 | public static getInstance() { 6 | if (!ConsoleLogger.logger) { 7 | ConsoleLogger.logger = new ConsoleLogger(); 8 | } 9 | return ConsoleLogger.logger; 10 | } 11 | 12 | log(...args: any[]): void { 13 | console.log(...args); 14 | } 15 | error(...args: any[]): void { 16 | console.log(...args); 17 | } 18 | end(): void { 19 | console.log('Готово'); 20 | } 21 | } -------------------------------------------------------------------------------- /src/core/handlers/stream.handler.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcessWithoutNullStreams } from 'child_process'; 2 | import { IStreamLogger } from './stream-logger.interface'; 3 | 4 | export class StreamHandler { 5 | constructor(private logger: IStreamLogger) { } 6 | 7 | processOutput(stream: ChildProcessWithoutNullStreams) { 8 | stream.stdout.on('data', (data: any) => { 9 | this.logger.log(data.toString()); 10 | }); 11 | 12 | stream.stderr.on('data', (data: any) => { 13 | this.logger.error(data.toString()); 14 | }); 15 | 16 | stream.on('close', () => { 17 | this.logger.end(); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /src/core/files/file.service.ts: -------------------------------------------------------------------------------- 1 | import { promises } from 'fs'; 2 | import { dirname, isAbsolute, join } from 'path'; 3 | 4 | export class FileService { 5 | private async isExist(path: string) { 6 | try { 7 | await promises.stat(path); 8 | return true; 9 | } catch { 10 | return false; 11 | } 12 | } 13 | 14 | public getFilePath(path: string, name: string, ext: string): string { 15 | if (!isAbsolute(path)) { 16 | path = join(__dirname + '/' + path); 17 | } 18 | return join(dirname(path) + '/' + name + '.' + ext); 19 | } 20 | 21 | async deleteFileIfExists(path: string) { 22 | if (await this.isExist(path)) { 23 | promises.unlink(path); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/commands/ffmpeg/ffmpeg.builder.ts: -------------------------------------------------------------------------------- 1 | export class FfmpegBuilder { 2 | private inputPath: string; 3 | private options: Map = new Map(); 4 | 5 | constructor() { 6 | this.options.set('-c:v', 'libx264'); 7 | } 8 | 9 | input(inputPath: string): this { 10 | this.inputPath = inputPath; 11 | return this; 12 | } 13 | 14 | setVideoSize(width: number, height: number): this { 15 | this.options.set('-s', `${width}x${height}`); 16 | return this; 17 | } 18 | 19 | output(outputPath: string): string[] { 20 | if (!this.inputPath) { 21 | throw new Error('Не задан параметр input'); 22 | } 23 | const args: string[] = ['-i', this.inputPath]; 24 | this.options.forEach((value, key) => { 25 | args.push(key); 26 | args.push(value); 27 | }); 28 | args.push(outputPath); 29 | return args; 30 | } 31 | } -------------------------------------------------------------------------------- /src/core/executor/command.executor.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcessWithoutNullStreams } from 'child_process'; 2 | import { IStreamLogger } from '../handlers/stream-logger.interface'; 3 | import { ICommandExec } from './command.types'; 4 | 5 | export abstract class CommandExecutor { 6 | constructor(private logger: IStreamLogger) { } 7 | 8 | public async execute() { 9 | const input = await this.prompt(); 10 | const command = this.build(input); 11 | const stream = this.spawn(command); 12 | this.processStream(stream, this.logger); 13 | } 14 | 15 | protected abstract prompt(): Promise; 16 | protected abstract build(input: Input): ICommandExec; 17 | protected abstract spawn(command: ICommandExec): ChildProcessWithoutNullStreams; 18 | protected abstract processStream(stream: ChildProcessWithoutNullStreams, logger: IStreamLogger): void; 19 | } -------------------------------------------------------------------------------- /src/commands/dir/dir.executor.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; 2 | import { CommandExecutor } from '../../core/executor/command.executor'; 3 | import { ICommandExec } from '../../core/executor/command.types'; 4 | import { IStreamLogger } from '../../core/handlers/stream-logger.interface'; 5 | import { StreamHandler } from '../../core/handlers/stream.handler'; 6 | import { PromptService } from '../../core/prompt/prompt.service'; 7 | import { DirBuilder } from './dir.builder'; 8 | import { DirInput } from './dir.types'; 9 | 10 | export class DirExecuter extends CommandExecutor { 11 | private promptService: PromptService = new PromptService() 12 | constructor( 13 | logger: IStreamLogger, 14 | ) { 15 | super(logger); 16 | } 17 | 18 | protected async prompt(): Promise { 19 | let path = await this.promptService.input('Путь', 'input'); 20 | return { path }; 21 | } 22 | protected build({ path }: DirInput): ICommandExec { 23 | const args = (new DirBuilder()) 24 | .detailedOutput() 25 | .output(); 26 | return { command: 'ls', args: args.concat(path) }; 27 | } 28 | protected spawn({ command: commmand, args }: ICommandExec): ChildProcessWithoutNullStreams { 29 | return spawn(commmand, args); 30 | } 31 | protected processStream(stream: ChildProcessWithoutNullStreams, output: IStreamLogger): void { 32 | const handler = new StreamHandler(output); 33 | handler.processOutput(stream); 34 | } 35 | } -------------------------------------------------------------------------------- /src/commands/ffmpeg/ffmpeg.executor.ts: -------------------------------------------------------------------------------- 1 | import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; 2 | import { CommandExecutor } from '../../core/executor/command.executor'; 3 | import { FileService } from '../../core/files/file.service'; 4 | import { IStreamLogger } from '../../core/handlers/stream-logger.interface'; 5 | import { StreamHandler } from '../../core/handlers/stream.handler'; 6 | import { PromptService } from '../../core/prompt/prompt.service'; 7 | import { FfmpegBuilder } from './ffmpeg.builder'; 8 | import { ICommandExecFfmpeg, IFfmpegInput } from './ffmpeg.types'; 9 | 10 | export class FfmpegExecutor extends CommandExecutor { 11 | private fileService: FileService = new FileService(); 12 | private promptService: PromptService = new PromptService(); 13 | 14 | constructor(logger: IStreamLogger) { 15 | super(logger); 16 | } 17 | 18 | protected async prompt(): Promise { 19 | const width = await this.promptService.input('Ширина', 'number'); 20 | const height = await this.promptService.input('Высота', 'number'); 21 | const path = await this.promptService.input('Путь до файла', 'input'); 22 | const name = await this.promptService.input('Имя', 'input'); 23 | return { width, height, path, name }; 24 | } 25 | 26 | protected build({ width, height, path, name }: IFfmpegInput): ICommandExecFfmpeg { 27 | const output = this.fileService.getFilePath(path, name, 'mp4'); 28 | const args = (new FfmpegBuilder) 29 | .input(path) 30 | .setVideoSize(width, height) 31 | .output(output); 32 | return { command: 'ffmpeg', args, output }; 33 | } 34 | 35 | protected spawn({ output, command: commmand, args }: ICommandExecFfmpeg): ChildProcessWithoutNullStreams { 36 | this.fileService.deleteFileIfExists(output); 37 | return spawn(commmand, args); 38 | } 39 | 40 | protected processStream(stream: ChildProcessWithoutNullStreams, logger: IStreamLogger): void { 41 | const handler = new StreamHandler(logger); 42 | handler.processOutput(stream); 43 | } 44 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | /* Projects */ 5 | // "incremental": true, /* Enable incremental compilation */ 6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 7 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 11 | /* Language and Environment */ 12 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 13 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 14 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 15 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 16 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 17 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 18 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 19 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 20 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 21 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 22 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 23 | /* Modules */ 24 | "module": "commonjs", /* Specify what module code is generated. */ 25 | "rootDir": "./src", /* Specify the root folder within your source files. */ 26 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 27 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 28 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 29 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 30 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 31 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 32 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 33 | // "resolveJsonModule": true, /* Enable importing .json files */ 34 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 35 | /* JavaScript Support */ 36 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 37 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 38 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 39 | /* Emit */ 40 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 41 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 42 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 43 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 44 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 45 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 46 | // "removeComments": true, /* Disable emitting comments. */ 47 | // "noEmit": true, /* Disable emitting files from a compilation. */ 48 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 49 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 50 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 51 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 54 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 55 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 56 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 57 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 58 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 59 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 60 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 61 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 62 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 63 | /* Interop Constraints */ 64 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 65 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 66 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 67 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 68 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 69 | /* Type Checking */ 70 | "strict": true, /* Enable all strict type-checking options. */ 71 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 72 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 73 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 74 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 75 | "strictPropertyInitialization": false, /* Check for class properties that are declared but not set in the constructor. */ 76 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 77 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 78 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 79 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 80 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 81 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 82 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 83 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 84 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 85 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 86 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 87 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 88 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 89 | /* Completeness */ 90 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 91 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 92 | } 93 | } --------------------------------------------------------------------------------