implementation of before-write callback + tests

This commit is contained in:
Chen Asraf
2022-04-19 16:19:03 +03:00
parent 6700708e1d
commit 4c9a838ebe
5 changed files with 84 additions and 25 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -86,7 +86,6 @@ export async function createDirIfNotExists(dir: string, options: ScaffoldConfig)
export function getOptionValueForFile<T>(
options: ScaffoldConfig,
filePath: string,
data: Record<string, string>,
fn: FileResponse<T>,
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<OutputFileInfo> {
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<string, string>,
{
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<string, string>,
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[])

View File

@@ -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(", ")
)
})
})
)
})

View File

@@ -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"))
})
})
})