diff --git a/packages/cli/src/commands/plugin/diff.ts b/packages/cli/src/commands/plugin/diff.ts index 542b94b0ff..ba3e99527f 100644 --- a/packages/cli/src/commands/plugin/diff.ts +++ b/packages/cli/src/commands/plugin/diff.ts @@ -16,7 +16,7 @@ import { Command } from 'commander'; import { - readTemplateFiles, + diffTemplateFiles, handlers, handleAllFiles, inquirerPromptFunc, @@ -54,7 +54,7 @@ export default async (cmd: Command) => { promptFunc = yesPromptFunc; } - const templateFiles = await readTemplateFiles('default-plugin'); + const templateFiles = await diffTemplateFiles('default-plugin'); await handleAllFiles(fileHandlers, templateFiles, promptFunc); await finalize(); }; diff --git a/packages/cli/src/lib/diff/handlers.ts b/packages/cli/src/lib/diff/handlers.ts index 643c018ddc..9e46dd92cf 100644 --- a/packages/cli/src/lib/diff/handlers.ts +++ b/packages/cli/src/lib/diff/handlers.ts @@ -14,36 +14,30 @@ * limitations under the License. */ -import fs from 'fs-extra'; import chalk from 'chalk'; -import { dirname } from 'path'; import { diffLines } from 'diff'; -import { paths } from '../paths'; -import { TemplateFile, PromptFunc, FileHandler } from './types'; - -export async function writeTargetFile(targetPath: string, contents: string) { - const path = paths.resolveTarget(targetPath); - await fs.ensureDir(dirname(path)); - await fs.writeFile(path, contents, 'utf8'); -} +import { FileDiff, PromptFunc, FileHandler, WriteFileFunc } from './types'; class PackageJsonHandler { - static async handler(file: TemplateFile, prompt: PromptFunc) { + static async handler( + { path, write, missing, targetContents, templateContents }: FileDiff, + prompt: PromptFunc, + ) { console.log('Checking package.json'); - if (!file.targetExists) { - throw new Error(`${file.targetPath} doesn't exist`); + if (missing) { + throw new Error(`${path} doesn't exist`); } - const pkg = JSON.parse(file.templateContents); - const targetPkg = JSON.parse(file.targetContents); + const pkg = JSON.parse(templateContents); + const targetPkg = JSON.parse(targetContents); - const handler = new PackageJsonHandler(file, prompt, pkg, targetPkg); + const handler = new PackageJsonHandler(write, prompt, pkg, targetPkg); await handler.handle(); } constructor( - private readonly file: TemplateFile, + private readonly writeFunc: WriteFileFunc, private readonly prompt: PromptFunc, private readonly pkg: any, private readonly targetPkg: any, @@ -144,31 +138,29 @@ class PackageJsonHandler { } private async write() { - await fs.writeFile( - paths.resolveTarget(this.file.targetPath), - `${JSON.stringify(this.targetPkg, null, 2)}\n`, - ); + await this.writeFunc(`${JSON.stringify(this.targetPkg, null, 2)}\n`); } } // Make sure the file is an exact match of the template -async function exactMatchHandler(file: TemplateFile, prompt: PromptFunc) { - console.log(`Checking ${file.targetPath}`); +async function exactMatchHandler( + { path, write, missing, targetContents, templateContents }: FileDiff, + prompt: PromptFunc, +) { + console.log(`Checking ${path}`); + const coloredPath = chalk.cyan(path); - const { targetPath, templateContents } = file; - const coloredPath = chalk.cyan(targetPath); - - if (!file.targetExists) { + if (missing) { if (await prompt(`Missing ${coloredPath}, do you want to add it?`)) { - await writeTargetFile(targetPath, templateContents); + await write(templateContents); } return; } - if (file.targetContents === templateContents) { + if (targetContents === templateContents) { return; } - const diffs = diffLines(file.targetContents, templateContents); + const diffs = diffLines(targetContents, templateContents); for (const diff of diffs) { if (diff.added) { process.stdout.write(chalk.green(`+${diff.value}`)); @@ -184,27 +176,29 @@ async function exactMatchHandler(file: TemplateFile, prompt: PromptFunc) { `Outdated ${coloredPath}, do you want to apply the above patch?`, ) ) { - await writeTargetFile(targetPath, templateContents); + await write(templateContents); } } // Adds the file if it is missing, but doesn't check existing files -async function existsHandler(file: TemplateFile, prompt: PromptFunc) { - console.log(`Making sure ${file.targetPath} exists`); +async function existsHandler( + { path, write, missing, templateContents }: FileDiff, + prompt: PromptFunc, +) { + console.log(`Making sure ${path} exists`); - const { targetPath, templateContents } = file; - const coloredPath = chalk.cyan(targetPath); + const coloredPath = chalk.cyan(path); - if (!file.targetExists) { + if (missing) { if (await prompt(`Missing ${coloredPath}, do you want to add it?`)) { - await writeTargetFile(targetPath, templateContents); + await write(templateContents); } return; } } -async function skipHandler(file: TemplateFile) { - console.log(`Skipping ${file.targetPath}`); +async function skipHandler({ path }: FileDiff) { + console.log(`Skipping ${path}`); } export const handlers = { @@ -216,22 +210,20 @@ export const handlers = { export async function handleAllFiles( fileHandlers: FileHandler[], - files: TemplateFile[], + files: FileDiff[], promptFunc: PromptFunc, ) { for (const file of files) { - const { targetPath } = file; + const { path } = file; const fileHandler = fileHandlers.find(handler => handler.patterns.some(pattern => - typeof pattern === 'string' - ? pattern === targetPath - : pattern.test(targetPath), + typeof pattern === 'string' ? pattern === path : pattern.test(path), ), ); if (fileHandler) { await fileHandler.handler(file, promptFunc); } else { - throw new Error(`No template file handler found for ${targetPath}`); + throw new Error(`No template file handler found for ${path}`); } } } diff --git a/packages/cli/src/lib/diff/read.ts b/packages/cli/src/lib/diff/read.ts index b408da94e9..a0bb6364ca 100644 --- a/packages/cli/src/lib/diff/read.ts +++ b/packages/cli/src/lib/diff/read.ts @@ -15,15 +15,29 @@ */ import fs from 'fs-extra'; -import { relative as relativePath } from 'path'; +import { + dirname, + resolve as resolvePath, + relative as relativePath, +} from 'path'; import handlebars from 'handlebars'; import recursiveReadDir from 'recursive-readdir'; import { paths } from '../paths'; import { version } from '../version'; -import { PluginInfo, TemplateFile } from './types'; +import { FileDiff } from './types'; + +export type PluginInfo = { + id: string; + name: string; +}; + +export type TemplatedFile = { + path: string; + contents: string; +}; // Reads info from the existing plugin -export async function readPluginInfo(): Promise { +async function readPluginInfo(): Promise { let name: string; try { const pkg = require(paths.resolveTarget('package.json')); @@ -47,7 +61,7 @@ export async function readPluginInfo(): Promise { return { id, name }; } -export async function readTemplateFile( +async function readTemplateFile( templateFile: string, templateVars: any, ): Promise { @@ -60,51 +74,70 @@ export async function readTemplateFile( return handlebars.compile(contents)(templateVars); } -export async function readTemplate( +async function readTemplate( templateDir: string, templateVars: any, -): Promise { +): Promise { const templateFilePaths = await recursiveReadDir(templateDir).catch(error => { throw new Error(`Failed to read template directory: ${error.message}`); }); - const templateFiles = new Array(); + const templatedFiles = new Array(); for (const templateFile of templateFilePaths) { - // Target file inside the target dir without template extension - const targetFile = templateFile - .replace(templateDir, paths.targetDir) - .replace(/\.hbs$/, ''); - const targetPath = relativePath(paths.targetDir, targetFile); + const path = relativePath(templateDir, templateFile).replace(/\.hbs$/, ''); + const contents = await readTemplateFile(templateFile, templateVars); - const templateContents = await readTemplateFile(templateFile, templateVars); + templatedFiles.push({ path, contents }); + } + + return templatedFiles; +} + +async function diffTemplatedFiles( + targetDir: string, + templatedFiles: TemplatedFile[], +): Promise { + const fileDiffs = new Array(); + for (const { path, contents: templateContents } of templatedFiles) { + const targetPath = resolvePath(targetDir, path); + const targetExists = await fs.pathExists(targetPath); + + const write = async (contents: string) => { + await fs.ensureDir(dirname(targetPath)); + await fs.writeFile(targetPath, contents, 'utf8'); + }; - const targetExists = await fs.pathExists(targetFile); if (targetExists) { - const targetContents = await fs.readFile(targetFile, 'utf8'); - templateFiles.push({ - targetPath, - targetExists, + const targetContents = await fs.readFile(targetPath, 'utf8'); + fileDiffs.push({ + path, + write, + missing: false, targetContents, templateContents, }); } else { - templateFiles.push({ - targetPath, - targetExists, + fileDiffs.push({ + path, + write, + missing: true, + targetContents: '', templateContents, }); } } - return templateFiles; + return fileDiffs; } // Read all template files for a given template, along with all matching files in the target dir -export async function readTemplateFiles(template: string) { +export async function diffTemplateFiles(template: string) { const pluginInfo = await readPluginInfo(); const templateVars = { version, ...pluginInfo }; const templateDir = paths.resolveOwn('templates', template); - return await readTemplate(templateDir, templateVars); + const templatedFiles = await readTemplate(templateDir, templateVars); + const fileDiffs = await diffTemplatedFiles(paths.targetDir, templatedFiles); + return fileDiffs; } diff --git a/packages/cli/src/lib/diff/types.ts b/packages/cli/src/lib/diff/types.ts index 7a04cf8d2e..6c50d9fcf9 100644 --- a/packages/cli/src/lib/diff/types.ts +++ b/packages/cli/src/lib/diff/types.ts @@ -14,35 +14,24 @@ * limitations under the License. */ -export type PluginInfo = { - id: string; - name: string; -}; +export type WriteFileFunc = (contents: string) => Promise; -export type TemplateFile = { +export type FileDiff = { // Relative path within the target directory - targetPath: string; + path: string; + // Wether the target file exists in the target directory. + missing: boolean; + // Contents of the file in the target directory, or an empty string if the file is missing. + targetContents: string; // Contents of the compiled template file templateContents: string; -} & ( - | { - // Whether the template file exists in the target directory - targetExists: true; - // Contents of the file in the target directory, if it exists - targetContents: string; - } - | { - // Whether the template file exists in the target directory - targetExists: false; - } -); + // Write new contents to the target file + write: WriteFileFunc; +}; export type PromptFunc = (msg: string) => Promise; -export type HandlerFunc = ( - file: TemplateFile, - prompt: PromptFunc, -) => Promise; +export type HandlerFunc = (file: FileDiff, prompt: PromptFunc) => Promise; export type FileHandler = { patterns: Array;