From 385829aa27db9ce46e4928e84e22463755f923a6 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Thu, 2 Dec 2021 11:46:08 +0200 Subject: [PATCH] fix errors, fix nested output --- .vscode/settings.json | 7 +++- package.json | 2 +- src/scaffold.ts | 80 +++++++++++++++++++++++++++--------------- src/utils.ts | 16 +++++++-- tests/scaffold.test.ts | 62 +++++++++++++++++++++----------- 5 files changed, 113 insertions(+), 54 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e264766..8b3c96c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,11 @@ "typescript.tsdk": "./node_modules/typescript/lib", "npm.packageManager": "yarn", "cSpell.words": [ - "massarg" + "massarg", + "nodir", + "nobrace", + "noext", + "nocomment", + "nonegate" ] } diff --git a/package.json b/package.json index 67396db..1ad1b8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simple-scaffold", - "version": "1.0.0-alpha.9", + "version": "1.0.0-alpha.10", "description": "Create files based on templates", "repository": "https://github.com/chenasraf/simple-scaffold.git", "author": "Chen Asraf ", diff --git a/src/scaffold.ts b/src/scaffold.ts index d1d8ebc..58aff3d 100644 --- a/src/scaffold.ts +++ b/src/scaffold.ts @@ -18,8 +18,8 @@ import { import { LogLevel, ScaffoldConfig } from "./types" export async function Scaffold(config: ScaffoldConfig) { + const options = { ...config } try { - const options = { ...config } const data = { name: options.name, Name: pascalCase(options.name), ...options.data } log(options, LogLevel.Debug, "Full config:", { name: options.name, @@ -29,35 +29,64 @@ export async function Scaffold(config: ScaffoldConfig) { data: options.data, overwrite: options.overwrite, quiet: options.quiet, + verbose: `${options.verbose} (${Object.keys(LogLevel).find( + (k) => (LogLevel[k as any] as unknown as number) === options.verbose! + )})`, }) log(options, LogLevel.Info, "Data:", data) for (let template of config.templates) { try { const _isGlob = template.includes("*") + if (!_isGlob && !(await pathExists(template))) { + const err: NodeJS.ErrnoException = new Error(`ENOENT, no such file or directory ${template}`) + err.code = "ENOENT" + err.path = "non-existing-input" + err.errno = -2 + throw err + } const _nonGlobTemplate = _isGlob ? removeGlob(template) : template - const _isDir = _isGlob ? false : await isDir(template) - const _shouldAddGlob = !_isGlob && !_isDir + log(options, LogLevel.Debug, "before isDir", "isGlob:", _isGlob, template) + const _isDir = _isGlob ? true : await isDir(template) + log(options, LogLevel.Debug, "after isDir", _isDir) + const _shouldAddGlob = !_isGlob && _isDir + const origTemplate = template if (_shouldAddGlob) { template = template + "/**/*" } - const files = await promisify(glob)(template, { dot: true, debug: false }) - for (const templatePath of files) { - if (!(await isDir(templatePath))) { + log(options, LogLevel.Debug, "before glob") + const files = await promisify(glob)(template, { + dot: true, + debug: false, + nodir: options.verbose === LogLevel.Debug, + nobrace: true, + noext: true, + nocomment: true, + nonegate: true, + }) + log(options, LogLevel.Debug, "after glob") + for (const inputFilePath of files) { + if (!(await isDir(inputFilePath))) { const basePath = path - .resolve( - process.cwd(), - _isDir - ? templatePath.replace(template, "") - : path.dirname(removeGlob(templatePath).replace(_nonGlobTemplate, "")) - ) + .resolve(process.cwd(), path.dirname(removeGlob(inputFilePath).replace(_nonGlobTemplate, "")).slice(1)) .replace(process.cwd() + "/", "") .replace(process.cwd(), "") log( options, LogLevel.Debug, - `\ntemplate: ${template}\ntemplatePath: ${templatePath}, \nbase path: ${basePath}\n` + `\nprocess.cwd(): ${process.cwd()}`, + `\norigTemplate: ${origTemplate}`, + `\nremoveGlob(inputFilePath).replace(_nonGlobTemplate, "").slice(1): ${removeGlob(inputFilePath) + .replace(_nonGlobTemplate, "") + .slice(1)}`, + `\ntemplate: ${template}`, + `\ninputFilePath: ${inputFilePath}`, + `\nnonGlobTemplate: ${_nonGlobTemplate}`, + `\nbase path: ${basePath}`, + `\nisDir: ${_isDir}`, + `\nisGlob: ${_isGlob}`, + `\n` ) - await handleTemplateFile(templatePath, basePath, options, data) + await handleTemplateFile(inputFilePath, basePath, options, data) } } } catch (e: any) { @@ -65,7 +94,7 @@ export async function Scaffold(config: ScaffoldConfig) { } } } catch (e: any) { - console.error(e) + log(options, LogLevel.Error, e) throw e } } @@ -84,30 +113,22 @@ async function handleTemplateFile( process.cwd(), ...([outputPathOpt, basePath, options.createSubFolder ? options.name : undefined].filter(Boolean) as string[]) ) + const outputPath = handlebarsParse(path.join(outputDir, path.basename(inputPath)), data) log( options, LogLevel.Debug, `\nParsing ${templatePath}`, `\nBase path: ${basePath}`, `\nFull input path: ${inputPath}`, - `\nFull output path: ${outputDir}\n` + `\nOutput Path Opt: ${outputPathOpt}`, + `\nFull output dir: ${outputDir}`, + `\nFull output path: ${outputPath}`, + `\n` ) - const outputPath = path.join(outputDir, handlebarsParse(path.basename(inputPath), data)) const overwrite = getOptionValueForFile(inputPath, data, options.overwrite ?? false) const exists = await pathExists(outputPath) - log( - options, - LogLevel.Debug, - "Filename parsed:", - handlebarsParse(path.basename(inputPath), data), - "Orig:", - path.basename(inputPath) - // "Test:", - // handlebarsParse("{{name}} {{name pascalCase}}", data) - ) - - await createDirIfNotExists(outputDir, options) + await createDirIfNotExists(path.dirname(outputPath), options) log(options, LogLevel.Info, `Writing to ${outputPath}`) if (!exists || overwrite) { @@ -119,6 +140,7 @@ async function handleTemplateFile( if (!options.dryRun) { await writeFile(outputPath, outputContents) + log(options, LogLevel.Info, "Done.") } else { log(options, LogLevel.Info, "Content output:") log(options, LogLevel.Info, outputContents) diff --git a/src/utils.ts b/src/utils.ts index 187d340..bf48803 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -28,7 +28,7 @@ export function handleErr(err: NodeJS.ErrnoException | null) { } export function log(options: ScaffoldConfig, level: LogLevel, ...obj: any[]) { - if (options.quiet || options.verbose === LogLevel.None || (options.verbose ?? LogLevel.Info) > level) { + if (options.quiet || options.verbose === LogLevel.None || level <= (options.verbose ?? LogLevel.Info)) { return } const levelColor: Record = { @@ -39,8 +39,17 @@ export function log(options: ScaffoldConfig, level: LogLevel, ...obj: any[]) { [LogLevel.Error]: "red", } const chalkFn: any = chalk[levelColor[level]] - console["log"](...obj.map((i) => (typeof i === "object" ? chalkFn(JSON.stringify(i, undefined, 1)) : chalkFn(i)))) - // console["log"](...obj) + const key: "log" | "warn" | "error" = level === LogLevel.Error ? "error" : level === LogLevel.Warning ? "warn" : "log" + const logFn: any = console[key] + logFn( + ...obj.map((i) => + i instanceof Error + ? chalkFn(i, JSON.stringify(i, undefined, 1), i.stack) + : typeof i === "object" + ? chalkFn(JSON.stringify(i, undefined, 1)) + : chalkFn(i) + ) + ) } export async function createDirIfNotExists(dir: string, options: ScaffoldConfig): Promise { @@ -52,6 +61,7 @@ export async function createDirIfNotExists(dir: string, options: ScaffoldConfig) if (!(await pathExists(dir))) { try { + log(options, LogLevel.Debug, `Creating dir ${dir}`) await mkdir(dir) return } catch (e: any) { diff --git a/tests/scaffold.test.ts b/tests/scaffold.test.ts index 7e63f21..ba6bf4e 100644 --- a/tests/scaffold.test.ts +++ b/tests/scaffold.test.ts @@ -2,6 +2,7 @@ import mockFs from "mock-fs" import FileSystem from "mock-fs/lib/filesystem" import Scaffold from "../src/scaffold" import { readdirSync, readFileSync } from "fs" +import { Console } from "console" const fileStructNormal = { input: { @@ -19,21 +20,32 @@ const fileStructWithData = { const fileStructNested = { input: { - "{{name}}-1.text": "This should be in root", + "{{name}}-1.txt": "This should be in root", "{{Name}}": { "{{name}}-2.txt": "Hello, my value is {{value}}", + moreNesting: { + "{{name}}-3.txt": "Hi! My value is actually NOT {{value}}!", + }, }, }, output: {}, } - +// let logsTemp: any = [] +// let logMock: any function withMock(fileStruct: FileSystem.DirectoryItems, testFn: jest.EmptyFunction): jest.EmptyFunction { return () => { beforeEach(() => { + // console.log("Mocking:", fileStruct) + console = new Console(process.stdout, process.stderr) + mockFs(fileStruct) + // logMock = jest.spyOn(console, 'log').mockImplementation((...args) => { + // logsTemp.push(args) + // }) }) testFn() afterEach(() => { + // console.log("Restoring mock") mockFs.restore() }) } @@ -48,9 +60,8 @@ describe("Scaffold", () => { name: "app_name", output: "output", templates: ["input"], - quiet: true, + verbose: 0, }) - const data = readFileSync(process.cwd() + "/output/app_name.txt") expect(data.toString()).toBe("Hello, my app is app_name") }) @@ -61,7 +72,7 @@ describe("Scaffold", () => { output: "output", templates: ["input"], createSubFolder: true, - quiet: true, + verbose: 0, }) const data = readFileSync(process.cwd() + "/output/app_name/app_name.txt") @@ -79,7 +90,7 @@ describe("Scaffold", () => { output: "output", templates: ["input"], data: { value: "1" }, - quiet: true, + verbose: 0, }) await Scaffold({ @@ -87,7 +98,7 @@ describe("Scaffold", () => { output: "output", templates: ["input"], data: { value: "2" }, - quiet: true, + verbose: 0, }) const data = readFileSync(process.cwd() + "/output/app_name.txt") @@ -100,7 +111,7 @@ describe("Scaffold", () => { output: "output", templates: ["input"], data: { value: "1" }, - quiet: true, + verbose: 0, }) await Scaffold({ @@ -109,7 +120,7 @@ describe("Scaffold", () => { templates: ["input"], data: { value: "2" }, overwrite: true, - quiet: true, + verbose: 0, }) const data = readFileSync(process.cwd() + "/output/app_name.txt") @@ -121,9 +132,13 @@ describe("Scaffold", () => { describe( "errors", withMock(fileStructNormal, () => { - let consoleMock: jest.SpyInstance + let consoleMock1: jest.SpyInstance beforeAll(() => { - consoleMock = jest.spyOn(console, "error").mockImplementation(() => void 0) + consoleMock1 = jest.spyOn(console, "error").mockImplementation(() => void 0) + }) + + afterAll(() => { + consoleMock1.mockRestore() }) test("should throw for bad input", async () => { @@ -133,16 +148,12 @@ describe("Scaffold", () => { output: "output", templates: ["non-existing-input"], data: { value: "1" }, - quiet: true, + verbose: 0, }) ).rejects.toThrow() expect(() => readFileSync(process.cwd() + "/output/app_name.txt")).toThrow() }) - - afterAll(() => { - consoleMock.mockRestore() - }) }) ) @@ -155,7 +166,7 @@ describe("Scaffold", () => { output: (fullPath, basedir, basename) => `custom-output/${basename.split(".")[0]}`, templates: ["input"], data: { value: "1" }, - quiet: true, + verbose: 0, }) const data = readFileSync(process.cwd() + "/custom-output/app_name/app_name.txt") expect(data.toString()).toBe("Hello, my app is app_name") @@ -169,14 +180,25 @@ describe("Scaffold", () => { test("should maintain input structure on output", async () => { await Scaffold({ name: "app_name", - output: "./", + output: "output", templates: ["input"], data: { value: "1" }, - quiet: true, + verbose: 0, }) - const dir = readdirSync(process.cwd()) + const rootDir = readdirSync(process.cwd() + "/output") + const dir = readdirSync(process.cwd() + "/output/AppName") + const nestedDir = readdirSync(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") + 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!") }) }) )