Compare commits

...

10 Commits

Author SHA1 Message Date
Chen Asraf
e26a434dba update README.md 2022-03-03 21:53:50 +02:00
Chen Asraf
1783ddf230 remove unnecessary package [skip ci] 2022-03-03 21:53:08 +02:00
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
8 changed files with 107 additions and 74 deletions

View File

@@ -104,7 +104,7 @@ const config = {
output: path.join(__dirname, "src", "components"),
createSubFolder: true,
subFolderNameHelper: "upperCase"
locals: {
data: {
property: "value",
},
helpers: {
@@ -290,5 +290,5 @@ Some tips on getting around the code:
enabling you to test different behaviors. See `yarn cmd -h` for more information.
> This requires an updated build, and does not trigger one itself. Either use `yarn dev` to watch
> for changes and build, or `yarn build` before running this, or use `yarn build-cmd` instead,
> for changes and build, or `yarn build` before running this, or use `yarn build-cmd` instead,
> which triggers a build right before running the command with the rest of the given arguments.

View File

@@ -1,6 +1,6 @@
{
"name": "simple-scaffold",
"version": "1.0.1",
"version": "1.0.3",
"description": "Create files based on templates",
"repository": "https://github.com/chenasraf/simple-scaffold.git",
"author": "Chen Asraf <inbox@casraf.com>",
@@ -18,7 +18,6 @@
"build-cmd": "yarn build && yarn cmd"
},
"dependencies": {
"args": "^5.0.1",
"chalk": "^4.1.2",
"glob": "^7.1.3",
"handlebars": "^4.7.7",

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

View File

@@ -759,16 +759,6 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
args@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/args/-/args-5.0.1.tgz#4bf298df90a4799a09521362c579278cc2fdd761"
integrity sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==
dependencies:
camelcase "5.0.0"
chalk "2.4.2"
leven "2.1.0"
mri "1.1.4"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -903,11 +893,6 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camelcase@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42"
integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==
camelcase@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
@@ -923,7 +908,7 @@ caniuse-lite@^1.0.30001219:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001245.tgz#45b941bbd833cb0fa53861ff2bae746b3c6ca5d4"
integrity sha512-768fM9j1PKXpOCKws6eTo3RHmvTUsG9UrpT4WoREFeZgJBTi4/X9g565azS/rVUGtqb8nt7FjLeF5u4kukERnA==
chalk@2.4.2, chalk@^2.0.0:
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -2105,11 +2090,6 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
leven@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA=
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -2221,11 +2201,6 @@ mock-fs@^5.0.0:
resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-5.0.0.tgz#5574520ac824c01a10091bf951c66f677c71acaa"
integrity sha512-A5mm/SpSDwwc/klSaEvvKMGQQtiGiQy8UcDAd/vpVO1fV+4zaHjt39yKgCSErFzv2zYxZIUx9Ud/7ybeHBf8Fg==
mri@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a"
integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"