sequenceDiagram autonumber User->>Hippo: Input a command Hippo->>Hippo: Check if there is a valid "type" in "package.json" or "hippo-config.js" opt When "type" is valid Hippo->>Toolkit: Get the commands Toolkit-->Hippo: Return the commands Hippo->>Hippo: Register the commands end Hippo->>User: Return the result
/** * Find the current toolkit and register its plugin commands */ const toolkitType = apis.config.getToolkitType(); if (toolkit.isValidToolkit(toolkitType)) { const commands = toolkit.requireModule( toolkitType asstring, 'commands' ) as HippoToolkit['commands'];
static usage = Command.Usage({ description: 'Run unit test using Jest', examples: [['Run unit test using Jest', '$0 test']], });
logger = createLogger('hippo-test');
/** * All the last args to pass to "Jest" directly */ args = Option.Proxy();
/** * Get the jest configs * @returns */ abstract getJestConfig(): Promise<Config.InitialOptions>;
async run() { /** * Get the toolkit dev function */ const jestConfigs = awaitthis.getJestConfig();
const jestCliArgs = omit(argv, ['_']);
if (jestConfigs) { try { if ( process.env.NODE_ENV === null || process.env.NODE_ENV === undefined ) { /** * When we use jest in normal scenes, jest would add this env automatically in its bin(https://github.com/facebook/jest/blob/39667e3680fb463eb8caedfa7e1f9edb3f0b69a2/packages/jest-cli/bin/jest.js#L13). * In hippo we directly used runCLI from jest to run test, so this env variable is null in hippo. */ process.env.NODE_ENV = 'test'; } // We need add some options when it is in "CI" environment const ciArgs = CI ? { maxWorkers: 3, ci: true, } : {}; const { results } = await runCLI( { config: JSON.stringify(jestConfigs), // We don't collect coverage by default coverage: false, ...ciArgs, ...jestCliArgs, } as Config.Argv, [fs.cwd] );
// Exit the process when these are failed tests const { numFailedTests } = results; if (numFailedTests > 0) { process.exit(1); } } catch (e) { this.logger.error('%o', e); process.exit(1); } } } }
static usage = Command.Usage({ description: 'Check the code by ESLint, tsc according to the hippo type setting', examples: [['Lint a workspace', '$0 lint']], });
/** * Tag if it is strict mode(warn message also trigger process.exit) */ strict = Option.Boolean('--strict');
/** * Auto Fix Option * Only works for 'ESLint' */ fix = Option.Boolean('--fix');
/** * If Check Commit Files * Only works for 'ESLint' */ checkCommit = Option.Boolean('--check-commit');
/** * Lint Source Option * Only works for 'ESLint' */ src = Option.String('--src');
/** * Limit the max warn numbers */ maxWarnings = Option.String('--max-warnings', { validator: t.isNumber() });
// Get the files from git diff const gitCommittedFiles = this.checkCommit ? await getAddedFiles() : undefined;
this.logger.debug('The committed files are %o', gitCommittedFiles);
/** * Execute the type check. If you don't add this path in config, we won't do any type check */ const tscResults = lint?.lintConfig?.typescript ? await typeCheck(lint.lintConfig.typescript) : null;
/** * Get the eslint files */ const eslintFiles = this.getLintFiles(ESLINT_FILE_EXT, gitCommittedFiles); this.logger.debug('The lint file pattern is %o', eslintFiles);
// Print the result const { errorCount, warningCount } = print({ eslint: eslintResults, tsc: tscResults, });
/** * Exit the process when check commit and errorCount is not 0 * Exit the process when check commit and warnCount is not 0 and it is strict mode * Exit the process when the warn count is more than the limit */ if ( errorCount > 0 || (this.strict && warningCount > 0) || (this.maxWarnings && warningCount - this.maxWarnings > 0) ) { process.exit(1); } }
/** * Get the lint file pattern for different path * @param {string[]} ext File extension list * @param {string[]} files File List * @returns */ getLintFiles(ext: string[], files?: string[]) { if (files && files.length) { return filter(files, (fileName) => ext.includes(path.extname(fileName))); } const pattern = `**/*.{${ext.map((e) => e.replace(/^./, '')).join(',')}}`; return path.resolve(fs.cwd, this.src || fs.cwd, pattern); } }
module.exports = { extends: [ 'airbnb-base', 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier', 'plugin:@hippo/recommended', 'plugin:eslint-comments/recommended', ], reportUnusedDisableDirectives: true, settings: { 'import/resolver': [ require.resolve('eslint-import-resolver-node'), require.resolve('eslint-import-resolver-typescript'), ], // Append 'ts' extensions to Airbnb 'import/extensions' setting 'import/extensions': ['.js', '.mjs', '.jsx', '.ts', '.tsx', '.d.ts'], }, rules: { // The reason why Airbnb turned on this rule is that they discourage to use loops // Details in https://github.com/airbnb/javascript/issues/1103 // That doesn't make sense to us since we allow to use `while` and early-return technique relies on `continue` in loops 'no-continue': 'off', ... } }
module.exports = { extends: [ 'airbnb/rules/react', // don't import a11y rules 'airbnb/hooks', require.resolve('./recommended'), ], rules: { // Since we already use TypeScript, we don't quite need this rule 'react/require-default-props': 'off',
// Since we already use TypeScript, turning on this rule will conflict with ts type detection 'react/static-property-placement': 'off',