packages/cli: split diff template read into two steps and tweak handlers api

This commit is contained in:
Patrik Oldsberg
2020-05-21 18:57:33 +02:00
parent 18139238a4
commit 12c669e904
4 changed files with 107 additions and 93 deletions
+2 -2
View File
@@ -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();
};
+37 -45
View File
@@ -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}`);
}
}
}
+57 -24
View File
@@ -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<PluginInfo> {
async function readPluginInfo(): Promise<PluginInfo> {
let name: string;
try {
const pkg = require(paths.resolveTarget('package.json'));
@@ -47,7 +61,7 @@ export async function readPluginInfo(): Promise<PluginInfo> {
return { id, name };
}
export async function readTemplateFile(
async function readTemplateFile(
templateFile: string,
templateVars: any,
): Promise<string> {
@@ -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<TemplateFile[]> {
): Promise<TemplatedFile[]> {
const templateFilePaths = await recursiveReadDir(templateDir).catch(error => {
throw new Error(`Failed to read template directory: ${error.message}`);
});
const templateFiles = new Array<TemplateFile>();
const templatedFiles = new Array<TemplatedFile>();
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<FileDiff[]> {
const fileDiffs = new Array<FileDiff>();
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;
}
+11 -22
View File
@@ -14,35 +14,24 @@
* limitations under the License.
*/
export type PluginInfo = {
id: string;
name: string;
};
export type WriteFileFunc = (contents: string) => Promise<void>;
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<boolean>;
export type HandlerFunc = (
file: TemplateFile,
prompt: PromptFunc,
) => Promise<void>;
export type HandlerFunc = (file: FileDiff, prompt: PromptFunc) => Promise<void>;
export type FileHandler = {
patterns: Array<string | RegExp>;