mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-18 01:29:09 +00:00
fixes + add log level [skip publish]
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -60,3 +60,4 @@ typings/
|
||||
examples/test-output/**/*
|
||||
dist/
|
||||
.DS_Store
|
||||
tmp/
|
||||
|
||||
39
README.md
39
README.md
@@ -43,15 +43,18 @@ Options:
|
||||
--data|-d Add custom data to the templates. By default, only your app
|
||||
name is included.
|
||||
|
||||
--create-sub-folder|-s Create subfolder with the input name (default:
|
||||
false)
|
||||
--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`:
|
||||
@@ -73,7 +76,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 +84,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
|
||||
@@ -120,19 +123,19 @@ Your `data` will be pre-populated with the following:
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
17
src/cmd.ts
17
src/cmd.ts
@@ -1,6 +1,7 @@
|
||||
import Scaffold from "./scaffold"
|
||||
import massarg from "massarg"
|
||||
import { ScaffoldCmdConfig } from "./types"
|
||||
import { LogLevel } from "."
|
||||
|
||||
massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
|
||||
.main(Scaffold)
|
||||
@@ -46,7 +47,21 @@ massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
|
||||
defaultValue: false,
|
||||
boolean: true,
|
||||
})
|
||||
.option({ aliases: ["q"], name: "quiet", description: "Suppress output logs", 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: 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",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./scaffold"
|
||||
export * from "./types"
|
||||
import Scaffold from "./scaffold"
|
||||
export default Scaffold
|
||||
|
||||
@@ -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) {
|
||||
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,
|
||||
@@ -29,19 +30,33 @@ export async function Scaffold(config: ScaffoldConfig) {
|
||||
overwrite: options.overwrite,
|
||||
quiet: options.quiet,
|
||||
})
|
||||
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("*")
|
||||
const _nonGlobTemplate = _isGlob ? removeGlob(template) : template
|
||||
const _isDir = _isGlob ? false : await isDir(template)
|
||||
const _shouldAddGlob = !_isGlob && !_isDir
|
||||
if (_shouldAddGlob) {
|
||||
template = template + "/**/*"
|
||||
}
|
||||
const files = await promisify(glob)(template, { dot: true, debug: false })
|
||||
for (const templatePath of files) {
|
||||
if (!(await isDir(templatePath))) {
|
||||
const basePath = path
|
||||
.resolve(
|
||||
process.cwd(),
|
||||
_isDir
|
||||
? templatePath.replace(template, "")
|
||||
: path.dirname(removeGlob(templatePath).replace(_nonGlobTemplate, ""))
|
||||
)
|
||||
.replace(process.cwd() + "/", "")
|
||||
.replace(process.cwd(), "")
|
||||
log(
|
||||
options,
|
||||
LogLevel.Debug,
|
||||
`\ntemplate: ${template}\ntemplatePath: ${templatePath}, \nbase path: ${basePath}\n`
|
||||
)
|
||||
await handleTemplateFile(templatePath, basePath, options, data)
|
||||
}
|
||||
}
|
||||
@@ -63,23 +78,41 @@ 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[])
|
||||
)
|
||||
log(
|
||||
options,
|
||||
LogLevel.Debug,
|
||||
`\nParsing ${templatePath}`,
|
||||
`\nBase path: ${basePath}`,
|
||||
`\nFull input path: ${inputPath}`,
|
||||
`\nFull output path: ${outputDir}\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)
|
||||
|
||||
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)
|
||||
@@ -87,11 +120,11 @@ async function handleTemplateFile(
|
||||
if (!options.dryRun) {
|
||||
await writeFile(outputPath, outputContents)
|
||||
} 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) {
|
||||
|
||||
11
src/types.ts
11
src/types.ts
@@ -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 {
|
||||
|
||||
22
src/utils.ts
22
src/utils.ts
@@ -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,20 @@ 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.Info) > level) {
|
||||
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]]
|
||||
console["log"](...obj.map((i) => (typeof i === "object" ? chalkFn(JSON.stringify(i, undefined, 1)) : chalkFn(i))))
|
||||
// console["log"](...obj)
|
||||
}
|
||||
|
||||
export async function createDirIfNotExists(dir: string, options: ScaffoldConfig): Promise<void> {
|
||||
@@ -95,3 +105,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, "/")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user