Compare commits

..

29 Commits

Author SHA1 Message Date
Chen Asraf
027f5576ba fix copyright 2021-12-06 00:03:51 +02:00
Chen Asraf
5d630e7e62 added custom helpers 2021-12-06 00:00:18 +02:00
Chen Asraf
7073add836 update deps + add MIGRATION.md 2021-12-05 23:28:23 +02:00
Chen Asraf
561477f07b update deps + update cmd requirements 2021-12-05 00:49:10 +02:00
Chen Asraf
bfcc59c20a update readme [skip ci] 2021-12-03 01:28:40 +02:00
Chen Asraf
ba5332d550 update readme [skip ci] 2021-12-03 01:11:18 +02:00
Chen Asraf
f9bc0419f3 update docs [skip publish] 2021-12-03 01:02:14 +02:00
Chen Asraf
381c55835f fix basename in some cases 2021-12-03 00:39:03 +02:00
Chen Asraf
efdb7dc00b run tests on ci [skip publish] 2021-12-02 11:48:57 +02:00
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
Chen Asraf
48631325c1 fixed release tarball file location 2021-11-18 14:16:58 +02:00
Chen Asraf
045ad0118a fix build output files 2021-11-18 14:09:21 +02:00
Chen Asraf
b4dca7a954 add build step 2021-11-18 13:57:57 +02:00
Chen Asraf
7c42808f63 try to fix workflow 2021-11-18 13:56:24 +02:00
Chen Asraf
fd42013e8b publish: debug mode off, try to fix workflow 2021-11-18 13:55:30 +02:00
Chen Asraf
961a72fcdc publish: debug mode on 2021-11-18 13:51:29 +02:00
Chen Asraf
d6d99cfdf2 try fix workflow 2021-11-18 13:45:37 +02:00
Chen Asraf
ea4ecabe02 Merge pull request #12 from chenasraf/v1.0
v1.0
2021-11-18 13:44:09 +02:00
30 changed files with 497 additions and 1871 deletions

View File

@@ -3,6 +3,3 @@ tab_width = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

View File

@@ -1,37 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug, needs-triage
assignees: chenasraf
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Prepare templates:
```
template contents
```
2. Run with args/config:
```
npx simple-scaffold@latest -t ... -o output TplName
```
**Expected behavior**
A clear and concise description of what you expected to happen.
**Logs**
If applicable, paste your logs to help explain your problem.
To see more logs, run the scaffold with `-v 1` to enable debug logging.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Node.js: [e.g. 16.8]
- Simple Scaffold Version [e.g. 1.0.4]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,20 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement, needs-triage
assignees: chenasraf
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -2,7 +2,7 @@ name: Alpha Releases
on:
push:
branches: [develop]
branches: [alpha]
jobs:
build:
runs-on: ubuntu-latest
@@ -16,31 +16,30 @@ jobs:
- run: yarn test
- run: yarn build
- run: cd ./dist && yarn pack --filename=../package.tgz
if: "contains(github.event.head_commit.message, '[publish]')"
if: "!contains(github.event.head_commit.message, '[skip publish]')"
- uses: Klemensas/action-autotag@stable
if: "contains(github.event.head_commit.message, '[publish]')"
if: "!contains(github.event.head_commit.message, '[skip publish]')"
id: update_tag
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
tag_prefix: "v"
- name: Publish on NPM
uses: JS-DevTools/npm-publish@v1
if: "contains(github.event.head_commit.message, '[publish]')"
if: "!contains(github.event.head_commit.message, '[skip publish]')"
with:
package: ./dist/package.json
token: "${{ secrets.NPM_TOKEN }}"
- name: Create Release
if: steps.update_tag.outputs.tagname && contains(github.event.head_commit.message, '[publish]')
if: steps.update_tag.outputs.tagname && !contains(github.event.head_commit.message, '[skip publish]')
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prerelease: true
tag_name: ${{ steps.update_tag.outputs.tagname }}
release_name: Release ${{ steps.update_tag.outputs.tagname }}
- name: Upload Release Asset
if: steps.update_tag.outputs.tagname && contains(github.event.head_commit.message, '[publish]')
if: steps.update_tag.outputs.tagname && !contains(github.event.head_commit.message, '[skip publish]')
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
@@ -48,5 +47,5 @@ jobs:
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./package.tgz
asset_name: simple-scaffold ${{ steps.update_tag.outputs.tagname }}.tgz
asset_name: package.tgz
asset_content_type: application/tgz

View File

@@ -1,21 +0,0 @@
name: Build Documentation
on:
push:
branches: [master, develop]
jobs:
docs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.head_commit.message, '[skip docs]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: "12.x"
- run: cd doc-theme && yarn install && yarn build && rm -rf node_modules && cd ..
- run: yarn install --frozen-lockfile
- run: yarn typedoc
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs

View File

@@ -1,4 +1,4 @@
name: Release
name: Releases
on:
push:
@@ -46,5 +46,5 @@ jobs:
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./package.tgz
asset_name: simple-scaffold ${{ steps.update_tag.outputs.tagname }}.tgz
asset_name: package.tgz
asset_content_type: application/tgz

View File

@@ -2,7 +2,7 @@ name: Pull Requests
on:
pull_request:
branches: [master, develop]
branches: [master, alpha, beta]
jobs:
build:
runs-on: ubuntu-latest

1
.gitignore vendored
View File

@@ -61,4 +61,3 @@ examples/test-output/**/*
dist/
.DS_Store
tmp/
docs/

View File

@@ -1,9 +0,0 @@
{
"line-length": {
"line_length": 100,
"tables": false,
"code_blocks": false
},
"no-inline-html": false,
"first-line-h1": false
}

18
.vscode/settings.json vendored
View File

@@ -3,18 +3,10 @@
"npm.packageManager": "yarn",
"cSpell.words": [
"massarg",
"myname",
"nobrace",
"nocomment",
"nodir",
"nobrace",
"noext",
"nonegate",
"subdir"
],
"[markdown]": {
"editor.rulers": [
87,
100
],
},
}
"nocomment",
"nonegate"
]
}

8
.vscode/tasks.json vendored
View File

@@ -13,12 +13,6 @@
"type": "npm",
"problemMatcher": [],
},
{
"command": "yarn typedoc --watch",
"label": "typedoc --watch",
"type": "shell",
"problemMatcher": [],
},
{
"script": "start",
"label": "start",
@@ -56,4 +50,4 @@
"problemMatcher": [],
},
],
}
}

View File

@@ -5,7 +5,6 @@ between versions. With these notable exceptions:
- Some of the argument names have changed
- Template syntax has been improved
- The command to run Scaffold has been simplified from `new SimpleScaffold(opts).run()` to `SimpleScaffold(opts)`, which now returns a promise that you can await to know when the process has been completed.
## Argument changes

386
README.md
View File

@@ -1,53 +1,16 @@
<h1 align="center">Simple Scaffold</h1>
# simple-scaffold
<h2 align="center">
Simple Scaffold allows you to create your structured files based on templates.
[GitHub](https://github.com/chenasraf/simple-scaffold) |
[Documentation](https://casraf.blog/simple-scaffold) |
[NPM](https://npmjs.com/package/simple-scaffold) |
[casraf.blog](https://casraf.blog)
Simply organize your commonly-created files in their original structure, and replace any variable
values (such as component or app name) inside the paths or contents of the files with tokens to be
populated upon scaffolding.
</h2>
Then, run Simple Scaffold and it will generate your files for you in the desired structure,
with file names and contents that contain your dynamic information.
Simple Scaffold allows you to generate any set of files in the easiest way possible with simple commands.
It is completely framework agnostic so you can use it for anything from a few simple files to an
entire app boilerplate setup.
Simply organize your commonly-created files in their original structure, and running Simple Scaffold
will copy the files to the output path, while replacing values (such as component or app name, or
other custom data) inside the paths or contents of the files using Handlebars.js syntax.
<br />
<details>
<summary>Table of contents</summary>
- [Install](#install)
- [Command Line Interface (CLI)](#command-line-interface-cli)
- [Available flags](#available-flags)
- [Node module](#node-module)
- [Node-specific options](#node-specific-options)
- [Preparing files](#preparing-files)
- [Template files](#template-files)
- [Variable/token replacement](#variabletoken-replacement)
- [Helpers](#helpers)
- [Built-in Helpers](#built-in-helpers)
- [Capitalization Helpers](#capitalization-helpers)
- [Date helpers](#date-helpers)
- [Custom Helpers](#custom-helpers)
- [Examples](#examples)
- [Run](#run)
- [Command Example](#command-example)
- [Node Module Example](#node-module-example)
- [Files](#files)
- [Input](#input)
- [Output](#output)
- [Contributing](#contributing)
</details>
---
It's a simple way to easily create reusable components, common class files to start writing from,
or even entire app structures.
## Install
@@ -59,113 +22,110 @@ npm install [-g] simple-scaffold
# yarn
yarn [global] add simple-scaffold
# run without installing
npx simple-scaffold@latest <...args>
npx simple-scaffold <...args>
```
## Command Line Interface (CLI)
## Use as a command line tool
### Available flags
### Command Line Options
The following is the help text from the `simple-scaffold` binary. To see this and more
information anytime, add the `-h` or `--help` flag to your call, e.g.
`npx simple-scaffold@latest -h`.
```text
```plaintext
Usage: simple-scaffold [options]
Create structured files based on templates.
Options:
--help|-h Display help information
--help|-h Display help information
--name|-n Name to be passed to the generated files. {{name}}
and {{Name}} inside contents and file names will be
replaced accordingly.
--name|-n Name to be passed to the generated files. {{name}} and
{{Name}} inside contents and file names will be replaced
accordingly.
--output|-o Path to output to. If --create-sub-folder is enabled,
the subfolder will be created inside this path.
(default: current dir)
--output|-o Path to output to. If --create-sub-folder is enabled, the
subfolder will be created inside this path.
--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.
--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. (default:
)
--overwrite|-w Enable to override output files, even if they already
exist. (default: false)
--overwrite|-w Enable to override output files, even if they already exist.
(default: false)
--data|-d Add custom data to the templates. By default, only
your app name is included.
--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)
--sub-folder-name-helper|-sh Default helper to apply to subfolder name when using
`--create-sub-folder true`.
--quiet|-q Suppress output logs (Same as --verbose 0)
(default: false)
--quiet|-q Suppress output logs (Same as --verbose 0)
(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)
--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)
--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": "npx simple-scaffold@latest -t scaffolds/component/**/* -o src/components -d '{\"myProp\": \"propName\", \"myVal\": 123}'"
...
"scaffold": "yarn simple-scaffold --templates scaffolds/component/**/* --output src/components --data '{\"myProp\": \"propName\", \"myVal\": \"123\"}'"
}
}
```
## Node module
You can also build the scaffold yourself, if you want to create more complex arguments or scaffold
groups - simply pass a config object to the Scaffold function when you are ready to start.
## Use in Node.js
You can also build the scaffold yourself, if you want to create more complex arguments or scaffold groups.
Simply pass a config object to the constructor, and invoke `run()` when you are ready to start.
The config takes similar arguments to the command line:
```typescript
import Scaffold from "simple-scaffold"
const config = {
const scaffold = SimpleScaffold({
name: "component",
templates: [path.join(__dirname, "scaffolds", "component")],
output: path.join(__dirname, "src", "components"),
createSubFolder: true,
subFolderNameHelper: "upperCase"
data: {
locals: {
property: "value",
},
helpers: {
twice: (text) => [text, text].join(" ")
},
beforeWrite: (content, rawContent, outputPath) => content.toString().toUpperCase()
}
const scaffold = Scaffold(config)
}
})
```
### Node-specific options
### Additional Node.js options
In addition to all the options available in the command line, there are some Node/JS-specific
options available:
In addition to all the options available in the command line, there are some JS-specific options
available:
| Option | Type | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `output` | | In addition to being passed the same as CLI, it may also be passed a function for each input file to output into a dynamic path: `{ output: (fullPath, baseDir, baseName) => path.resolve(baseDir, baseName) }` |
| `helpers` | `Record<string, (string) => string>` | Helpers are simple functions that transform your `data` variables into other values. See [Helpers](#helpers) for the list of default helpers, or add your own to be loaded into the template parser. |
| `beforeWrite` | `(content: Buffer, rawContent: Buffer, outputPath: string) => Promise<String \| Buffer \| undefined> \| String \| Buffer \| undefined` | Supply this function to override the final output contents of each of your files, allowing you to add more pre-processing to your generator pipeline. The return value of this function will replace the output content of the respective file, which you may discriminate (if needed) using the `outputPath` argument. |
1. When `output` is used in Node directly, it may also be passed a function for each input file to
output into a dynamic path:
```typescript
config.output = (fullPath, baseDir, baseName) => {
console.log({ fullPath, baseDir, baseName })
return path.resolve(baseDir, baseName)
}
```
2. You may add custom `helpers` to your scaffolds. Helpers are simple `(string) => string` functions
that transform your `data` variables into other values. See [Helpers](#helpers) for the list of
default helpers, or add your own to be loaded into the template parser.
## Preparing files
@@ -173,22 +133,6 @@ options available:
Put your template files anywhere, and fill them with tokens for replacement.
Each template (not file) in the config array is parsed individually, and copied to the output
directory. If a single template path contains multiple files (e.g. if you use a folder path or a
glob pattern), the first directory up the tree of that template will become the base inside the
defined output path for that template, while copying files recursively and maintaining their
relative structure.
Examples:
> In the following examples, the config `name` is `AppName`, and the config `output` is `src`.
| Input template | Files in template | Output path(s) |
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| `./templates/{{ name }}.txt` | `./templates/{{ name }}.txt` | `src/AppName.txt` |
| `./templates/directory` | `outer/{{name}}.txt`,<br />`outer2/inner/{{name}}.txt` | `src/outer/AppName.txt`,<br />`src/outer2/inner/AppName.txt` |
| `./templates/others/**/*.txt` | `outer/{{name}}.jpg`,<br />`outer2/inner/{{name}}.txt` | `src/outer2/inner/AppName.txt` |
### Variable/token replacement
Scaffolding will replace `{{ varName }}` in both the file name and its contents and put the
@@ -197,89 +141,37 @@ transformed files in the output directory.
The data available for the template parser is the data you pass to the `data` config option (or
`--data` argument in CLI).
For example, using the following command:
```bash
npx simple-scaffold@latest \
--templates templates/components/{{name}}.jsx \
--output src/components \
--create-sub-folder true \
MyComponent
```
Will output a file with the path:
```text
<working_dir>/src/components/MyComponent.jsx
```
The contents of the file will be transformed in a similar fashion.
Your `data` will be pre-populated with the following:
- `{{Name}}`: PascalCase of the component name
- `{{name}}`: raw name of the component as you entered it
- `{{name}}`: raw name of the component
> Simple-Scaffold uses [Handlebars.js](https://handlebarsjs.com/) for outputting the file contents.
> Simple-Scaffold uses [Handlebars.js](https://handlebarsjs.com/) for outputting the file contents,
> see their documentation for more information on syntax.
> Any `data` you add in the config will be available for use with their names wrapped in
> `{{` and `}}`. Other Handlebars built-ins such as `each`, `if` and `with` are also supported, see
> [Handlebars.js Language Features](https://handlebarsjs.com/guide/#language-features) for more
> information.
> `{{` and `}}`.
### Helpers
#### Helpers
#### Built-in Helpers
Simple-Scaffold provides some built-in text transformation filters usable by Handlebars.
Simple-Scaffold provides some built-in text transformation filters usable by handleBars.
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.
##### Capitalization Helpers
Here are the built-in helpers available for use:
| Helper name | Example code | Example output |
| ------------ | ----------------------- | -------------- |
| [None] | `{{ name }}` | my name |
| `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 |
| `upperCase` | `{{ upperCase name }}` | MY NAME |
| `lowerCase` | `{{ lowerCase name }}` | my name |
| 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 |
| upperCase | `{{ upperCase name }}` | MYNAME |
| lowerCase | `{{ lowerCase name }}` | myname |
##### Date helpers
| Helper name | Description | Example code | Example output |
| -------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------ |
| `now` | Current date with format | `{{ now "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
| `now` (with offset) | Current date with format, and with offset | `{{ now "yyyy-MM-dd HH:mm" -1 "hours" }}` | `2042-01-01 14:00` |
| `date` | Custom date with format | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
| `date` (with offset) | Custom date with format, and with offset | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" -1 "days" }}` | `2041-31-12 15:00` |
| `date` (with date from `--data`) | Custom date with format, with data from the `data` config option | `{{ date myCustomDate "yyyy-MM-dd HH:mm" }}` | `2042-01-01 12:00` |
Further details:
- We use [`date-fns`](https://date-fns.org/docs/) for parsing/manipulating the dates.
If you want more information on the date tokens to use, refer to
[their format documentation](https://date-fns.org/docs/format).
- The date helper format takes the following arguments:
```typescript
(
date: string,
format: string,
offsetAmount?: number,
offsetType?: "years" | "months" | "weeks" | "days" | "hours" | "minutes" | "seconds"
)
```
- **The now helper** (for current time) takes the same arguments, minus the first one (`date`) as
it is implicitly the current date.
#### Custom Helpers
> These helpers are available for any data property, not exclusive to `name`.
You may also add your own custom helpers using the `helpers` options when using the JS API (rather
than the CLI). The `helpers` option takes an object whose keys are helper names, and values are
@@ -291,17 +183,9 @@ config.helpers = {
}
```
All of the above helpers (built in and custom) will also be available to you when using
`subFolderNameHelper` (`--sub-folder-name-helper`/`-sh`) as a possible value.
> To see more information on how helpers work and more features, see
> [Handlebars.js docs](https://handlebarsjs.com/guide/#custom-helpers).
## Examples
### Run
#### Command Example
### Command Example
```bash
simple-scaffold MyComponent \
@@ -311,74 +195,60 @@ simple-scaffold MyComponent \
MyComponent
```
#### Node Module Example
### Example Scaffold Input
```typescript
import Scaffold from "simple-scaffold"
#### Input Directory structure
async function main() {
await Scaffold({
name: "MyComponent",
templates: ["project/scaffold/**/*"],
output: ["src/components"],
data: {
className: "myClassName",
},
})
console.log("Done.")
}
```plaintext
- project
- scaffold
- {{Name}}.js
- src
- components
- ...
```
### Files
#### Contents of `project/scaffold/{{Name}}.jsx`
#### Input
```js
const React = require('react')
Input file path:
```text
project → scaffold → {{Name}}.js → src → components
```
Input file contents:
```typescript
import React from 'react'
export default {{camelCase name}}: React.FC = (props) => {
module.exports = function {{Name}}(props) {
return (
<div className="{{className}}">{{camelCase name}} Component</div>
<div className="{{className}}">{{Name}} Component</div>
)
}
```
#### Output
### Example Scaffold Output
Output file path:
### Output directory structure
- With `createSubFolder = false` (default):
```plaintext
- project
- src
- components
- MyComponent
- MyComponent.js
- ...
```
```text
project → src → components → MyComponent.js
```
With `createSubFolder = false`:
- With `createSubFolder = true`:
```plaintext
- project
- src
- components
- MyComponent.js
- ...
```
```text
project → src → components → MyComponent → MyComponent.js
```
#### Contents of `project/scaffold/MyComponent/MyComponent.jsx`
- With `createSubFolder = true` and `subFolderNameHelper = 'upperCase'`:
```js
const React = require("react")
```text
project → src → components → MYCOMPONENT → MyComponent.js
```
Output file contents:
```typescript
import React from 'react'
export default MyComponent: React.FC = (props) => {
module.exports = function MyComponent(props) {
return (
<div className="myClassName">MyComponent Component</div>
)
@@ -387,16 +257,6 @@ export default MyComponent: React.FC = (props) => {
## Contributing
I am developing this package on my free time, so any support, whether code, issues, or just stars
is very helpful to sustaining its life. If you would like to donate a bit to help keep the project
alive, I would be very thankful!
<a href='https://ko-fi.com/casraf' target='_blank'>
<img height='36' style='border:0px;height:36px;'
src='https://cdn.ko-fi.com/cdn/kofi1.png?v=3'
alt='Buy Me a Coffee at ko-fi.com' />
</a>
I welcome any issues or pull requests on GitHub. If you find a bug, or would like a new feature,
don't hesitate to open an appropriate issue and I will do my best to reply promptly.
@@ -419,6 +279,6 @@ Some tips on getting around the code:
- Use `yarn cmd` to use the CLI feature of Simple Scaffold from within the root directory,
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,
> which triggers a build right before running the command with the rest of the given arguments.
> This requires an updated build, and does not trigger one itself. Either use `yarn dev` 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,2 +0,0 @@
node_modules/
build/

View File

@@ -1,15 +0,0 @@
{
"name": "doc-theme",
"version": "1.0.0",
"keywords": [
"typedocplugin"
],
"scripts": {
"build": "tsc"
},
"main": "build/index.js",
"devDependencies": {
"typedoc": "^0.22.15",
"typescript": "^4.6.3"
}
}

View File

@@ -1,42 +0,0 @@
import { Application, DefaultTheme, PageEvent, JSX, DefaultThemeRenderContext } from "typedoc"
class MyThemeContext extends DefaultThemeRenderContext {
// Important: If you use `this`, this function MUST be bound! Template functions are free
// to destructure the context object to only grab what they care about.
override analytics = () => {
// Reusing existing option rather than declaring our own for brevity
if (!this.options.isSet("gaID")) return
const gaID = this.options.getValue("gaID")
const scr = `
(function (w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" });
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != "dataLayer" ? "&l=" + l : "";
j.async = true;
j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl;
f.parentNode.insertBefore(j, f);
})(window, document, "script", "dataLayer", "${gaID}");
`
return (
<script>
<JSX.Raw html={scr} />
</script>
)
}
}
class MyTheme extends DefaultTheme {
private _contextCache?: MyThemeContext
override getRenderContext(): DefaultThemeRenderContext {
return (this._contextCache ||= new MyThemeContext(this, this.application.options))
}
}
export function load(app: Application) {
app.renderer.defineTheme("doc-theme", MyTheme)
}

View File

@@ -1,93 +0,0 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react", // /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
"jsxFactory": "JSX.createElement", // /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
"jsxFragmentFactory": "JSX.Fragment", // /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build", // /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}

View File

@@ -1,139 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
glob@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
jsonc-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22"
integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==
lunr@^2.3.9:
version "2.3.9"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
marked@^4.0.12:
version "4.0.14"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.14.tgz#7a3a5fa5c80580bac78c1ed2e3b84d7bd6fc3870"
integrity sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==
minimatch@^3.0.4:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
minimatch@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
dependencies:
brace-expansion "^2.0.1"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
shiki@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.10.1.tgz#6f9a16205a823b56c072d0f1a0bcd0f2646bef14"
integrity sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==
dependencies:
jsonc-parser "^3.0.0"
vscode-oniguruma "^1.6.1"
vscode-textmate "5.2.0"
typedoc@^0.22.15:
version "0.22.15"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.22.15.tgz#c6ad7ed9d017dc2c3a06c9189cb392bd8e2d8c3f"
integrity sha512-CMd1lrqQbFvbx6S9G6fL4HKp3GoIuhujJReWqlIvSb2T26vGai+8Os3Mde7Pn832pXYemd9BMuuYWhFpL5st0Q==
dependencies:
glob "^7.2.0"
lunr "^2.3.9"
marked "^4.0.12"
minimatch "^5.0.1"
shiki "^0.10.1"
typescript@^4.6.3:
version "4.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
vscode-oniguruma@^1.6.1:
version "1.6.2"
resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz#aeb9771a2f1dbfc9083c8a7fdd9cccaa3f386607"
integrity sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==
vscode-textmate@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e"
integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,38 +1,25 @@
{
"name": "simple-scaffold",
"version": "1.1.2",
"description": "A simple command to generate any file structure, from single components to entire app boilerplates.",
"homepage": "https://casraf.blog/simple-scaffold",
"version": "1.0.0-alpha.14",
"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",
"keywords": [
"javascript",
"cli",
"template",
"files",
"typescript",
"generator",
"scaffold",
"file",
"scaffolding"
],
"scripts": {
"clean": "rimraf dist/",
"build": "yarn clean && tsc && chmod -R +x ./dist && cp ./package.json ./README.md ./dist/",
"clean": "rm -rf dist/",
"build": "yarn clean && tsc && chmod -R +x ./dist && cp ./package.json ./dist/ && cp ./README.md ./dist/",
"dev": "tsc --watch",
"start": "node dist/scaffold.js",
"test": "jest --verbose",
"cmd": "node --trace-warnings dist/cmd.js",
"build-test": "yarn build && yarn test",
"build-cmd": "yarn build && yarn cmd",
"audit-fix": "npm_config_yes=true npx yarn-audit-fix --flow=convert"
"build-cmd": "yarn build && yarn cmd"
},
"dependencies": {
"args": "^5.0.1",
"chalk": "^4.1.2",
"date-fns": "^2.28.0",
"glob": "^7.1.3",
"handlebars": "^4.7.7",
"lodash": "^4.17.21",
@@ -40,18 +27,16 @@
"util.promisify": "^1.1.1"
},
"devDependencies": {
"@types/args": "^3.0.1",
"@types/glob": "^7.1.1",
"@types/jest": "^26.0.24",
"@types/lodash": "^4.14.171",
"@types/mock-fs": "^4.13.1",
"@types/node": "^14.14.22",
"doc-theme": "file:./doc-theme",
"jest": "^27.0.6",
"mock-fs": "^5.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.3",
"ts-node": "^10.1.0",
"typedoc": "^0.22.15",
"typescript": "^4.3.5"
}
}
}

View File

@@ -1,109 +1,2 @@
#!/usr/bin/env node
import massarg from "massarg"
import chalk from "chalk"
import { LogLevel, ScaffoldCmdConfig } from "./types"
import { Scaffold } from "./scaffold"
import path from "path"
import fs from "fs/promises"
export async function parseCliArgs(args = process.argv.slice(2)) {
const pkg = JSON.parse((await fs.readFile(path.join(__dirname, "package.json"))).toString())
return (
massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
.main(Scaffold)
.option({
name: "name",
aliases: ["n"],
description:
"Name to be passed to the generated files. {{name}} and {{Name}} inside contents and file names will be replaced accordingly.",
isDefault: true,
required: true,
})
.option({
name: "output",
aliases: ["o"],
description: `Path to output to. If --create-sub-folder is enabled, the subfolder will be created inside this path. ${chalk.reset`${chalk.white`(default: current dir)`}`}`,
required: true,
})
.option({
name: "templates",
aliases: ["t"],
array: true,
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.",
required: true,
})
.option({
name: "overwrite",
aliases: ["w"],
boolean: true,
defaultValue: false,
description: "Enable to override output files, even if they already exist.",
})
.option({
name: "data",
aliases: ["d"],
description: "Add custom data to the templates. By default, only your app name is included.",
parse: (v) => JSON.parse(v),
})
.option({
name: "create-sub-folder",
aliases: ["s"],
boolean: true,
defaultValue: false,
description: "Create subfolder with the input name",
})
.option({
name: "sub-folder-name-helper",
aliases: ["sh"],
description: "Default helper to apply to subfolder name when using `--create-sub-folder true`.",
})
.option({
name: "quiet",
aliases: ["q"],
boolean: true,
defaultValue: false,
description: "Suppress output logs (Same as --verbose 0)",
})
.option({
name: "verbose",
aliases: ["v"],
defaultValue: LogLevel.Info,
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.`,
parse: Number,
})
.option({
name: "dry-run",
aliases: ["dr"],
boolean: true,
defaultValue: false,
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.",
})
// .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.`].join("\n"),
footer: [
`Version: ${pkg.version}`,
`Copyright © Chen Asraf 2017-${new Date().getFullYear()}`,
``,
`Documentation: ${chalk.underline`https://casraf.blog/simple-scaffold`}`,
`NPM: ${chalk.underline`https://npmjs.com/package/simple-scaffold`}`,
`GitHub: ${chalk.underline`https://github.com/chenasraf/simple-scaffold`}`,
].join("\n"),
})
.parse(args)
)
}
import { parseCliArgs } from "./cmd_util"
parseCliArgs()

95
src/cmd_util.ts Normal file
View File

@@ -0,0 +1,95 @@
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"],
description:
"Name to be passed to the generated files. {{name}} and {{Name}} inside contents and file names will be replaced accordingly.",
isDefault: true,
required: true,
})
.option({
name: "output",
aliases: ["o"],
description:
"Path to output to. If --create-sub-folder is enabled, the subfolder will be created inside this path.",
required: true,
})
.option({
name: "templates",
aliases: ["t"],
array: true,
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.",
required: true,
})
.option({
name: "overwrite",
aliases: ["w"],
boolean: true,
defaultValue: false,
description: "Enable to override output files, even if they already exist.",
})
.option({
name: "data",
aliases: ["d"],
description: "Add custom data to the templates. By default, only your app name is included.",
parse: (v) => JSON.parse(v),
})
.option({
name: "create-sub-folder",
aliases: ["s"],
boolean: true,
defaultValue: false,
description: "Create subfolder with the input name",
})
.option({
name: "quiet",
aliases: ["q"],
boolean: true,
defaultValue: false,
description: "Suppress output logs (Same as --verbose 0)",
})
.option({
name: "verbose",
aliases: ["v"],
defaultValue: LogLevel.Info,
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.`,
parse: Number,
})
.option({
name: "dry-run",
aliases: ["dr"],
boolean: true,
defaultValue: false,
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.",
})
// .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`,
`NPM: ${chalk.underline`https://npmjs.com/package/simple-scaffold`}`,
`GitHub: ${chalk.underline`https://github.com/chenasraf/simple-scaffold`}`,
].join("\n"),
})
.parse(args)
)
}

0
src/filters.ts Normal file
View File

View File

@@ -1,118 +1,124 @@
/**
* @module
* Simple Scaffold
*
* See [readme](README.md)
*/
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,
makeRelativePath,
registerHelpers,
getTemplateGlobInfo,
getFileList,
getBasePath,
copyFileTransformed,
getTemplateFileInfo,
logInitStep,
logInputFile,
} from "./utils"
import { LogLevel, ScaffoldConfig } from "./types"
/**
* Create a scaffold using given `options`.
*
* #### Create files
* To create a file structure to output, use any directory and file structure you would like.
* Inside folder names, file names or file contents, you may place `{{ var }}` where `var` is either
* `name` which is the scaffold name you provided or one of the keys you provided in the `data` option.
*
* The contents and names will be replaced with the transformed values so you can use your original structure as a
* boilerplate for other projects, components, modules, or even single files.
*
* The files will maintain their structure, starting from the directory containing the template (or the template itself
* if it is already a directory), and will output from that directory into the directory defined by `config.output`.
*
* #### Helpers
* Helpers are functions you can use to transform your `{{ var }}` contents into other values without having to
* pre-define the data and use a duplicated key.
*
* Any functions you provide in `helpers` option will also be available to you to make custom formatting as you see fit
* (for example, formatting a date)
*
* For available default values, see {@link DefaultHelperKeys}.
*
* @param {ScaffoldConfig} config The main configuration object
*
* @see {@link DefaultHelperKeys}
*
* @category Main
*/
export async function Scaffold(config: ScaffoldConfig): Promise<void> {
config.output ??= process.cwd()
registerHelpers(config)
export async function Scaffold(config: ScaffoldConfig) {
const options = { ...config }
try {
config.data = { name: config.name, Name: pascalCase(config.name), ...config.data }
logInitStep(config)
for (let _template of config.templates) {
const data = { name: options.name, Name: pascalCase(options.name), ...options.data }
log(options, LogLevel.Debug, "Full config:", {
name: options.name,
templates: options.templates,
output: options.output,
createSubfolder: options.createSubFolder,
data: options.data,
overwrite: options.overwrite,
quiet: options.quiet,
helpers: Object.keys(options.helpers ?? {}),
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 { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template } = await getTemplateGlobInfo(
config,
_template
)
const files = await getFileList(config, template)
registerHelpers(options)
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 + "/**/*"
}
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)) {
continue
if (!(await isDir(inputFilePath))) {
const relPath = makeRelativePath(path.dirname(removeGlob(inputFilePath).replace(_nonGlobTemplate, "")))
const basePath = path
.resolve(process.cwd(), relPath)
.replace(process.cwd() + "/", "")
.replace(process.cwd(), "")
log(
options,
LogLevel.Debug,
`\nprocess.cwd(): ${process.cwd()}`,
`\norigTemplate: ${origTemplate}`,
`\nrelPath: ${relPath}`,
`\ntemplate: ${template}`,
`\ninputFilePath: ${inputFilePath}`,
`\nnonGlobTemplate: ${_nonGlobTemplate}`,
`\nbasePath: ${basePath}`,
`\nisDir: ${_isDir}`,
`\nisGlob: ${_isGlob}`,
`\n`
)
await handleTemplateFile(inputFilePath, basePath, options, data)
}
const relPath = makeRelativePath(path.dirname(removeGlob(inputFilePath).replace(nonGlobTemplate, "")))
const basePath = getBasePath(relPath)
logInputFile(config, {
origTemplate,
relPath,
template,
inputFilePath,
nonGlobTemplate,
basePath,
isDirOrGlob,
isGlob,
})
await handleTemplateFile(config, {
templatePath: inputFilePath,
basePath,
})
}
} catch (e: any) {
handleErr(e)
}
}
} catch (e: any) {
log(config, LogLevel.Error, e)
log(options, LogLevel.Error, e)
throw e
}
}
async function handleTemplateFile(
config: ScaffoldConfig,
{ templatePath, basePath }: { templatePath: string; basePath: string }
templatePath: string,
basePath: string,
options: ScaffoldConfig,
data: Record<string, string>
): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const { inputPath, outputPathOpt, outputDir, outputPath, exists } = await getTemplateFileInfo(config, {
templatePath,
basePath,
})
const overwrite = getOptionValueForFile(config, inputPath, config.overwrite ?? false)
const inputPath = path.resolve(process.cwd(), templatePath)
const outputPathOpt = getOptionValueForFile(inputPath, data, options.output)
const outputDir = path.resolve(
process.cwd(),
...([outputPathOpt, basePath, options.createSubFolder ? options.name : undefined].filter(Boolean) as string[])
)
const outputPath = handlebarsParse(path.join(outputDir, path.basename(inputPath)), data)
log(
config,
options,
LogLevel.Debug,
`\nParsing ${templatePath}`,
`\nBase path: ${basePath}`,
@@ -122,11 +128,29 @@ async function handleTemplateFile(
`\nFull output path: ${outputPath}`,
`\n`
)
const overwrite = getOptionValueForFile(inputPath, data, options.overwrite ?? false)
const exists = await pathExists(outputPath)
await createDirIfNotExists(path.dirname(outputPath), config)
await createDirIfNotExists(path.dirname(outputPath), options)
log(config, LogLevel.Info, `Writing to ${outputPath}`)
await copyFileTransformed(config, { exists, overwrite, outputPath, inputPath })
log(options, LogLevel.Info, `Writing to ${outputPath}`)
if (!exists || overwrite) {
if (exists && overwrite) {
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, LogLevel.Info, "Content output:")
log(options, LogLevel.Info, outputContents)
}
} else if (exists) {
log(options, LogLevel.Info, `File ${outputPath} already exists, skipping`)
}
resolve()
} catch (e: any) {
handleErr(e)

View File

@@ -1,13 +1,15 @@
import { HelperDelegate } from "handlebars/runtime"
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>
/**
* The config object for defining a scaffolding group.
*
* @see https://github.com/chenasraf/simple-scaffold#readme
* @see {@link DefaultHelperKeys}
*
* @category Config
*/
export interface ScaffoldConfig {
/**
* Name to be passed to the generated files. `{{name}}` and `{{Name}}` inside contents and file names will be replaced
@@ -17,77 +19,46 @@ export interface ScaffoldConfig {
/**
* 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.
*
* @default Current working directory
* or a glob pattern for multiple file matching easily. (default: current working directory)
*/
templates: string[]
/**
* Path to output to. If `createSubFolder` is `true`, the subfolder will be created inside this path.
*
* May also be a {@link FileResponseHandler} which returns a new output path to override the default one.
*
* @see {@link FileResponse}
* @see {@link FileResponseHandler}
*/
/** Path to output to. If `createSubFolder` is `true`, the subfolder will be created inside this path. */
output: FileResponse<string>
/**
* Whether to create subfolder with the input name.
*
* When `true`, you may also use {@link subFolderNameHelper} to determine a pre-process helper on
* the directory name.
*
* @default `false`
* Create subfolder with the input name (default: `false`)
*/
createSubFolder?: boolean
/**
* Add custom data to the templates. By default, only your app name is included as `{{name}}` and `{{Name}}`.
*
* This can be any object that will be usable by Handlebars.
*/
data?: Record<string, any>
data?: Record<string, string>
/**
* Enable to override output files, even if they already exist.
* Enable to override output files, even if they already exist. (default: `false`)
*
* You may supply a function to this option, which can take the arguments `(fullPath, baseDir, baseName)` and returns
* a string, to return a dynamic path for each file.
*
* May also be a {@link FileResponseHandler} which returns a boolean value per file.
*
* @see {@link FileResponse}
* @see {@link FileResponseHandler}
*
* @default `false`
*/
overwrite?: FileResponse<boolean>
/**
* Suppress output logs (Same as `verbose: 0` or `verbose: LogLevel.None`)
* @see {@link verbose}
*/
/** Suppress output logs (Same as `verbose: 0` or `verbose: LogLevel.None`) */
quiet?: boolean
/**
* 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.
*
* @see {@link LogLevel}
*
* @default `2 (info)`
* of the same level or higher. (default: `2 (info)`)
* @see LogLevel
*/
verbose?: LogLevel
/**
* 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`
* actual file contents or create directories. (default: `false`)
*/
dryRun?: boolean
@@ -108,177 +79,9 @@ export interface ScaffoldConfig {
* }
* })
* ```
*
* See {@link DefaultHelperKeys} for a list of all the built-in available helpers.
*
* @see https://github.com/chenasraf/simple-scaffold#helpers
* @see https://github.com/chenasraf/simple-scaffold#built-in-helpers
*/
helpers?: Record<string, Helper>
/**
* Default transformer to apply to subfolder name when using `createSubFolder: true`. Can be one of the default
* capitalization helpers, or a custom one you provide to `helpers`. Defaults to `undefined`, which means no transformation is done.
*
* @see {@link createSubFolder}
* @see {@link CapitalizationHelperKeys}
* @see {@link DefaultHelperKeys}
*/
subFolderNameHelper?: DefaultHelperKeys | string
/**
* This callback runs right before content is being written to the disk. If you supply this function, you may return
* a string that represents the final content of your file, you may process the content as you see fit. For example,
* you may run formatters on a file, fix output in edge-cases not supported by helpers or data, etc.
*
* If the return value of this function is `undefined`, the original content will be used.
*
* @param content The original template after token replacement
* @param rawContent The original template before token replacement
* @param outputPath The final output path of the processed file
*
* @returns {Promise<String | Buffer | undefined> | String | Buffer | undefined} The final output of the file contents-only, after further modifications -
* or `undefined` to use the original content (i.e. `content.toString()`)
*/
beforeWrite?(
content: Buffer,
rawContent: Buffer,
outputPath: string
): string | Buffer | undefined | Promise<string | Buffer | undefined>
helpers?: Record<string, (text: string) => string>
}
/**
* The names of the available helper functions that relate to text capitalization.
*
* These are available for `subfolderNameHelper`.
*
* | Helper name | Example code | Example output |
* | ------------ | ----------------------- | -------------- |
* | [None] | `{{ name }}` | my name |
* | `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 |
* | `upperCase` | `{{ upperCase name }}` | MY NAME |
* | `lowerCase` | `{{ lowerCase name }}` | my name |
*
* @see {@link ScaffoldConfig}
* @see {@link subFolderNameHelper}
* @see {@link DefaultHelperKeys}
*
* @category Helpers
*/
export type CapitalizationHelperKeys =
| "camelCase"
| "hyphenCase"
| "kebabCase"
| "lowerCase"
| "pascalCase"
| "snakeCase"
| "startCase"
| "upperCase"
/**
* The names of the available helper functions that relate to dates.
*
* | Helper name | Description | Example code | Example output |
* | -------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------ |
* | `now` | Current date with format | `{{ now "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
* | `now` (with offset) | Current date with format, and with offset | `{{ now "yyyy-MM-dd HH:mm" -1 "hours" }}` | `2042-01-01 14:00` |
* | `date` | Custom date with format | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
* | `date` (with offset) | Custom date with format, and with offset | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" -1 "days" }}` | `2041-31-12 15:00` |
* | `date` (with date from `--data`) | Custom date with format, with data from the `data` config option | `{{ date myCustomDate "yyyy-MM-dd HH:mm" }}` | `2042-01-01 12:00` |
*
* @see {@link DefaultHelperKeys}
*
* @category Helpers
*/
export type DateHelperKeys = "date" | "now"
/**
* The names of all the available helper functions in templates.
* Simple-Scaffold provides some built-in text transformation filters usable by handleBars.
*
* 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.
*
* @see {@link CapitalizationHelperKeys}
* @see {@link DateHelperKeys}
*
* @category Helpers
*/
export type DefaultHelperKeys = CapitalizationHelperKeys | DateHelperKeys
/**
* Helper function, see https://handlebarsjs.com/guide/#custom-helpers
*
* @category Helpers
*/
export type Helper = HelperDelegate
/**
* The amount of information to log when generating scaffold.
* When not `None`, the selected level will be the lowest level included.
*
* For example, level `Info` (2) will include `Info`, `Warning` and `Error`, but not `Debug`; and `Warning` will only
* show `Warning` and `Error`.
*
* @default `2 (info)`
*
* @category Logging
*/
export enum LogLevel {
/** Silent output */
None = 0,
/** Debugging information. Very verbose and only recommended for troubleshooting. */
Debug = 1,
/**
* The regular level of logging. Major actions are logged to show the scaffold progress.
*
* @default
*/
Info = 2,
/** Warnings such as when file fails to replace token values properly in template. */
Warning = 3,
/** Errors, such as missing files, bad replacement token syntax, or un-writable directories. */
Error = 4,
}
/**
* A function that takes path information about file, and returns a value of type `T`
*
* @template T The return type for the function
* @param {string} fullPath The full path of the current file
* @param {string} basedir The directory containing the current file
* @param {string} basename The name of the file
*
* @returns {T} A return value
*
* @category Config
*/
export type FileResponseHandler<T> = (fullPath: string, basedir: string, basename: string) => T
/**
* Represents a response for file path information.
* Can either be:
*
* 1. `T` - static value
* 2. A function with the following signature which returns `T`:
* ```typescript
* (fullPath: string, basedir: string, basename: string) => T
* ```
*
* @typedef T The return type
*
* @see {@link FileResponseHandler}
*
* @category Config
* */
export type FileResponse<T> = T | FileResponseHandler<T>
/** @internal */
export interface ScaffoldCmdConfig {
name: string
templates: string[]

View File

@@ -1,6 +1,6 @@
import path from "path"
import { F_OK } from "constants"
import { DefaultHelperKeys, FileResponse, FileResponseHandler, Helper, LogLevel, 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"
@@ -9,21 +9,8 @@ import Handlebars from "handlebars"
import { promises as fsPromises } from "fs"
import chalk from "chalk"
const { stat, access, mkdir } = fsPromises
import dtAdd from "date-fns/add"
import dtFormat from "date-fns/format"
import dtParseISO from "date-fns/parseISO"
const dateFns = {
add: dtAdd,
format: dtFormat,
parseISO: dtParseISO,
}
import { glob } from "glob"
import { promisify } from "util"
const { readFile, writeFile } = fsPromises
export const defaultHelpers: Record<DefaultHelperKeys, Helper> = {
export const defaultHelpers: Exclude<ScaffoldConfig["helpers"], undefined> = {
camelCase,
snakeCase,
startCase,
@@ -32,66 +19,22 @@ export const defaultHelpers: Record<DefaultHelperKeys, Helper> = {
pascalCase,
lowerCase: (text) => text.toLowerCase(),
upperCase: (text) => text.toUpperCase(),
now: nowHelper,
date: dateHelper,
}
export function _dateHelper(date: Date, formatString: string): string
export function _dateHelper(
date: Date,
formatString: string,
durationDifference: number,
durationType: keyof Duration
): string
export function _dateHelper(
date: Date,
formatString: string,
durationDifference?: number,
durationType?: keyof Duration
): string {
if (durationType && durationDifference !== undefined) {
return dateFns.format(dateFns.add(date, { [durationType]: durationDifference }), formatString)
}
return dateFns.format(date, formatString)
}
export function nowHelper(formatString: string): string
export function nowHelper(formatString: string, durationDifference: number, durationType: keyof Duration): string
export function nowHelper(formatString: string, durationDifference?: number, durationType?: keyof Duration): string {
return _dateHelper(new Date(), formatString, durationDifference!, durationType!)
}
export function dateHelper(date: string, formatString: string): string
export function dateHelper(
date: string,
formatString: string,
durationDifference: number,
durationType: keyof Duration
): string
export function dateHelper(
date: string,
formatString: string,
durationDifference?: number,
durationType?: keyof Duration
): string {
return _dateHelper(dateFns.parseISO(date), formatString, durationDifference!, durationType!)
}
export function registerHelpers(config: ScaffoldConfig): void {
const _helpers = { ...defaultHelpers, ...config.helpers }
export function registerHelpers(options: ScaffoldConfig) {
const _helpers = { ...defaultHelpers, ...options.helpers }
for (const helperName in _helpers) {
log(config, LogLevel.Debug, `Registering helper: ${helperName}`)
log(options, LogLevel.Debug, `Registering helper: ${helperName}`)
Handlebars.registerHelper(helperName, _helpers[helperName as keyof typeof _helpers])
}
}
export function handleErr(err: NodeJS.ErrnoException | null): void {
export function handleErr(err: NodeJS.ErrnoException | null) {
if (err) throw err
}
export function log(config: ScaffoldConfig, level: LogLevel, ...obj: any[]): void {
if (config.quiet || config.verbose === LogLevel.None || level < (config.verbose ?? LogLevel.Info)) {
export function log(options: ScaffoldConfig, level: LogLevel, ...obj: any[]) {
if (options.quiet || options.verbose === LogLevel.None || level <= (options.verbose ?? LogLevel.Info)) {
return
}
const levelColor: Record<LogLevel, keyof typeof chalk> = {
@@ -115,16 +58,16 @@ export function log(config: ScaffoldConfig, level: LogLevel, ...obj: any[]): voi
)
}
export async function createDirIfNotExists(dir: string, config: ScaffoldConfig): Promise<void> {
export async function createDirIfNotExists(dir: string, options: ScaffoldConfig): Promise<void> {
const parentDir = path.dirname(dir)
if (!(await pathExists(parentDir))) {
await createDirIfNotExists(parentDir, config)
await createDirIfNotExists(parentDir, options)
}
if (!(await pathExists(dir))) {
try {
log(config, LogLevel.Debug, `Creating dir ${dir}`)
log(options, LogLevel.Debug, `Creating dir ${dir}`)
await mkdir(dir)
return
} catch (e: any) {
@@ -137,43 +80,25 @@ export async function createDirIfNotExists(dir: string, config: ScaffoldConfig):
}
export function getOptionValueForFile<T>(
config: ScaffoldConfig,
filePath: string,
data: Record<string, string>,
fn: FileResponse<T>,
defaultValue?: T
): T {
if (typeof fn !== "function") {
return defaultValue ?? (fn as T)
}
return (fn as FileResponseHandler<T>)(
return (fn as FileResponseFn<T>)(
filePath,
path.dirname(handlebarsParse(config, filePath, { isPath: true }).toString()),
path.basename(handlebarsParse(config, filePath, { isPath: true }).toString())
path.dirname(handlebarsParse(filePath, data)),
path.basename(handlebarsParse(filePath, data))
)
}
export function handlebarsParse(
config: ScaffoldConfig,
templateBuffer: Buffer | string,
{ isPath = false }: { isPath?: boolean } = {}
): Buffer {
const { data } = config
try {
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 Buffer.from(outputContents)
} catch (e) {
log(config, LogLevel.Debug, e)
log(config, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content")
return Buffer.from(templateBuffer)
}
export function handlebarsParse(templateBuffer: Buffer | string, data: Record<string, string>) {
const parser = Handlebars.compile(templateBuffer.toString(), { noEscape: true })
const outputContents = parser(data)
return outputContents
}
export async function pathExists(filePath: string): Promise<boolean> {
@@ -197,178 +122,10 @@ export async function isDir(path: string): Promise<boolean> {
return tplStat.isDirectory()
}
export function removeGlob(template: string): string {
return template.replace(/\*/g, "").replace(/(\/\/|\\\\)/g, path.sep)
export function removeGlob(template: string) {
return template.replace(/\*/g, "").replace(/\/\//g, "/")
}
export function makeRelativePath(str: string): string {
return str.startsWith(path.sep) ? str.slice(1) : str
}
export function getBasePath(relPath: string): string {
return path
.resolve(process.cwd(), relPath)
.replace(process.cwd() + path.sep, "")
.replace(process.cwd(), "")
}
export async function getFileList(config: ScaffoldConfig, template: string): Promise<string[]> {
return (
await promisify(glob)(template, {
dot: true,
debug: config.verbose === LogLevel.Debug,
nodir: true,
})
).map((f) => f.replace(/\//g, path.sep))
}
export interface GlobInfo {
nonGlobTemplate: string
origTemplate: string
isDirOrGlob: boolean
isGlob: boolean
template: string
}
export async function getTemplateGlobInfo(config: ScaffoldConfig, template: string): Promise<GlobInfo> {
const isGlob = glob.hasMagic(template)
log(config, LogLevel.Debug, "before isDir", "isGlob:", isGlob, template)
let _template = template
const nonGlobTemplate = isGlob ? removeGlob(template) : template
const isDirOrGlob = isGlob ? true : await isDir(template)
log(config, LogLevel.Debug, "after isDir", isDirOrGlob)
const _shouldAddGlob = !isGlob && isDirOrGlob
const origTemplate = template
if (_shouldAddGlob) {
_template = path.join(template, "**", "*")
}
return { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template: _template }
}
export interface OutputFileInfo {
inputPath: string
outputPathOpt: string
outputDir: string
outputPath: string
exists: boolean
}
export async function getTemplateFileInfo(
config: ScaffoldConfig,
{ templatePath, basePath }: { templatePath: string; basePath: string }
): Promise<OutputFileInfo> {
const inputPath = path.resolve(process.cwd(), templatePath)
const outputPathOpt = getOptionValueForFile(config, inputPath, config.output)
const outputDir = getOutputDir(config, outputPathOpt, basePath)
const outputPath = handlebarsParse(config, path.join(outputDir, path.basename(inputPath)), {
isPath: true,
}).toString()
const exists = await pathExists(outputPath)
return { inputPath, outputPathOpt, outputDir, outputPath, exists }
}
export async function copyFileTransformed(
config: ScaffoldConfig,
{
exists,
overwrite,
outputPath,
inputPath,
}: {
exists: boolean
overwrite: boolean
outputPath: string
inputPath: string
}
): Promise<void> {
if (!exists || overwrite) {
if (exists && overwrite) {
log(config, LogLevel.Info, `File ${outputPath} exists, overwriting`)
}
const templateBuffer = await readFile(inputPath)
const unprocessedOutputContents = handlebarsParse(config, templateBuffer)
const finalOutputContents =
(await config.beforeWrite?.(unprocessedOutputContents, templateBuffer, outputPath)) ?? unprocessedOutputContents
if (!config.dryRun) {
await writeFile(outputPath, finalOutputContents)
log(config, LogLevel.Info, "Done.")
} else {
log(config, LogLevel.Info, "Content output:")
log(config, LogLevel.Info, finalOutputContents)
}
} else if (exists) {
log(config, LogLevel.Info, `File ${outputPath} already exists, skipping`)
}
}
export function getOutputDir(config: ScaffoldConfig, outputPathOpt: string, basePath: string): string {
return path.resolve(
process.cwd(),
...([
outputPathOpt,
basePath,
config.createSubFolder
? config.subFolderNameHelper
? handlebarsParse(config, `{{ ${config.subFolderNameHelper} name }}`).toString()
: config.name
: undefined,
].filter(Boolean) as string[])
)
}
export function logInputFile(
config: ScaffoldConfig,
{
origTemplate,
relPath,
template,
inputFilePath,
nonGlobTemplate,
basePath,
isDirOrGlob,
isGlob,
}: {
origTemplate: string
relPath: string
template: string
inputFilePath: string
nonGlobTemplate: string
basePath: string
isDirOrGlob: boolean
isGlob: boolean
}
): void {
log(
config,
LogLevel.Debug,
`\nprocess.cwd(): ${process.cwd()}`,
`\norigTemplate: ${origTemplate}`,
`\nrelPath: ${relPath}`,
`\ntemplate: ${template}`,
`\ninputFilePath: ${inputFilePath}`,
`\nnonGlobTemplate: ${nonGlobTemplate}`,
`\nbasePath: ${basePath}`,
`\nisDirOrGlob: ${isDirOrGlob}`,
`\nisGlob: ${isGlob}`,
`\n`
)
}
export function logInitStep(config: ScaffoldConfig): void {
log(config, LogLevel.Debug, "Full config:", {
name: config.name,
templates: config.templates,
output: config.output,
createSubfolder: config.createSubFolder,
data: config.data,
overwrite: config.overwrite,
quiet: config.quiet,
subFolderTransformHelper: config.subFolderNameHelper,
helpers: Object.keys(config.helpers ?? {}),
verbose: `${config.verbose} (${Object.keys(LogLevel).find(
(k) => (LogLevel[k as any] as unknown as number) === config.verbose!
)})`,
})
log(config, LogLevel.Info, "Data:", config.data)
return str.startsWith("/") ? str.slice(1) : str
}

View File

@@ -4,9 +4,6 @@ import Scaffold from "../src/scaffold"
import { readdirSync, readFileSync } from "fs"
import { Console } from "console"
import { defaultHelpers } from "../src/utils"
import { join } from "path"
import * as dateFns from "date-fns"
import crypto from "crypto"
const fileStructNormal = {
input: {
@@ -14,13 +11,6 @@ const fileStructNormal = {
},
output: {},
}
const fileStructWithBinary = {
input: {
"{{name}}.txt": "Hello, my app is {{name}}",
"{{name}}.bin": crypto.randomBytes(10000),
},
output: {},
}
const fileStructWithData = {
input: {
@@ -41,12 +31,6 @@ const fileStructNested = {
},
output: {},
}
const fileStructSubdirTransformer = {
input: {
"{{name}}.txt": "Hello, my app is {{name}}",
},
output: {},
}
const defaultHelperNames = Object.keys(defaultHelpers)
const fileStructHelpers = {
@@ -61,16 +45,8 @@ const fileStructHelpers = {
},
output: {},
}
const fileStructDates = {
input: {
"now.txt": "Today is {{ now 'mmm' }}, time is {{ now 'HH:mm' }}",
"offset.txt": "Yesterday was {{ now 'mmm' -1 'days' }}, time is {{ now 'HH:mm' -1 'days' }}",
"custom.txt": "Custom date is {{ date customDate 'mmm' }}, time is {{ date customDate 'HH:mm' }}",
},
output: {},
}
// let logsTemp: any = []
// let logMock: any
function withMock(fileStruct: FileSystem.DirectoryItems, testFn: jest.EmptyFunction): jest.EmptyFunction {
return () => {
beforeEach(() => {
@@ -101,8 +77,8 @@ describe("Scaffold", () => {
templates: ["input"],
verbose: 0,
})
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
const data = readFileSync(process.cwd() + "/output/app_name.txt")
expect(data.toString()).toBe("Hello, my app is app_name")
})
test("should create with config", async () => {
@@ -114,26 +90,8 @@ describe("Scaffold", () => {
verbose: 0,
})
const data = readFileSync(join(process.cwd(), "output", "app_name", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
})
)
describe(
"binary files",
withMock(fileStructWithBinary, () => {
test("should copy as-is", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
})
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
const dataBin = readFileSync(join(process.cwd(), "output", "app_name.bin"))
expect(dataBin).toEqual(fileStructWithBinary.input["{{name}}.bin"])
const data = readFileSync(process.cwd() + "/output/app_name/app_name.txt")
expect(data.toString()).toBe("Hello, my app is app_name")
})
})
)
@@ -158,8 +116,8 @@ describe("Scaffold", () => {
verbose: 0,
})
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my value is 1")
const data = readFileSync(process.cwd() + "/output/app_name.txt")
expect(data.toString()).toBe("Hello, my value is 1")
})
test("should overwrite with config", async () => {
@@ -180,8 +138,8 @@ describe("Scaffold", () => {
verbose: 0,
})
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my value is 2")
const data = readFileSync(process.cwd() + "/output/app_name.txt")
expect(data.toString()).toBe("Hello, my value is 2")
})
})
)
@@ -209,44 +167,7 @@ describe("Scaffold", () => {
})
).rejects.toThrow()
await expect(
Scaffold({
name: "app_name",
output: "output",
templates: ["non-existing-input/non-existing-file.txt"],
data: { value: "1" },
verbose: 0,
})
).rejects.toThrow()
expect(() => readFileSync(join(process.cwd(), "output", "app_name.txt"))).toThrow()
})
})
)
describe(
"dry run",
withMock(fileStructNormal, () => {
let consoleMock1: jest.SpyInstance
beforeAll(() => {
consoleMock1 = jest.spyOn(console, "error").mockImplementation(() => void 0)
})
afterAll(() => {
consoleMock1.mockRestore()
})
test("should not write to disk", async () => {
Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
data: { value: "1" },
verbose: 0,
dryRun: true,
})
expect(() => readFileSync(join(process.cwd(), "output", "app_name.txt"))).toThrow()
expect(() => readFileSync(process.cwd() + "/output/app_name.txt")).toThrow()
})
})
)
@@ -257,13 +178,13 @@ describe("Scaffold", () => {
test("should allow override function", async () => {
await Scaffold({
name: "app_name",
output: (fullPath, basedir, basename) => join("custom-output", `${basename.split(".")[0]}`),
output: (fullPath, basedir, basename) => `custom-output/${basename.split(".")[0]}`,
templates: ["input"],
data: { value: "1" },
verbose: 0,
})
const data = readFileSync(join(process.cwd(), "/custom-output/app_name/app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
const data = readFileSync(process.cwd() + "/custom-output/app_name/app_name.txt")
expect(data.toString()).toBe("Hello, my app is app_name")
})
})
)
@@ -280,16 +201,16 @@ describe("Scaffold", () => {
verbose: 0,
})
const rootDir = readdirSync(join(process.cwd(), "output"))
const dir = readdirSync(join(process.cwd(), "output", "AppName"))
const nestedDir = readdirSync(join(process.cwd(), "output", "AppName", "moreNesting"))
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(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"))
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!")
@@ -298,204 +219,56 @@ describe("Scaffold", () => {
)
describe(
"capitalization helpers",
"helpers",
withMock(fileStructHelpers, () => {
const _helpers: Record<string, (text: string) => string> = {
add1: (text) => text + " 1",
}
test("should work", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
helpers: _helpers,
})
describe("default helpers", () => {
test("should work", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
helpers: _helpers,
})
const results = {
camelCase: "appName",
snakeCase: "app_name",
startCase: "App Name",
kebabCase: "app-name",
hyphenCase: "app-name",
pascalCase: "AppName",
lowerCase: "app_name",
upperCase: "APP_NAME",
}
for (const key in results) {
const file = readFileSync(join(process.cwd(), "output", "defaults", `${key}.txt`))
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
const results = {
camelCase: "appName",
snakeCase: "app_name",
startCase: "App Name",
kebabCase: "app-name",
hyphenCase: "app-name",
pascalCase: "AppName",
lowerCase: "app_name",
upperCase: "APP_NAME",
}
for (const key in results) {
const file = readFileSync(process.cwd() + `/output/defaults/${key}.txt`)
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
})
})
)
describe(
"date helpers",
withMock(fileStructDates, () => {
test("should work", async () => {
const now = new Date()
const yesterday = dateFns.add(new Date(), { days: -1 })
const customDate = dateFns.formatISO(dateFns.add(new Date(), { days: -1 }))
describe("custom helpers", () => {
test("should work", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
helpers: _helpers,
})
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
data: { customDate },
const results = {
add1: "app_name 1",
}
for (const key in results) {
const file = readFileSync(process.cwd() + `/output/custom/${key}.txt`)
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
const nowFile = readFileSync(join(process.cwd(), "output", "now.txt"))
const offsetFile = readFileSync(join(process.cwd(), "output", "offset.txt"))
const customFile = readFileSync(join(process.cwd(), "output", "custom.txt"))
// "now.txt": "Today is {{ now 'mmm' }}, time is {{ now 'HH:mm' }}",
// "offset.txt": "Yesterday was {{ now 'mmm' -1 'days' }}, time is {{ now 'HH:mm' -1 'days' }}",
// "custom.txt": "Custom date is {{ date customDate 'mmm' }}, time is {{ date customDate 'HH:mm' }}",
expect(nowFile.toString()).toEqual(
`Today is ${dateFns.format(now, "mmm")}, time is ${dateFns.format(now, "HH:mm")}`
)
expect(offsetFile.toString()).toEqual(
`Yesterday was ${dateFns.format(yesterday, "mmm")}, time is ${dateFns.format(yesterday, "HH:mm")}`
)
expect(customFile.toString()).toEqual(
`Custom date is ${dateFns.format(dateFns.parseISO(customDate), "mmm")}, time is ${dateFns.format(
dateFns.parseISO(customDate),
"HH:mm"
)}`
)
})
})
)
describe(
"custom helpers",
withMock(fileStructHelpers, () => {
const _helpers: Record<string, (text: string) => string> = {
add1: (text) => text + " 1",
}
test("should work", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
helpers: _helpers,
})
const results = {
add1: "app_name 1",
}
for (const key in results) {
const file = readFileSync(join(process.cwd(), "output", "custom", `${key}.txt`))
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
})
)
describe(
"transform subfolder",
withMock(fileStructSubdirTransformer, () => {
test("should work with no helper", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
createSubFolder: true,
verbose: 0,
})
const data = readFileSync(join(process.cwd(), "output", "app_name", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
test("should work with default helper", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
createSubFolder: true,
verbose: 0,
subFolderNameHelper: "upperCase",
})
const data = readFileSync(join(process.cwd(), "output", "APP_NAME", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
test("should work with custom helper", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
createSubFolder: true,
verbose: 0,
subFolderNameHelper: "test",
helpers: {
test: () => "REPLACED",
},
})
const data = readFileSync(join(process.cwd(), "output", "REPLACED", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
})
)
describe(
"before write",
withMock(fileStructNormal, () => {
test("should work with no callback", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
data: {
value: "value",
},
})
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
test("should work with custom callback", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
data: {
value: "value",
},
beforeWrite: (content, beforeContent, outputPath) =>
[content.toString().toUpperCase(), beforeContent, outputPath].join(", "),
})
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual(
[
"Hello, my app is app_name".toUpperCase(),
fileStructNormal.input["{{name}}.txt"],
join(process.cwd(), "output", "app_name.txt"),
].join(", ")
)
})
test("should work with undefined response custom callback", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
data: {
value: "value",
},
beforeWrite: () => undefined,
})
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
})
)

View File

@@ -1,91 +0,0 @@
import { dateHelper, handlebarsParse, nowHelper } from "../src/utils"
import { ScaffoldConfig } from "../src/types"
import path from "path"
import * as dateFns from "date-fns"
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(
Buffer.from("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(
Buffer.from("/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(Buffer.from("/home/test/test {{escaped}}.txt"))
})
})
describe("Helpers", () => {
describe("date helpers", () => {
describe("now", () => {
test("should work without extra params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(nowHelper(fmt)).toEqual(dateFns.format(now, fmt))
})
})
describe("date", () => {
test("should work with no offset params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(dateHelper(now.toISOString(), fmt)).toEqual(dateFns.format(now, fmt))
})
test("should work with offset params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(dateHelper(now.toISOString(), fmt, -1, "days")).toEqual(
dateFns.format(dateFns.add(now, { days: -1 }), fmt)
)
expect(dateHelper(now.toISOString(), fmt, 1, "months")).toEqual(
dateFns.format(dateFns.add(now, { months: 1 }), fmt)
)
})
})
})
})
})

View File

@@ -1,26 +0,0 @@
{
"name": "Simple Scaffold",
"entryPoints": [
"src/index.ts"
],
"includeVersion": true,
"categorizeByGroup": false,
"sort": [
"visibility"
],
"categoryOrder": [
"Main",
"*"
],
"theme": "doc-theme",
"githubPages": true,
"entryPointStrategy": "expand",
"out": "docs",
"excludePrivate": true,
"excludeProtected": true,
"excludeInternal": true,
"gaID": "GTM-KHQS9TQ",
"validation": {
"invalidLink": true
}
}

131
yarn.lock
View File

@@ -537,6 +537,11 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.1.tgz#a6ca6a9a0ff366af433f42f5f0e124794ff6b8f1"
integrity sha512-FTgBI767POY/lKNDNbIzgAX6miIDBs6NTCbdlDb8TrWovHsSvaVIZDlTqym29C6UqhzwcJx4CYr+AlrMywA0cA==
"@types/args@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@types/args/-/args-3.0.1.tgz#59e8e787ed8a810408dd9a324157cc20b7001468"
integrity sha512-Mfre8T2comeJbw2D6W8mzQP+0Q8fpS7nkbHgatzU31tWsLs0Lkyc+ObdYfgV4SuMZn/n5MEWlxh2rc25125s0Q==
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
version "7.1.15"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024"
@@ -711,9 +716,9 @@ ansi-escapes@^4.2.1:
type-fest "^0.21.3"
ansi-regex@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
ansi-styles@^3.2.1:
version "3.2.1"
@@ -754,6 +759,16 @@ 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"
@@ -833,13 +848,6 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
braces@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@@ -895,6 +903,11 @@ 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"
@@ -910,7 +923,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.0.0:
chalk@2.4.2, 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==
@@ -1057,11 +1070,6 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
date-fns@^2.28.0:
version "2.28.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
debug@4, debug@^4.1.0, debug@^4.1.1:
version "4.3.2"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
@@ -1121,9 +1129,6 @@ diff@^4.0.1:
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
"doc-theme@file:./doc-theme":
version "1.0.0"
domexception@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
@@ -1355,18 +1360,6 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^11.1.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -2107,16 +2100,16 @@ json5@2.x, json5@^2.1.2:
dependencies:
minimist "^1.2.5"
jsonc-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22"
integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==
kleur@^3.0.3:
version "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"
@@ -2149,11 +2142,6 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
lunr@^2.3.9:
version "2.3.9"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
make-dir@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
@@ -2173,11 +2161,6 @@ makeerror@1.0.x:
dependencies:
tmpl "1.0.x"
marked@^4.0.12:
version "4.0.14"
resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.14.tgz#7a3a5fa5c80580bac78c1ed2e3b84d7bd6fc3870"
integrity sha512-HL5sSPE/LP6U9qKgngIIPTthuxC0jrfxpYMZ3LdGDD3vTnLs59m2Z7r6+LNDR3ToqEQdkKd6YaaEfJhodJmijQ==
massarg@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/massarg/-/massarg-1.0.5.tgz#3dfd49bb63bfca4b0371a2f0ffc7580a9a6cc7a0"
@@ -2223,17 +2206,10 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimatch@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
dependencies:
brace-expansion "^2.0.1"
minimist@^1.2.5:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mkdirp@1.x:
version "1.0.4"
@@ -2245,6 +2221,11 @@ 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"
@@ -2488,7 +2469,7 @@ resolve@^1.20.0:
is-core-module "^2.2.0"
path-parse "^1.0.6"
rimraf@^3.0.0, rimraf@^3.0.2:
rimraf@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
@@ -2536,15 +2517,6 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shiki@^0.10.1:
version "0.10.1"
resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.10.1.tgz#6f9a16205a823b56c072d0f1a0bcd0f2646bef14"
integrity sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==
dependencies:
jsonc-parser "^3.0.0"
vscode-oniguruma "^1.6.1"
vscode-textmate "5.2.0"
signal-exit@^3.0.2, signal-exit@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
@@ -2702,9 +2674,9 @@ throat@^6.0.1:
integrity sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==
tmpl@1.0.x:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
to-fast-properties@^2.0.0:
version "2.0.0"
@@ -2790,17 +2762,6 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typedoc@^0.22.15:
version "0.22.15"
resolved "https://registry.yarnpkg.com/typedoc/-/typedoc-0.22.15.tgz#c6ad7ed9d017dc2c3a06c9189cb392bd8e2d8c3f"
integrity sha512-CMd1lrqQbFvbx6S9G6fL4HKp3GoIuhujJReWqlIvSb2T26vGai+8Os3Mde7Pn832pXYemd9BMuuYWhFpL5st0Q==
dependencies:
glob "^7.2.0"
lunr "^2.3.9"
marked "^4.0.12"
minimatch "^5.0.1"
shiki "^0.10.1"
typescript@^4.3.5:
version "4.3.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
@@ -2846,16 +2807,6 @@ v8-to-istanbul@^8.0.0:
convert-source-map "^1.6.0"
source-map "^0.7.3"
vscode-oniguruma@^1.6.1:
version "1.6.2"
resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz#aeb9771a2f1dbfc9083c8a7fdd9cccaa3f386607"
integrity sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==
vscode-textmate@5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-5.2.0.tgz#01f01760a391e8222fe4f33fbccbd1ad71aed74e"
integrity sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==
w3c-hr-time@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"