Compare commits

...

12 Commits

Author SHA1 Message Date
Chen Asraf
385829aa27 fix errors, fix nested output 2021-12-02 11:46:08 +02:00
Chen Asraf
33c357bccc remove types from package.json 2021-11-28 15:50:27 +02:00
Chen Asraf
b74b781a5b fix yarn.lock 2021-11-28 15:45:51 +02:00
Chen Asraf
57410c8d74 fix log level 0 2021-11-28 15:34:59 +02:00
Chen Asraf
f81cfd8ae1 add export for cmd_util 2021-11-28 15:28:48 +02:00
Chen Asraf
414494734d bump version number 2021-11-28 14:24:01 +02:00
Chen Asraf
89b588f64e update workflows [skip publish] 2021-11-28 14:09:25 +02:00
Chen Asraf
cf22e2e62f fixes + add log level [skip publish] 2021-11-28 14:07:25 +02:00
Chen Asraf
246c139061 fix readme [skip publish] 2021-11-18 15:25:43 +02:00
Chen Asraf
711d8f0333 try fix release upload 2021-11-18 14:22:23 +02:00
Chen Asraf
8b22e96329 try new release version 2021-11-18 14:19:50 +02:00
Chen Asraf
53c0842ab8 fixed release tarball file location 2021-11-18 14:18:18 +02:00
14 changed files with 315 additions and 148 deletions

View File

@@ -14,8 +14,10 @@ jobs:
node-version: "12.x"
- run: yarn install --frozen-lockfile
- run: yarn build
- run: cd ./dist && yarn pack dist/ --filename=../release.tgz
- run: cd ./dist && yarn pack --filename=../package.tgz
if: "!contains(github.event.head_commit.message, '[skip publish]')"
- uses: Klemensas/action-autotag@stable
if: "!contains(github.event.head_commit.message, '[skip publish]')"
id: update_tag
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -14,8 +14,10 @@ jobs:
node-version: "12.x"
- run: yarn install --frozen-lockfile
- run: yarn build
- run: cd ./dist && yarn pack dist/ --filename=../release.tgz
- run: cd ./dist && yarn pack --filename=../package.tgz
if: "!contains(github.event.head_commit.message, '[skip publish]')"
- uses: Klemensas/action-autotag@stable
if: "!contains(github.event.head_commit.message, '[skip publish]')"
id: update_tag
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"

1
.gitignore vendored
View File

@@ -60,3 +60,4 @@ typings/
examples/test-output/**/*
dist/
.DS_Store
tmp/

View File

@@ -2,6 +2,11 @@
"typescript.tsdk": "./node_modules/typescript/lib",
"npm.packageManager": "yarn",
"cSpell.words": [
"massarg"
"massarg",
"nodir",
"nobrace",
"noext",
"nocomment",
"nonegate"
]
}

View File

@@ -22,6 +22,8 @@ npx simple-scaffold <...args>
```plaintext
Usage: simple-scaffold [options]
Create structured files based on templates.
Options:
--help|-h Display help information
@@ -35,7 +37,8 @@ Options:
--templates|-t Template files to use as input. You may provide multiple
files, each of which can be a relative or absolute path, or a glob
pattern for multiple file matching easily.
pattern for multiple file matching easily. (default:
)
--overwrite|-w Enable to override output files, even if they already exist.
(default: false)
@@ -46,19 +49,27 @@ Options:
--create-sub-folder|-s Create subfolder with the input name (default:
false)
--quiet|-q Suppress output logs (default:
false)
--quiet|-q Suppress output logs (Same as --verbose 0)
(default: false)
--dry-run|-dr Don't emit actual files. This is good for testing your
scaffolds and making sure they don't fail, without having to write
actual files. (default: false)
--verbose|-v Determine amount of logs to display. The values are: 0
(none) | 1 (debug) | 2 (info) | 3 (warn) | 4 (error). The
provided level will display messages of the same level or higher.
(default: 2)
--dry-run|-dr Don't emit files. This is good for testing your scaffolds and
making sure they don't fail, without having to write actual file
contents or create directories. (default:
false)
```
You can also add this as a script in your `package.json`:
```json
{
...
"scripts": {
...
"scaffold": "yarn simple-scaffold --templates scaffolds/component/**/* --output src/components --data '{\"myProp\": \"propName\", \"myVal\": \"123\"}'"
}
}
@@ -73,7 +84,7 @@ The config takes similar arguments to the command line:
```javascript
const SimpleScaffold = require("simple-scaffold").default
const scaffold = new SimpleScaffold({
const scaffold = SimpleScaffold({
name: "component",
templates: [path.join(__dirname, "scaffolds", "component")],
output: path.join(__dirname, "src", "components"),
@@ -81,7 +92,7 @@ const scaffold = new SimpleScaffold({
locals: {
property: "value",
},
}).run()
})
```
The exception in the config is that `output`, when used in Node directly, may also be passed a
@@ -90,7 +101,7 @@ function for each input file to output into a dynamic path:
```javascript
config.output = (fullPath, baseDir, baseName) => {
console.log({ fullPath, baseDir, baseName })
return [baseDir, baseName].join(path.sep)
return path.resolve(baseDir, baseName)
}
```
@@ -118,21 +129,23 @@ Your `data` will be pre-populated with the following:
> Any `data` you add in the config will be available for use with their names wrapped in
> `{{` and `}}`.
#### Helpers
Simple-Scaffold provides some built-in text transformation filters usable by handleBars.
For example, you may use `{{ name | snakeCase }}` inside a template file or filename, and it will
For example, you may use `{{ snakeCase name }}` inside a template file or filename, and it will
replace `My Name` with `my_name` when producing the final value.
Here are the built-in helpers available for use:
```plaintext
{{ name | camelCase }} => myName
{{ name | snakeCase }} => my_name
{{ name | startCase }} => My Name
{{ name | kebabCase }} => my-name
{{ name | hyphenCase }} => my-name
{{ name | pascalCase }} => MyName
```
| Helper name | Example code | Example output |
| ----------- | ----------------------- | -------------- |
| camelCase | `{{ camelCase name }}` | myName |
| snakeCase | `{{ snakeCase name }}` | my_name |
| startCase | `{{ startCase name }}` | My Name |
| kebabCase | `{{ kebabCase name }}` | my-name |
| hyphenCase | `{{ hyphenCase name }}` | my-name |
| pascalCase | `{{ pascalCase name }}` | MyName |
**Note:** These helpers are available for any data property, not exclusive to `name`.
@@ -144,7 +157,8 @@ Here are the built-in helpers available for use:
simple-scaffold MyComponent \
-t project/scaffold/**/* \
-o src/components \
-d '{"className":"myClassName"}'
-d '{"className": "myClassName"}'
MyComponent
```
### Example Scaffold Input
@@ -185,7 +199,7 @@ module.exports = class {{Name}} extends React.Component {
- ...
```
With `createSubfolder = false`:
With `createSubFolder = false`:
```plaintext
- project
@@ -202,7 +216,7 @@ const React = require("react")
module.exports = class MyComponent extends React.Component {
render() {
<div className="my-component">MyComponent Component</div>
<div className="myClassName">MyComponent Component</div>
}
}
```

View File

@@ -1,13 +1,12 @@
{
"name": "simple-scaffold",
"version": "1.0.0-alpha.3",
"version": "1.0.0-alpha.10",
"description": "Create files based on templates",
"repository": "https://github.com/chenasraf/simple-scaffold.git",
"author": "Chen Asraf <inbox@casraf.com>",
"license": "MIT",
"main": "index.js",
"bin": "cmd.js",
"types": "types.d.ts",
"scripts": {
"clean": "rm -rf dist/",
"build": "yarn clean && tsc && chmod -R +x ./dist && cp ./package.json ./dist/ && cp ./README.md ./dist/",
@@ -20,10 +19,11 @@
},
"dependencies": {
"args": "^5.0.1",
"chalk": "^4.1.2",
"glob": "^7.1.3",
"handlebars": "^4.7.7",
"lodash": "^4.17.21",
"massarg": "^0.1.2",
"massarg": "^1.0.3",
"util.promisify": "^1.1.1"
},
"devDependencies": {

View File

@@ -1,69 +1,2 @@
import Scaffold from "./scaffold"
import massarg from "massarg"
import { ScaffoldCmdConfig } from "./types"
massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
.main(Scaffold)
.option({
name: "name",
aliases: ["n"],
isDefault: true,
description:
"Name to be passed to the generated files. {{name}} and {{Name}} inside contents and file names will be replaced accordingly.",
})
.option({
name: "output",
aliases: ["o"],
description:
"Path to output to. If --create-sub-folder is enabled, the subfolder will be created inside this path.",
})
.option({
name: "templates",
aliases: ["t"],
description:
"Template files to use as input. You may provide multiple files, each of which can be a relative or absolute path, " +
"or a glob pattern for multiple file matching easily.",
defaultValue: [],
array: true,
})
.option({
aliases: ["w"],
name: "overwrite",
description: "Enable to override output files, even if they already exist.",
defaultValue: false,
boolean: true,
})
.option({
aliases: ["d"],
name: "data",
description: "Add custom data to the templates. By default, only your app name is included.",
parse: (v) => JSON.parse(v),
})
.option({
aliases: ["s"],
name: "create-sub-folder",
description: "Create subfolder with the input name",
defaultValue: false,
boolean: true,
})
.option({ aliases: ["q"], name: "quiet", description: "Suppress output logs", defaultValue: false, boolean: true })
.option({
aliases: ["dr"],
name: "dry-run",
description:
"Don't emit files. This is good for testing your scaffolds and making sure they " +
"don't fail, without having to write actual file contents or create directories.",
defaultValue: false,
boolean: true,
})
// .example({
// input: `yarn cmd -t examples/test-input/Component -o examples/test-output -d '{"property":"myProp","value":"10"}'`,
// description: "Usage",
// output: "",
// })
.help({
binName: "simple-scaffold",
useGlobalColumns: true,
usageExample: "[options]",
})
.parse()
import { parseCliArgs } from "./cmd_util"
parseCliArgs()

89
src/cmd_util.ts Normal file
View File

@@ -0,0 +1,89 @@
import massarg from "massarg"
import chalk from "chalk"
import { LogLevel, ScaffoldCmdConfig } from "./types"
import { Scaffold } from "./scaffold"
export function parseCliArgs(args = process.argv.slice(2)) {
return (
massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
.main(Scaffold)
.option({
name: "name",
aliases: ["n"],
isDefault: true,
description:
"Name to be passed to the generated files. {{name}} and {{Name}} inside contents and file names will be replaced accordingly.",
})
.option({
name: "output",
aliases: ["o"],
description:
"Path to output to. If --create-sub-folder is enabled, the subfolder will be created inside this path.",
})
.option({
name: "templates",
aliases: ["t"],
description:
"Template files to use as input. You may provide multiple files, each of which can be a relative or absolute path, " +
"or a glob pattern for multiple file matching easily.",
defaultValue: [],
array: true,
})
.option({
aliases: ["w"],
name: "overwrite",
description: "Enable to override output files, even if they already exist.",
defaultValue: false,
boolean: true,
})
.option({
aliases: ["d"],
name: "data",
description: "Add custom data to the templates. By default, only your app name is included.",
parse: (v) => JSON.parse(v),
})
.option({
aliases: ["s"],
name: "create-sub-folder",
description: "Create subfolder with the input name",
defaultValue: false,
boolean: true,
})
.option({
aliases: ["q"],
name: "quiet",
description: "Suppress output logs (Same as --verbose 0)",
defaultValue: false,
boolean: true,
})
.option({
aliases: ["v"],
name: "verbose",
description: `Determine amount of logs to display. The values are: ${chalk.bold`0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4 (error)`}. The provided level will display messages of the same level or higher.`,
defaultValue: LogLevel.Info,
parse: Number,
})
.option({
aliases: ["dr"],
name: "dry-run",
description:
"Don't emit files. This is good for testing your scaffolds and making sure they " +
"don't fail, without having to write actual file contents or create directories.",
defaultValue: false,
boolean: true,
})
// .example({
// input: `yarn cmd -t examples/test-input/Component -o examples/test-output -d '{"property":"myProp","value":"10"}'`,
// description: "Usage",
// output: "",
// })
.help({
binName: "simple-scaffold",
useGlobalColumns: true,
usageExample: "[options]",
header: "Create structured files based on templates.",
footer: `Copyright © Chen Asraf 2021\nNPM: ${chalk.underline`https://npmjs.com/package/massarg`}\nGitHub: ${chalk.underline`https://github.com/chenasraf/massarg`}`,
})
.parse(args)
)
}

View File

@@ -1,3 +1,4 @@
export * from "./scaffold"
export * from "./types"
import Scaffold from "./scaffold"
export default Scaffold

View File

@@ -13,14 +13,15 @@ import {
pathExists,
pascalCase,
isDir,
removeGlob,
} from "./utils"
import { ScaffoldConfig } from "./types"
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, "Config:", {
log(options, LogLevel.Debug, "Full config:", {
name: options.name,
templates: options.templates,
output: options.output,
@@ -28,21 +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, "Data:", data)
log(options, LogLevel.Info, "Data:", data)
for (let template of config.templates) {
try {
const _isDir = await isDir(template)
const basePath = path
.resolve(process.cwd(), _isDir ? template : path.dirname(template.replace("*", "").replace("//", "/")))
.replace(process.cwd(), ".")
if (_isDir) {
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
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))) {
await handleTemplateFile(templatePath, basePath, options, data)
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(), path.dirname(removeGlob(inputFilePath).replace(_nonGlobTemplate, "")).slice(1))
.replace(process.cwd() + "/", "")
.replace(process.cwd(), "")
log(
options,
LogLevel.Debug,
`\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(inputFilePath, basePath, options, data)
}
}
} catch (e: any) {
@@ -50,7 +94,7 @@ export async function Scaffold(config: ScaffoldConfig) {
}
}
} catch (e: any) {
console.error(e)
log(options, LogLevel.Error, e)
throw e
}
}
@@ -63,35 +107,46 @@ async function handleTemplateFile(
): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
log(options, `Parsing ${templatePath}`)
const inputPath = path.join(process.cwd(), templatePath)
const inputPath = path.resolve(process.cwd(), templatePath)
const outputPathOpt = getOptionValueForFile(inputPath, data, options.output)
const outputDir = path.resolve(
process.cwd(),
...([outputPathOpt, options.createSubFolder ? options.name : undefined].filter(Boolean) as string[])
...([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}`,
`\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)
await createDirIfNotExists(outputDir, options)
await createDirIfNotExists(path.dirname(outputPath), options)
log(options, `Writing to ${outputPath}`)
log(options, LogLevel.Info, `Writing to ${outputPath}`)
if (!exists || overwrite) {
if (exists && overwrite) {
log(options, `File ${outputPath} exists, overwriting`)
log(options, LogLevel.Info, `File ${outputPath} exists, overwriting`)
}
const templateBuffer = await readFile(inputPath)
const outputContents = handlebarsParse(templateBuffer, data)
if (!options.dryRun) {
await writeFile(outputPath, outputContents)
log(options, LogLevel.Info, "Done.")
} else {
log(options, "Content output:")
log(options, outputContents)
log(options, LogLevel.Info, "Content output:")
log(options, LogLevel.Info, outputContents)
}
} else if (exists) {
log(options, `File ${outputPath} already exists, skipping`)
log(options, LogLevel.Info, `File ${outputPath} already exists, skipping`)
}
resolve()
} catch (e: any) {

View File

@@ -1,3 +1,11 @@
export enum LogLevel {
None = 0,
Debug = 1,
Info = 2,
Warning = 3,
Error = 4,
}
export type FileResponseFn<T> = (fullPath: string, basedir: string, basename: string) => T
export type FileResponse<T> = T | FileResponseFn<T>
@@ -5,12 +13,15 @@ export type FileResponse<T> = T | FileResponseFn<T>
export interface ScaffoldConfig {
/** The name supplied for the output templates */
name: string
/** Template input files/dirs/glob patterns to use as template input. These will be copied to the output directory. */
templates: string[]
/** Output directory to put scaffolded files in. */
output: FileResponse<string>
createSubFolder?: boolean
data?: Record<string, string>
overwrite?: FileResponse<boolean>
quiet?: boolean
verbose?: LogLevel
dryRun?: boolean
}
export interface ScaffoldCmdConfig {

View File

@@ -1,12 +1,13 @@
import path from "path"
import { F_OK } from "constants"
import { FileResponse, FileResponseFn, ScaffoldConfig } from "./types"
import { FileResponse, FileResponseFn, LogLevel, ScaffoldConfig } from "./types"
import camelCase from "lodash/camelCase"
import snakeCase from "lodash/snakeCase"
import kebabCase from "lodash/kebabCase"
import startCase from "lodash/startCase"
import Handlebars from "handlebars"
import { promises as fsPromises } from "fs"
import chalk from "chalk"
const { stat, access, mkdir } = fsPromises
const helpers = {
@@ -26,11 +27,29 @@ export function handleErr(err: NodeJS.ErrnoException | null) {
if (err) throw err
}
export function log(options: ScaffoldConfig, ...obj: any[]) {
if (options.quiet) {
export function log(options: ScaffoldConfig, level: LogLevel, ...obj: any[]) {
if (options.quiet || options.verbose === LogLevel.None || level <= (options.verbose ?? LogLevel.Info)) {
return
}
console["log"](...obj)
const levelColor: Record<LogLevel, keyof typeof chalk> = {
[LogLevel.None]: "reset",
[LogLevel.Debug]: "blue",
[LogLevel.Info]: "dim",
[LogLevel.Warning]: "yellow",
[LogLevel.Error]: "red",
}
const chalkFn: any = chalk[levelColor[level]]
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<void> {
@@ -42,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) {
@@ -95,3 +115,7 @@ export async function isDir(path: string): Promise<boolean> {
const tplStat = await stat(path)
return tplStat.isDirectory()
}
export function removeGlob(template: string) {
return template.replace(/\*/g, "").replace(/\/\//g, "/")
}

View File

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

View File

@@ -940,6 +940,14 @@ chalk@^4.0.0, chalk@^4.1.1:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
char-regex@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
@@ -2153,10 +2161,10 @@ makeerror@1.0.x:
dependencies:
tmpl "1.0.x"
massarg@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/massarg/-/massarg-0.1.2.tgz#f298741318172be14f3d2b701329fbe10eff41bb"
integrity sha512-gpFIjsvOoqyQnrqNDytQXPljOGlX5lvJFGYzAIqjxDqiSZwHOvz+/YfjtzrFvokfYsk0uZbE/XOH4LVRiu/1cg==
massarg@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/massarg/-/massarg-1.0.3.tgz#228cf2d84896924b6b12021ea23ed9a9077e15b7"
integrity sha512-LYb4XvAQ+PbBClyfkn9B4JtfwycfpnOnGIznALt9YLnrmQaCcXXJBQsG5SA/2w+bmLOeYRoR9GqqFLyaniCn9g==
dependencies:
chalk "^4.1.1"
lodash "^4.17.21"