Skip to content

Commit

Permalink
refactor: rename lib folder
Browse files Browse the repository at this point in the history
  • Loading branch information
BioPhoton committed Jan 6, 2023
1 parent f6d4351 commit bc08cb2
Show file tree
Hide file tree
Showing 19 changed files with 582 additions and 0 deletions.
18 changes: 18 additions & 0 deletions packages/node-cli-testing/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
11 changes: 11 additions & 0 deletions packages/node-cli-testing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# packages-cli-testing

This library was generated with [Nx](https://nx.dev).

## Building

Run `nx build packages-cli-testing` to build the library.

## Running unit tests

Run `nx test packages-cli-testing` to execute the unit tests via [Jest](https://jestjs.io).
7 changes: 7 additions & 0 deletions packages/node-cli-testing/cli-project/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "../../../../node_modules/ng-packagr/package.schema.json",
"lib": {
"entryFile": "src/index.ts",
"flatModuleFile": "cli-testing-cli-project"
}
}
3 changes: 3 additions & 0 deletions packages/node-cli-testing/cli-project/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './lib/types';
export * from './lib/utils';
export * from './lib/cli';
194 changes: 194 additions & 0 deletions packages/node-cli-testing/cli-project/src/lib/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
import { dirname, join } from 'path';
import { CliProcess, FileOrFolderMap, ProcessParams, ProcessTestOptions, ProjectConfig } from './types';
import { ProcessOptions, PromptTestOptions, testProcessE2e, TestResult } from '../../../process/src/index';
import { deleteFileOrFolder, processParamsToParamsArray } from './utils';

/**
* A closure for the testProcessE2e function to seperate process configuration and testing config from test data.
*
* @param processOptions: passed directly to execa as options
*/
export function getCliProcess(processOptions: ProcessOptions, promptTestOptions: PromptTestOptions & ProcessTestOptions): CliProcess {
return {
exec: (processParams: ProcessParams = {}, userInput: string[] = []): Promise<TestResult> => {
const processOpts = [promptTestOptions.bin, ...processParamsToParamsArray(processParams)];
return testProcessE2e(processOpts, userInput, processOptions, promptTestOptions);
}
};
}

/**
* A helper class to manage an project structure for a yargs based CLI
*/
export class CliProject<RcConfig extends {}> {

/**
* A flag to add more detailed information as logs
*/
protected verbose: boolean = false;

/**
* The folder in which to execute the process
*/
public root: string = '';

/**
* The the binary to execute
*/
protected bin: string = '';

/**
* The process executing the CLI bin
*/
protected process: CliProcess = undefined as unknown as CliProcess;

/**
* Filenames to delete e.g. in project teardown
* All files are located from root
*/
protected deleteFiles: string[] = [];
/**
* Filenames to create e.g. in project setup
* All files are located from root
*/
protected createFiles: FileOrFolderMap = {};
/**
* Filenames to create e.g. in project setup
*/
protected rcFile: Record<string, RcConfig> = {};

constructor() {
}

/**
* freezes jest so we can read logs :)
* @param ms
*/
async wait(ms: number = 30000) {
await new Promise(r => setTimeout(r, ms));
}

logVerbose(...args: any): void {
this.verbose && console.log(...args);
}

tableVerbose(...args: any): void {
this.verbose && console.table(...args);
}

async _setup(cfg: ProjectConfig<RcConfig>): Promise<void> {
// global settings
this.verbose = Boolean(cfg.verbose);

// use configurations
this.root = cfg.root;
this.bin = cfg.bin;
cfg.delete && (this.deleteFiles = cfg.delete);
cfg.create && (this.createFiles = cfg.create);
cfg.rcFile && (this.rcFile = cfg.rcFile);

// handle default rcPath
if (Object.keys(this.rcFile).length > 0) {
Object.entries(this.rcFile).forEach(([rcName, rcContent]) => {
this.deleteFiles.push(rcName);
this.createFiles[rcName] = rcContent;
});
}

// remove duplicated paths
this.deleteFiles = Array.from(new Set(this.deleteFiles));

this.process = getCliProcess({
cwd: this.root,
env: cfg.env
}, { bin: this.bin });
}

/**
* @internal
* @protected
*
* Method to delete files generated during the CLI run
* Notice all files will get located from the project root
*/
deleteGeneratedFiles(): void {
deleteFileOrFolder((this.deleteFiles || [])
.map(file => join(this.root, file))
);
}

/**
* @internal
* @protected
*
* Method to create files needed during the CLI run
* Notice all files will get located from the project root
*/
createInitialFiles(): void {
const preparedPaths = Object.entries(this?.createFiles || {})
.map(entry => {
entry[0] = join(this.root, entry[0]);
return entry;
});
preparedPaths
.forEach(([file, content]) => {
const exists = existsSync(file);
if (exists) {
if (content !== undefined) {
rmSync(file);
this.logVerbose(`File ${file} got deleted as it already exists`);
}
}
if (content === undefined) {
!exists && mkdirSync(file, { recursive: true });
} else {
const dir = dirname(file);
if (!existsSync(dir)) {
this.logVerbose(`Created dir ${dir} to save ${file}`);
mkdirSync(dir);
}

switch (true) {
case file.endsWith('.png'):
case file.endsWith('.ico'):
content = Buffer.from(content + '', 'base64') as any;
break;
case file.endsWith('.json'):
content = JSON.stringify(content) as any;
break;
}

writeFileSync(file, content as any, 'utf8');
}
this.logVerbose(`File ${file} created`);
});
}

/**
* Set up the project. e.g. create files, start processes
*/
async setup(): Promise<void> {
this.createInitialFiles();
}


/**
* Teardown the project. e.g delete files, stop processes
*/
async teardown(): Promise<void> {
this.deleteGeneratedFiles();
}

/**
* Executes the CLI with given parameters and user input.
* See getCliProcess for details
*
* @param processParams
* @param userInput
*/
exec(processParams?: ProcessParams, userInput?: string[]): Promise<TestResult> {
return this.process.exec(processParams, userInput);
}

}
42 changes: 42 additions & 0 deletions packages/node-cli-testing/cli-project/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { PromptTestOptions, TestResult } from '../../../process/src/index';

export type ProcessTestOptions = {
bin: string
}

export type ProcessParams = {
// command placeholder
_?: string
} & Record<string, any>

export type ExecFn<T extends ProcessParams = ProcessParams> = (processParams?: T, userInput?: string[], promptOptions?: PromptTestOptions) => Promise<TestResult>;

export type CliProcess = {
exec: ExecFn
}

export type CliCommand = Record<`\$${string}`, ExecFn>;

export type Project = {
deleteGeneratedFiles: () => void,
createInitialFiles: () => void,
setup: () => void,
teardown: () => void,
exec: ExecFn,
} &
{
[value in keyof CliCommand]: ExecFn<any>
}

export type FileOrFolderMap = Record<string, string | {} | undefined>;
export type ProjectConfig<RcConfig extends {}> = {
verbose?: boolean,
root: string,
bin: string,
// the process env of the created process
env?: Record<string, string>,
// files
rcFile?: Record<string, RcConfig>,
delete?: string[],
create?: FileOrFolderMap;
}
47 changes: 47 additions & 0 deletions packages/node-cli-testing/cli-project/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as fs from 'fs';
import { ProcessParams } from './types';
import * as path from 'path';

export function getFolderContent(folders: string[]): string[] {
return folders.flatMap((d) => {
if (fs.existsSync(d)) {
const files = fs.readdirSync(d);
return files.map((f) => path.join(d, f));
}
return [d];
});
}

export function deleteFileOrFolder(files: string | string[]): void {
(typeof files === 'string' ? [files] : files).forEach((p) => {
if (fs.existsSync(p)) {
const stats = fs.lstatSync(p);
if(stats.isDirectory()) {
fs.rmdirSync(p, { recursive: true });
} else {
fs.rmSync(p);
}
// console.info(`Deleted file ${file}`);
} else {
// console.error(`File ${file} does not exist`);
}
});
}

export function processParamsToParamsArray(params: ProcessParams): string[] {
return Object.entries(params).flatMap(([key, value]) => {
if (key === '_') {
return value.toString();
} else if (Array.isArray(value)) {
return value.map(v => `--${key}=${v.toString()}`);
} else {
if (typeof value === 'string') {
return [`--${key}=${value + ''}`];
} else if (typeof value === 'boolean') {
return [`--${value ? '' : 'no-'}${key}`];
}
return [`--${key}=${value + ''}`];
}
}) as string[];
}

15 changes: 15 additions & 0 deletions packages/node-cli-testing/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable */
export default {
displayName: 'packages-cli-testing',
preset: '../../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[tj]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../../coverage/packages/cli-testing',
};
18 changes: 18 additions & 0 deletions packages/node-cli-testing/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "@push-based/node-cli-testing",
"version": "0.0.0",
"type": "commonjs",
"main": "src/index.js",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/push-based/node-cli-testing.git"
},
"keywords": [
"testing",
"cli",
"node"
],
"peerDdependencies": {},
"dependencies": {}
}
7 changes: 7 additions & 0 deletions packages/node-cli-testing/process/ng-package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "../../../../node_modules/ng-packagr/package.schema.json",
"lib": {
"entryFile": "src/index.ts",
"flatModuleFile": "cli-testing-process"
}
}
3 changes: 3 additions & 0 deletions packages/node-cli-testing/process/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './lib/types';
export * from './lib/test-process-e2e';
export * from './lib/keyboard';
6 changes: 6 additions & 0 deletions packages/node-cli-testing/process/src/lib/keyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const DOWN = '\x1B\x5B\x42';
export const UP = '\x1B\x5B\x41';
export const ENTER = '\x0D';
export const SPACE = '\x20';
export const DECLINE_BOOLEAN = 'n';
export const ACCEPT_BOOLEAN = 'Y';
Loading

0 comments on commit bc08cb2

Please sign in to comment.