packages/cli: split diff template read into two steps and tweak handlers api
This commit is contained in:
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user