mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-18 01:29:09 +00:00
Compare commits
10 Commits
v1.0.1-pre
...
v1.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4575f73d69 | ||
|
|
f07df79e82 | ||
|
|
cb6e06f1c9 | ||
|
|
a043a05bc9 | ||
|
|
52cb3e7353 | ||
|
|
89d7897f4e | ||
|
|
d6e1693074 | ||
|
|
56f1340093 | ||
|
|
8782f18a73 | ||
|
|
21c4ab6e1a |
@@ -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>",
|
||||
|
||||
@@ -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`.
|
||||
|
||||
46
src/utils.ts
46
src/utils.ts
@@ -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[])
|
||||
|
||||
@@ -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
54
tests/utils.test.ts
Normal 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")
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user