From 56f1340093122bee0db3557ec2993af39847143f Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Thu, 3 Mar 2022 10:21:37 +0200 Subject: [PATCH 1/7] fix transform of windows-style paths --- src/scaffold.ts | 9 +------ src/utils.ts | 22 +++++++++++----- tests/utils.test.ts | 62 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 14 deletions(-) create mode 100644 tests/utils.test.ts diff --git a/src/scaffold.ts b/src/scaffold.ts index dd64f78..e0cbb52 100644 --- a/src/scaffold.ts +++ b/src/scaffold.ts @@ -1,16 +1,11 @@ -import { glob } from "glob" import path from "path" -import { promisify } from "util" import { promises as fsPromises } from "fs" -const { readFile, writeFile } = fsPromises import { createDirIfNotExists, getOptionValueForFile, handleErr, - handlebarsParse, log, - pathExists, pascalCase, isDir, removeGlob, @@ -24,10 +19,8 @@ import { getTemplateFileInfo, logInitStep, logInputFile, - GlobInfo, - OutputFileInfo, } from "./utils" -import { FileResponse, LogLevel, ScaffoldConfig } from "./types" +import { LogLevel, ScaffoldConfig } from "./types" /** * Create a scaffold using given `options`. diff --git a/src/utils.ts b/src/utils.ts index 0565d20..9717890 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -95,19 +95,27 @@ export function getOptionValueForFile( } return (fn as FileResponseFn)( filePath, - path.dirname(handlebarsParse(options, filePath, data).toString()), - path.basename(handlebarsParse(options, filePath, data).toString()) + path.dirname(handlebarsParse(options, filePath, data, { isPath: true }).toString()), + path.basename(handlebarsParse(options, filePath, data, { isPath: true }).toString()) ) } export function handlebarsParse( options: ScaffoldConfig, templateBuffer: Buffer | string, - data: Record + data: Record, + { isPath = false }: { isPath?: boolean } = {} ) { try { - const parser = Handlebars.compile(templateBuffer.toString(), { noEscape: true }) - const outputContents = parser(data) + let str = templateBuffer.toString() + if (isPath) { + str = str.replace(/\\/g, "/") + } + const parser = Handlebars.compile(str, { noEscape: true }) + let outputContents = parser(data) + if (isPath && path.sep !== "/") { + outputContents = outputContents.replace(/\//g, "\\") + } return outputContents } catch { log(options, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content") @@ -208,7 +216,9 @@ export async function getTemplateFileInfo( const inputPath = path.resolve(process.cwd(), templatePath) const outputPathOpt = getOptionValueForFile(options, inputPath, data, options.output) const outputDir = getOutputDir(options, data, outputPathOpt, basePath) - const outputPath = handlebarsParse(options, path.join(outputDir, path.basename(inputPath)), data).toString() + const outputPath = handlebarsParse(options, path.join(outputDir, path.basename(inputPath)), data, { + isPath: true, + }).toString() const exists = await pathExists(outputPath) return { inputPath, outputPathOpt, outputDir, outputPath, exists } } diff --git a/tests/utils.test.ts b/tests/utils.test.ts new file mode 100644 index 0000000..87e5ef0 --- /dev/null +++ b/tests/utils.test.ts @@ -0,0 +1,62 @@ +import { handlebarsParse } from "../src/utils" +import path from "path" + +describe("Utils", () => { + describe("handlebarsParse", () => { + let origSep: any + describe("windows paths", () => { + beforeAll(() => { + origSep = path.sep + Object.defineProperty(path, "sep", { value: "\\" }) + }) + afterAll(() => { + Object.defineProperty(path, "sep", { value: origSep }) + }) + test("should work for windows paths", async () => { + expect( + handlebarsParse( + { + verbose: 0, + name: "", + output: "", + templates: [], + }, + "C:\\exports\\{{name}}.txt", + { name: "test" }, + { isPath: true } + ) + ).toEqual("C:\\exports\\test.txt") + }) + }) + test("should work for non-windows paths", async () => { + expect( + handlebarsParse( + { + verbose: 0, + name: "", + output: "", + templates: [], + }, + "/home/test/{{name}}.txt", + { name: "test" }, + { isPath: true } + ) + ).toEqual("/home/test/test.txt") + }) + test("should not do path escaping on non-path compiles", async () => { + expect( + handlebarsParse( + { + verbose: 0, + name: "", + output: "", + templates: [], + }, + "/home/test/{{name}} \\{{escaped}}.txt", + { name: "test" }, + { isPath: false } + ) + ).toEqual("/home/test/test {{escaped}}.txt") + }) + }) +}) From d6e169307439b2b7fef6b7f272721a81c290ea6b Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Thu, 3 Mar 2022 10:25:44 +0200 Subject: [PATCH 2/7] import/file cleanup --- src/filters.ts | 0 src/scaffold.ts | 1 - 2 files changed, 1 deletion(-) delete mode 100644 src/filters.ts diff --git a/src/filters.ts b/src/filters.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/scaffold.ts b/src/scaffold.ts index e0cbb52..a91e023 100644 --- a/src/scaffold.ts +++ b/src/scaffold.ts @@ -1,5 +1,4 @@ import path from "path" -import { promises as fsPromises } from "fs" import { createDirIfNotExists, From 89d7897f4eede290d385177c384b0c6a7f7f6850 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Thu, 3 Mar 2022 10:31:30 +0200 Subject: [PATCH 3/7] refactor handlebarsParse - remove redundant arg --- src/utils.ts | 12 +++++----- tests/utils.test.ts | 55 +++++++++++++-------------------------------- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 9717890..5ea2d80 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -95,17 +95,17 @@ export function getOptionValueForFile( } return (fn as FileResponseFn)( filePath, - path.dirname(handlebarsParse(options, filePath, data, { isPath: true }).toString()), - path.basename(handlebarsParse(options, filePath, data, { isPath: true }).toString()) + path.dirname(handlebarsParse(options, filePath, { isPath: true }).toString()), + path.basename(handlebarsParse(options, filePath, { isPath: true }).toString()) ) } export function handlebarsParse( options: ScaffoldConfig, templateBuffer: Buffer | string, - data: Record, { isPath = false }: { isPath?: boolean } = {} ) { + const { data } = options try { let str = templateBuffer.toString() if (isPath) { @@ -216,7 +216,7 @@ export async function getTemplateFileInfo( const inputPath = path.resolve(process.cwd(), templatePath) const outputPathOpt = getOptionValueForFile(options, inputPath, data, options.output) const outputDir = getOutputDir(options, data, outputPathOpt, basePath) - const outputPath = handlebarsParse(options, path.join(outputDir, path.basename(inputPath)), data, { + const outputPath = handlebarsParse(options, path.join(outputDir, path.basename(inputPath)), { isPath: true, }).toString() const exists = await pathExists(outputPath) @@ -238,7 +238,7 @@ export async function copyFileTransformed( log(options, LogLevel.Info, `File ${outputPath} exists, overwriting`) } const templateBuffer = await readFile(inputPath) - const outputContents = handlebarsParse(options, templateBuffer, data) + const outputContents = handlebarsParse(options, templateBuffer) if (!options.dryRun) { await writeFile(outputPath, outputContents) @@ -265,7 +265,7 @@ export function getOutputDir( basePath, options.createSubFolder ? options.subFolderNameHelper - ? handlebarsParse(options, `{{ ${options.subFolderNameHelper} name }}`, data) + ? handlebarsParse(options, `{{ ${options.subFolderNameHelper} name }}`) : options.name : undefined, ].filter(Boolean) as string[]) diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 87e5ef0..142afb8 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,6 +1,15 @@ import { handlebarsParse } from "../src/utils" +import { ScaffoldConfig } from "../src/types" import path from "path" +const blankConf: ScaffoldConfig = { + verbose: 0, + name: "", + output: "", + templates: [], + data: { name: "test" }, +} + describe("Utils", () => { describe("handlebarsParse", () => { let origSep: any @@ -13,50 +22,18 @@ describe("Utils", () => { Object.defineProperty(path, "sep", { value: origSep }) }) test("should work for windows paths", async () => { - expect( - handlebarsParse( - { - verbose: 0, - name: "", - output: "", - templates: [], - }, - "C:\\exports\\{{name}}.txt", - { name: "test" }, - { isPath: true } - ) - ).toEqual("C:\\exports\\test.txt") + expect(handlebarsParse(blankConf, "C:\\exports\\{{name}}.txt", { isPath: true })).toEqual( + "C:\\exports\\test.txt" + ) }) }) test("should work for non-windows paths", async () => { - expect( - handlebarsParse( - { - verbose: 0, - name: "", - output: "", - templates: [], - }, - "/home/test/{{name}}.txt", - { name: "test" }, - { isPath: true } - ) - ).toEqual("/home/test/test.txt") + expect(handlebarsParse(blankConf, "/home/test/{{name}}.txt", { isPath: true })).toEqual("/home/test/test.txt") }) test("should not do path escaping on non-path compiles", async () => { - expect( - handlebarsParse( - { - verbose: 0, - name: "", - output: "", - templates: [], - }, - "/home/test/{{name}} \\{{escaped}}.txt", - { name: "test" }, - { isPath: false } - ) - ).toEqual("/home/test/test {{escaped}}.txt") + expect(handlebarsParse(blankConf, "/home/test/{{name}} \\{{escaped}}.txt", { isPath: false })).toEqual( + "/home/test/test {{escaped}}.txt" + ) }) }) }) From 52cb3e7353b22df99d709500988f5be89fa31cfb Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Thu, 3 Mar 2022 12:25:39 +0200 Subject: [PATCH 4/7] fixed more windows paths, updated tests --- src/utils.ts | 20 +++++++++++--------- tests/utils.test.ts | 13 +++++++++++-- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index 5ea2d80..2d2a287 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -145,26 +145,28 @@ export async function isDir(path: string): Promise { } export function removeGlob(template: string) { - return template.replace(/\*/g, "").replace(/\/\//g, "/") + return template.replace(/\*/g, "").replace(/(\/\/|\\\\)/g, path.sep) } export function makeRelativePath(str: string): string { - return str.startsWith("/") ? str.slice(1) : str + return str.startsWith(path.sep) ? str.slice(1) : str } export function getBasePath(relPath: string) { return path .resolve(process.cwd(), relPath) - .replace(process.cwd() + "/", "") + .replace(process.cwd() + path.sep, "") .replace(process.cwd(), "") } export async function getFileList(options: ScaffoldConfig, template: string) { - return await promisify(glob)(template, { - dot: true, - debug: options.verbose === LogLevel.Debug, - nodir: true, - }) + return ( + await promisify(glob)(template, { + dot: true, + debug: options.verbose === LogLevel.Debug, + nodir: true, + }) + ).map((f) => f.replace(/\//g, path.sep)) } export interface GlobInfo { @@ -185,7 +187,7 @@ export async function getTemplateGlobInfo(options: ScaffoldConfig, template: str const _shouldAddGlob = !isGlob && isDirOrGlob const origTemplate = template if (_shouldAddGlob) { - _template = template + "/**/*" + _template = path.join(template, "**", "*") } return { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template: _template } } diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 142afb8..ecc820d 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -27,8 +27,17 @@ describe("Utils", () => { ) }) }) - test("should work for non-windows paths", async () => { - expect(handlebarsParse(blankConf, "/home/test/{{name}}.txt", { isPath: true })).toEqual("/home/test/test.txt") + describe("non-windows paths", () => { + beforeAll(() => { + origSep = path.sep + Object.defineProperty(path, "sep", { value: "/" }) + }) + afterAll(() => { + 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") + }) }) test("should not do path escaping on non-path compiles", async () => { expect(handlebarsParse(blankConf, "/home/test/{{name}} \\{{escaped}}.txt", { isPath: false })).toEqual( From a043a05bc9ba98a5ec7e443018a12f2a01f70678 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Thu, 3 Mar 2022 21:42:31 +0200 Subject: [PATCH 5/7] updated tests --- tests/scaffold.test.ts | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/scaffold.test.ts b/tests/scaffold.test.ts index ed5fe4a..c68ffbd 100644 --- a/tests/scaffold.test.ts +++ b/tests/scaffold.test.ts @@ -4,6 +4,7 @@ import Scaffold from "../src/scaffold" import { readdirSync, readFileSync } from "fs" import { Console } from "console" import { defaultHelpers } from "../src/utils" +import { join } from "path" const fileStructNormal = { input: { @@ -83,7 +84,7 @@ describe("Scaffold", () => { templates: ["input"], verbose: 0, }) - const data = readFileSync(process.cwd() + "/output/app_name.txt") + const data = readFileSync(join(process.cwd(), "output", "app_name.txt")) expect(data.toString()).toBe("Hello, my app is app_name") }) @@ -96,7 +97,7 @@ describe("Scaffold", () => { verbose: 0, }) - const data = readFileSync(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") }) }) @@ -122,7 +123,7 @@ describe("Scaffold", () => { verbose: 0, }) - const data = readFileSync(process.cwd() + "/output/app_name.txt") + const data = readFileSync(join(process.cwd(), "output", "app_name.txt")) expect(data.toString()).toBe("Hello, my value is 1") }) @@ -144,7 +145,7 @@ describe("Scaffold", () => { verbose: 0, }) - const data = readFileSync(process.cwd() + "/output/app_name.txt") + const data = readFileSync(join(process.cwd(), "output", "app_name.txt")) expect(data.toString()).toBe("Hello, my value is 2") }) }) @@ -173,7 +174,7 @@ describe("Scaffold", () => { }) ).rejects.toThrow() - expect(() => readFileSync(process.cwd() + "/output/app_name.txt")).toThrow() + expect(() => readFileSync(join(process.cwd(), "output", "app_name.txt"))).toThrow() }) }) ) @@ -184,12 +185,12 @@ describe("Scaffold", () => { test("should allow override function", async () => { await Scaffold({ name: "app_name", - output: (fullPath, basedir, basename) => `custom-output/${basename.split(".")[0]}`, + output: (fullPath, basedir, basename) => join("custom-output", `${basename.split(".")[0]}`), templates: ["input"], data: { value: "1" }, verbose: 0, }) - const data = readFileSync(process.cwd() + "/custom-output/app_name/app_name.txt") + const data = readFileSync(join(process.cwd(), "/custom-output/app_name/app_name.txt")) expect(data.toString()).toBe("Hello, my app is app_name") }) }) @@ -207,16 +208,16 @@ describe("Scaffold", () => { verbose: 0, }) - const rootDir = readdirSync(process.cwd() + "/output") - const dir = readdirSync(process.cwd() + "/output/AppName") - const nestedDir = readdirSync(process.cwd() + "/output/AppName/moreNesting") + const rootDir = readdirSync(join(process.cwd(), "output")) + const dir = readdirSync(join(process.cwd(), "output", "AppName")) + const nestedDir = readdirSync(join(process.cwd(), "output", "AppName", "moreNesting")) expect(rootDir).toHaveProperty("length") expect(dir).toHaveProperty("length") expect(nestedDir).toHaveProperty("length") - const rootFile = readFileSync(process.cwd() + "/output/app_name-1.txt") - const oneDeepFile = readFileSync(process.cwd() + "/output/AppName/app_name-2.txt") - const twoDeepFile = readFileSync(process.cwd() + "/output/AppName/moreNesting/app_name-3.txt") + const rootFile = readFileSync(join(process.cwd(), "output", "app_name-1.txt")) + const oneDeepFile = readFileSync(join(process.cwd(), "output", "AppName/app_name-2.txt")) + const twoDeepFile = readFileSync(join(process.cwd(), "output", "AppName/moreNesting/app_name-3.txt")) expect(rootFile.toString()).toEqual("This should be in root") expect(oneDeepFile.toString()).toEqual("Hello, my value is 1") expect(twoDeepFile.toString()).toEqual("Hi! My value is actually NOT 1!") @@ -252,7 +253,7 @@ describe("Scaffold", () => { upperCase: "APP_NAME", } for (const key in results) { - const file = readFileSync(process.cwd() + `/output/defaults/${key}.txt`) + const file = readFileSync(join(process.cwd(), "output", "defaults", `${key}.txt`)) expect(file.toString()).toEqual(results[key as keyof typeof results]) } }) @@ -271,7 +272,7 @@ describe("Scaffold", () => { add1: "app_name 1", } for (const key in results) { - const file = readFileSync(process.cwd() + `/output/custom/${key}.txt`) + const file = readFileSync(join(process.cwd(), "output", "custom", `${key}.txt`)) expect(file.toString()).toEqual(results[key as keyof typeof results]) } }) @@ -290,7 +291,7 @@ describe("Scaffold", () => { verbose: 0, }) - const data = readFileSync(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") }) @@ -304,7 +305,7 @@ describe("Scaffold", () => { subFolderNameHelper: "upperCase", }) - const data = readFileSync(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") }) @@ -321,7 +322,7 @@ describe("Scaffold", () => { }, }) - const data = readFileSync(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") }) }) From cb6e06f1c944210a93c23b0e6c7ac578cf140316 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Thu, 3 Mar 2022 21:43:55 +0200 Subject: [PATCH 6/7] bump version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d80c1a..97b2e2d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simple-scaffold", - "version": "1.0.1", + "version": "1.0.2", "description": "Create files based on templates", "repository": "https://github.com/chenasraf/simple-scaffold.git", "author": "Chen Asraf ", From f07df79e826b8fe8ee434e17ccb3fd6d577e49ae Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Thu, 3 Mar 2022 21:48:13 +0200 Subject: [PATCH 7/7] improved test --- tests/utils.test.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/utils.test.ts b/tests/utils.test.ts index ecc820d..c9d071b 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -40,9 +40,15 @@ describe("Utils", () => { }) }) test("should not do path escaping on non-path compiles", async () => { - expect(handlebarsParse(blankConf, "/home/test/{{name}} \\{{escaped}}.txt", { isPath: false })).toEqual( - "/home/test/test {{escaped}}.txt" - ) + expect( + handlebarsParse( + { ...blankConf, data: { ...blankConf.data, escaped: "value" } }, + "/home/test/{{name}} \\{{escaped}}.txt", + { + isPath: false, + } + ) + ).toEqual("/home/test/test {{escaped}}.txt") }) }) })