mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-17 17:28:09 +00:00
implementation of before-write callback + tests
This commit is contained in:
@@ -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)
|
||||
|
||||
13
src/types.ts
13
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
|
||||
|
||||
31
src/utils.ts
31
src/utils.ts
@@ -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[])
|
||||
|
||||
@@ -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(", ")
|
||||
)
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user