import { CliTree, CliTrees, objectPropertiesToCliTrees, writeCliTree } from '@cirrusct/cli-utils'; import { Logger } from '@cirrusct/logging'; import { createError, getIncludeExcludeConfigFilePathsWithOptions, getPackageMappedFileList, MrCommandHandler, MrCommandResults, MrPackage, } from '@cirrusct/mr-core'; import * as chalk from 'chalk'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; import * as prettier from 'prettier'; import { CommandName } from './manifest'; import { FormatCommandArguments, FormatCommandConfigSettings, FormatCommandOptions } from './types'; // typings outdated:` const resolveConfigFile: (filePath: string, options?: prettier.Options) => Promise = // tslint:disable-next-line: no-string-literal prettier['resolveConfigFile']; export const formatPackage = async ( mrPackage: MrPackage, options: FormatCommandOptions, logger: Logger, files: string[] ) => { const initialCwd = process.cwd(); try { process.chdir(mrPackage.path); logger.trace( { package: mrPackage.name, }, 'Prettier formatPackage' ); const commandConfig = mrPackage.config.getCommandConfig(CommandName); const commandConfigSettings = commandConfig ? commandConfig.value : {}; logger.trace( { ...commandConfigSettings, }, 'prettier config' ); // TODO: this will only include supplied file arguments if they are included // in the include/exclude config. Should probably format all files passed, // even if they are not in the filter defined by config. // get list of files, and default options associated with each file type const includedFiles = await getIncludeExcludeConfigFilePathsWithOptions( commandConfigSettings, mrPackage.path, files ); if (includedFiles.length > 0) { logger.debug( { fileCount: includedFiles.length, }, 'Applying Prettier Format' ); let written = 0; let skipped = 0; for (const filePathAndOptions of includedFiles) { const { filePath: relFilePath, fileOptions } = filePathAndOptions; const filePath = path.join(mrPackage.path, relFilePath); // try to get prettier config from standard prettier config file in the local path const prettierLocalOptions = await prettier.resolveConfig(filePath); let localPrettierConfigFile = null; if (prettierLocalOptions) { localPrettierConfigFile = await resolveConfigFile(filePath); } // default to standard prettier config, fall back to default options from profile const resolvedFileOptions = prettierLocalOptions || fileOptions; if (!resolvedFileOptions) { throw createError(`No Prettier Options for ${filePath}`); } const source = fs.readFileSync(filePath, 'utf8'); const output = prettier.format(source, { ...resolvedFileOptions, filepath: filePath }); const changed = output !== source; logger.debug( { filePath, configSource: localPrettierConfigFile || 'profile/default', changed, }, `${changed ? 'Writing' : 'Skipping'} file` ); if (changed) { if (!options.dryRun) { logger.write(`Writing: ${filePath}${os.EOL}`); fs.writeFileSync(filePath, output, 'utf8'); } else { logger.write(`Would write: ${filePath}`); } written++; } else { logger.write(chalk.grey(filePath) + os.EOL); skipped++; } } logger.write(os.EOL); logger.info( { package: mrPackage.name, written, skipped, }, `Format: ${written} files written` ); } else { logger.info('Format: No files to format'); } } catch (e) { throw createError('formatPackage failed', e); } finally { process.chdir(initialCwd); } }; export const handleCommand: MrCommandHandler = async ({ args, options, session, packages, logger, }) => { const { project } = session; // get optional individual file names (mapped to packages) based on --git-staged setting and cmd args const isFileSpec = !!options.gitStaged || (args && args.files && args.files.length > 0); if (isFileSpec) { const filesMap = await getPackageMappedFileList( project, options.gitStaged || false, args.files || [], packages ); if (filesMap.size > 0) { // operating on individual files // get array of packages in the map (union with packages array passed to command) const filePkgs = Array.from(filesMap.keys()); // iterated packages for (const pkg of filePkgs) { await formatPackage(pkg, options, logger, filesMap.get(pkg)); } } else { logger.info( { ...options, packages: (packages || []).map(p => p.name).join(', '), }, 'Format: No files matching fileSpec options' ); } } else { // iterate over packages passed to command, processing all files based on command config for (const pkg of packages) { await formatPackage(pkg, options, logger, []); } } };