Compare commits

...

10 Commits

Author SHA1 Message Date
Chen Asraf
4575f73d69 Windows compatibility
Fix transform of windows-style paths
2022-03-03 21:49:44 +02:00
Chen Asraf
f07df79e82 improved test 2022-03-03 21:48:13 +02:00
Chen Asraf
cb6e06f1c9 bump version number 2022-03-03 21:43:55 +02:00
Chen Asraf
a043a05bc9 updated tests 2022-03-03 21:42:31 +02:00
Chen Asraf
52cb3e7353 fixed more windows paths, updated tests 2022-03-03 12:25:39 +02:00
Chen Asraf
89d7897f4e refactor handlebarsParse - remove redundant arg 2022-03-03 10:31:30 +02:00
Chen Asraf
d6e1693074 import/file cleanup 2022-03-03 10:25:44 +02:00
Chen Asraf
56f1340093 fix transform of windows-style paths 2022-03-03 10:21:37 +02:00
Chen Asraf
8782f18a73 v1.0.1
Merge pull request #24 from chenasraf/alpha
2022-02-20 14:10:59 +02:00
Chen Asraf
21c4ab6e1a bump version number [skip ci] 2022-02-20 14:06:56 +02:00
6 changed files with 104 additions and 45 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "simple-scaffold",
"version": "1.0.1-pre.1",
"version": "1.0.2",
"description": "Create files based on templates",
"repository": "https://github.com/chenasraf/simple-scaffold.git",
"author": "Chen Asraf <inbox@casraf.com>",

View File

View File

@@ -1,16 +1,10 @@
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 +18,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`.

View File

@@ -95,19 +95,27 @@ export function getOptionValueForFile<T>(
}
return (fn as FileResponseFn<T>)(
filePath,
path.dirname(handlebarsParse(options, filePath, data).toString()),
path.basename(handlebarsParse(options, filePath, data).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<string, string>
{ isPath = false }: { isPath?: boolean } = {}
) {
const { data } = options
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")
@@ -137,26 +145,28 @@ export async function isDir(path: string): Promise<boolean> {
}
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 {
@@ -177,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 }
}
@@ -208,7 +218,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)), {
isPath: true,
}).toString()
const exists = await pathExists(outputPath)
return { inputPath, outputPathOpt, outputDir, outputPath, exists }
}
@@ -228,7 +240,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)
@@ -255,7 +267,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[])

View File

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

54
tests/utils.test.ts Normal file
View File

@@ -0,0 +1,54 @@
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
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(blankConf, "C:\\exports\\{{name}}.txt", { isPath: true })).toEqual(
"C:\\exports\\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, data: { ...blankConf.data, escaped: "value" } },
"/home/test/{{name}} \\{{escaped}}.txt",
{
isPath: false,
}
)
).toEqual("/home/test/test {{escaped}}.txt")
})
})
})