From 4c9a838ebe0b412e59d36bf5569f65ca2d31d58e Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Tue, 19 Apr 2022 16:19:03 +0300 Subject: [PATCH] implementation of before-write callback + tests --- src/scaffold.ts | 9 +++++--- src/types.ts | 13 ++++++++++++ src/utils.ts | 31 +++++++++++++-------------- tests/scaffold.test.ts | 48 +++++++++++++++++++++++++++++++++++++++--- tests/utils.test.ts | 8 ++++--- 5 files changed, 84 insertions(+), 25 deletions(-) diff --git a/src/scaffold.ts b/src/scaffold.ts index a91e023..90e1ef5 100644 --- a/src/scaffold.ts +++ b/src/scaffold.ts @@ -82,7 +82,10 @@ export async function Scaffold({ ...options }: ScaffoldConfig) { isDirOrGlob, isGlob, }) - await handleTemplateFile(options, options.data, { templatePath: inputFilePath, basePath }) + await handleTemplateFile(options, options.data, { + templatePath: inputFilePath, + basePath, + }) } } catch (e: any) { handleErr(e) @@ -104,7 +107,7 @@ async function handleTemplateFile( templatePath, basePath, }) - const overwrite = getOptionValueForFile(options, inputPath, data, options.overwrite ?? false) + const overwrite = getOptionValueForFile(options, inputPath, options.overwrite ?? false) log( options, @@ -121,7 +124,7 @@ async function handleTemplateFile( await createDirIfNotExists(path.dirname(outputPath), options) log(options, LogLevel.Info, `Writing to ${outputPath}`) - await copyFileTransformed(options, data, { exists, overwrite, outputPath, inputPath }) + await copyFileTransformed(options, { exists, overwrite, outputPath, inputPath }) resolve() } catch (e: any) { handleErr(e) diff --git a/src/types.ts b/src/types.ts index e5b6ed5..4d3d1ca 100644 --- a/src/types.ts +++ b/src/types.ts @@ -113,6 +113,19 @@ export interface ScaffoldConfig { * helpers, or a custom one you provide to `helpers`. Defaults to `undefined`, which means no transformation is done. */ subFolderNameHelper?: DefaultHelperKeys | string + + /** + * This callback runs right before content is being written to the disk. If you supply this function, you may return + * a string that represents the final content of your file, you may process the content as you see fit. For example, + * you may run formatters on a file, fix output in edge-cases not supported by helpers or data, etc. + * + * If the return value of this function is `undefined`, the original content will be used. + * + * @param content The original template after modification + * @param rawContent The original template before modification + * @param outputPath The full output path of the processed file + */ + beforeWrite?(content: Buffer, rawContent: Buffer, outputPath: string): string | undefined } export interface ScaffoldCmdConfig { name: string diff --git a/src/utils.ts b/src/utils.ts index c6dc942..8f2b508 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -86,7 +86,6 @@ export async function createDirIfNotExists(dir: string, options: ScaffoldConfig) export function getOptionValueForFile( options: ScaffoldConfig, filePath: string, - data: Record, fn: FileResponse, defaultValue?: T ): T { @@ -104,7 +103,7 @@ export function handlebarsParse( options: ScaffoldConfig, templateBuffer: Buffer | string, { isPath = false }: { isPath?: boolean } = {} -) { +): Buffer { const { data } = options try { let str = templateBuffer.toString() @@ -116,11 +115,11 @@ export function handlebarsParse( if (isPath && path.sep !== "/") { outputContents = outputContents.replace(/\//g, "\\") } - return outputContents + return Buffer.from(outputContents) } catch (e) { log(options, LogLevel.Debug, e) log(options, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content") - return templateBuffer + return Buffer.from(templateBuffer) } } @@ -217,8 +216,8 @@ export async function getTemplateFileInfo( { templatePath, basePath }: { templatePath: string; basePath: string } ): Promise { const inputPath = path.resolve(process.cwd(), templatePath) - const outputPathOpt = getOptionValueForFile(options, inputPath, data, options.output) - const outputDir = getOutputDir(options, data, outputPathOpt, basePath) + const outputPathOpt = getOptionValueForFile(options, inputPath, options.output) + const outputDir = getOutputDir(options, outputPathOpt, basePath) const outputPath = handlebarsParse(options, path.join(outputDir, path.basename(inputPath)), { isPath: true, }).toString() @@ -228,20 +227,25 @@ export async function getTemplateFileInfo( export async function copyFileTransformed( options: ScaffoldConfig, - data: Record, { exists, overwrite, outputPath, inputPath, - }: { exists: boolean; overwrite: boolean; outputPath: string; inputPath: string } + }: { + exists: boolean + overwrite: boolean + outputPath: string + inputPath: string + } ) { if (!exists || overwrite) { if (exists && overwrite) { log(options, LogLevel.Info, `File ${outputPath} exists, overwriting`) } const templateBuffer = await readFile(inputPath) - const outputContents = handlebarsParse(options, templateBuffer) + const preOutputContents = handlebarsParse(options, templateBuffer) + const outputContents = options.beforeWrite?.(preOutputContents, templateBuffer, outputPath) ?? preOutputContents if (!options.dryRun) { await writeFile(outputPath, outputContents) @@ -255,12 +259,7 @@ export async function copyFileTransformed( } } -export function getOutputDir( - options: ScaffoldConfig, - data: Record, - outputPathOpt: string, - basePath: string -) { +export function getOutputDir(options: ScaffoldConfig, outputPathOpt: string, basePath: string) { return path.resolve( process.cwd(), ...([ @@ -268,7 +267,7 @@ export function getOutputDir( basePath, options.createSubFolder ? options.subFolderNameHelper - ? handlebarsParse(options, `{{ ${options.subFolderNameHelper} name }}`) + ? handlebarsParse(options, `{{ ${options.subFolderNameHelper} name }}`).toString() : options.name : undefined, ].filter(Boolean) as string[]) diff --git a/tests/scaffold.test.ts b/tests/scaffold.test.ts index c68ffbd..9a540ce 100644 --- a/tests/scaffold.test.ts +++ b/tests/scaffold.test.ts @@ -291,7 +291,7 @@ describe("Scaffold", () => { verbose: 0, }) - const data = readFileSync(join(process.cwd(), "output", "app_name/app_name.txt")) + const data = readFileSync(join(process.cwd(), "output", "app_name", "app_name.txt")) expect(data.toString()).toBe("Hello, my app is app_name") }) @@ -305,7 +305,7 @@ describe("Scaffold", () => { subFolderNameHelper: "upperCase", }) - const data = readFileSync(join(process.cwd(), "output", "APP_NAME/app_name.txt")) + const data = readFileSync(join(process.cwd(), "output", "APP_NAME", "app_name.txt")) expect(data.toString()).toBe("Hello, my app is app_name") }) @@ -322,9 +322,51 @@ describe("Scaffold", () => { }, }) - const data = readFileSync(join(process.cwd(), "output", "REPLACED/app_name.txt")) + const data = readFileSync(join(process.cwd(), "output", "REPLACED", "app_name.txt")) expect(data.toString()).toBe("Hello, my app is app_name") }) }) ) + describe( + "before write", + withMock(fileStructNormal, () => { + test("should work with no callback", async () => { + await Scaffold({ + name: "app_name", + output: "output", + templates: ["input"], + verbose: 0, + data: { + value: "value", + }, + }) + + const data = readFileSync(join(process.cwd(), "output", "app_name.txt")) + expect(data.toString()).toBe("Hello, my app is app_name") + }) + + test("should work with custom callback", async () => { + await Scaffold({ + name: "app_name", + output: "output", + templates: ["input"], + verbose: 0, + data: { + value: "value", + }, + beforeWrite: (content, beforeContent, outputPath) => + [content.toString().toUpperCase(), beforeContent, outputPath].join(", "), + }) + + const data = readFileSync(join(process.cwd(), "output", "app_name.txt")) + expect(data.toString()).toBe( + [ + "Hello, my app is app_name".toUpperCase(), + fileStructNormal.input["{{name}}.txt"], + join(process.cwd(), "output", "app_name.txt"), + ].join(", ") + ) + }) + }) + ) }) diff --git a/tests/utils.test.ts b/tests/utils.test.ts index c9d071b..0708603 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -23,7 +23,7 @@ describe("Utils", () => { }) test("should work for windows paths", async () => { expect(handlebarsParse(blankConf, "C:\\exports\\{{name}}.txt", { isPath: true })).toEqual( - "C:\\exports\\test.txt" + Buffer.from("C:\\exports\\test.txt") ) }) }) @@ -36,7 +36,9 @@ describe("Utils", () => { Object.defineProperty(path, "sep", { value: origSep }) }) test("should work for non-windows paths", async () => { - expect(handlebarsParse(blankConf, "/home/test/{{name}}.txt", { isPath: true })).toEqual("/home/test/test.txt") + expect(handlebarsParse(blankConf, "/home/test/{{name}}.txt", { isPath: true })).toEqual( + Buffer.from("/home/test/test.txt") + ) }) }) test("should not do path escaping on non-path compiles", async () => { @@ -48,7 +50,7 @@ describe("Utils", () => { isPath: false, } ) - ).toEqual("/home/test/test {{escaped}}.txt") + ).toEqual(Buffer.from("/home/test/test {{escaped}}.txt")) }) }) })