mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-18 01:29:09 +00:00
Compare commits
23 Commits
v1.3.1
...
v1.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e175e6815 | ||
|
|
0742fc8771 | ||
|
|
0ecf07de18 | ||
|
|
51081cf2ee | ||
|
|
5a39c08a9f | ||
|
|
83e5724152 | ||
|
|
7455ac1ead | ||
|
|
f315373d18 | ||
|
|
38d557d450 | ||
|
|
a27389e262 | ||
|
|
ce9fededf0 | ||
|
|
4653428bda | ||
|
|
19b2203f30 | ||
|
|
3f103cd4c2 | ||
|
|
ec5503bc3e | ||
|
|
19ad60c3cb | ||
|
|
f04dd7e487 | ||
|
|
4c9a838ebe | ||
|
|
6700708e1d | ||
|
|
2727bd526c | ||
|
|
834a805794 | ||
|
|
ba9434f504 | ||
|
|
59353f4121 |
@@ -3,6 +3,3 @@ tab_width = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
5
.github/FUNDING.yml
vendored
5
.github/FUNDING.yml
vendored
@@ -1,6 +1,6 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: chenasraf
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: casraf
|
||||
@@ -9,5 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom:
|
||||
- "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22¤cy_code=ILS&source=url"
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
|
||||
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
46
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,47 +1,37 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] "
|
||||
title: ''
|
||||
labels: bug, needs-triage
|
||||
assignees: chenasraf
|
||||
|
||||
---
|
||||
|
||||
#### Describe the bug
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
#### How To Reproduce
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Prepare templates:
|
||||
|
||||
```text
|
||||
This is my {{ template }}
|
||||
```
|
||||
|
||||
```
|
||||
template contents
|
||||
```
|
||||
2. Run with args/config:
|
||||
|
||||
```shell
|
||||
npx simple-scaffold@latest -t input -o output TplName
|
||||
```
|
||||
|
||||
#### Expected behavior\*\*
|
||||
|
||||
```
|
||||
npx simple-scaffold@latest -t ... -o output TplName
|
||||
```
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
#### Logs
|
||||
**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.
|
||||
|
||||
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. macOS, Windows, Linux]
|
||||
- OS Version: [e.g. Big Sur, 11, Ubuntu 20.04]
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Node.js: [e.g. 16.8]
|
||||
- Simple Scaffold Version [e.g. 1.1.2]
|
||||
|
||||
#### Additional context
|
||||
- Simple Scaffold Version [e.g. 1.0.4]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,24 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] "
|
||||
title: ''
|
||||
labels: enhancement, needs-triage
|
||||
assignees: chenasraf
|
||||
|
||||
---
|
||||
|
||||
#### Is your feature request related to a problem? Please describe.
|
||||
|
||||
**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
|
||||
|
||||
**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, if
|
||||
applicable.
|
||||
|
||||
#### Additional context
|
||||
**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.
|
||||
|
||||
52
.github/workflows/develop.yml
vendored
Normal file
52
.github/workflows/develop.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Alpha Releases
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [develop]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "12.x"
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn test
|
||||
- run: yarn build
|
||||
- run: cd ./dist && yarn pack --filename=../package.tgz
|
||||
if: "!contains(github.event.head_commit.message, '[skip publish]')"
|
||||
- uses: Klemensas/action-autotag@stable
|
||||
if: "!contains(github.event.head_commit.message, '[skip publish]')"
|
||||
id: update_tag
|
||||
with:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
tag_prefix: "v"
|
||||
- name: Publish on NPM
|
||||
uses: JS-DevTools/npm-publish@v1
|
||||
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, '[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, '[skip publish]')
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./package.tgz
|
||||
asset_name: simple-scaffold ${{ steps.update_tag.outputs.tagname }}.tgz
|
||||
asset_content_type: application/tgz
|
||||
20
.github/workflows/docs.yml
vendored
20
.github/workflows/docs.yml
vendored
@@ -1,20 +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@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "18.x"
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build-docs
|
||||
- uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./docs
|
||||
6
.github/workflows/pull_requests.yml
vendored
6
.github/workflows/pull_requests.yml
vendored
@@ -8,10 +8,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "18.x"
|
||||
node-version: "12.x"
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build
|
||||
- run: yarn test
|
||||
|
||||
44
.github/workflows/release.yml
vendored
44
.github/workflows/release.yml
vendored
@@ -1,22 +1,50 @@
|
||||
name: Semantic Release
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, develop, feat/*, fix/*]
|
||||
branches: [master]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "18.x"
|
||||
node-version: "12.x"
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn test
|
||||
- run: yarn build
|
||||
- run: yarn semantic-release
|
||||
- run: cd ./dist && yarn pack --filename=../package.tgz
|
||||
if: "!contains(github.event.head_commit.message, '[skip publish]')"
|
||||
env:
|
||||
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
|
||||
- uses: Klemensas/action-autotag@stable
|
||||
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
|
||||
with:
|
||||
package: ./dist/package.json
|
||||
token: "${{ secrets.NPM_TOKEN }}"
|
||||
- name: Create Release
|
||||
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:
|
||||
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, '[skip publish]')
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./package.tgz
|
||||
asset_name: simple-scaffold ${{ steps.update_tag.outputs.tagname }}.tgz
|
||||
asset_content_type: application/tgz
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -42,9 +42,6 @@ typings/
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# NPM
|
||||
.npmrc
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
@@ -64,5 +61,3 @@ examples/test-output/**/*
|
||||
dist/
|
||||
.DS_Store
|
||||
tmp/
|
||||
docs/
|
||||
.nvmrc
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"line-length": {
|
||||
"MD013": {
|
||||
"line_length": 100,
|
||||
"tables": false,
|
||||
"code_blocks": false
|
||||
},
|
||||
"no-inline-html": false,
|
||||
"first-line-h1": false
|
||||
"MD033": false
|
||||
}
|
||||
|
||||
12
.prettierrc
12
.prettierrc
@@ -1,15 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.md",
|
||||
"options": {
|
||||
"printWidth": 100,
|
||||
"proseWrap": "always"
|
||||
}
|
||||
}
|
||||
]
|
||||
"tabWidth": 2
|
||||
}
|
||||
|
||||
50
.vscode/launch.json
vendored
50
.vscode/launch.json
vendored
@@ -1,27 +1,29 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Scaffold",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"protocol": "inspector",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/test.ts",
|
||||
"outFiles": ["${workspaceRoot}/dist/test.js"],
|
||||
"env": {
|
||||
"NODE_ENV": "develop"
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Scaffold",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"protocol": "inspector",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/test.ts",
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/dist/test.js"
|
||||
],
|
||||
"env": {
|
||||
"NODE_ENV": "develop"
|
||||
},
|
||||
"sourceMaps": true,
|
||||
},
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach by Process ID",
|
||||
"processId": "${command:PickProcess}"
|
||||
}
|
||||
]
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach by Process ID",
|
||||
"processId": "${command:PickProcess}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -3,20 +3,12 @@
|
||||
"npm.packageManager": "yarn",
|
||||
"cSpell.words": [
|
||||
"massarg",
|
||||
"MYCOMPONENT",
|
||||
"myname",
|
||||
"nobrace",
|
||||
"nocomment",
|
||||
"nodir",
|
||||
"noext",
|
||||
"nonegate",
|
||||
"subdir",
|
||||
"variabletoken"
|
||||
],
|
||||
"[markdown]": {
|
||||
"editor.rulers": [
|
||||
87,
|
||||
100
|
||||
],
|
||||
},
|
||||
"subdir"
|
||||
]
|
||||
}
|
||||
|
||||
26
.vscode/tasks.json
vendored
26
.vscode/tasks.json
vendored
@@ -5,55 +5,49 @@
|
||||
"script": "build",
|
||||
"label": "build",
|
||||
"type": "npm",
|
||||
"problemMatcher": []
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"script": "dev",
|
||||
"label": "dev",
|
||||
"type": "npm",
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"command": "yarn typedoc --watch",
|
||||
"label": "typedoc --watch",
|
||||
"type": "shell",
|
||||
"problemMatcher": []
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"script": "start",
|
||||
"label": "start",
|
||||
"type": "npm",
|
||||
"problemMatcher": []
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"script": "test",
|
||||
"label": "test",
|
||||
"type": "npm",
|
||||
"problemMatcher": []
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"command": "yarn test --watchAll",
|
||||
"label": "yarn test --watchAll",
|
||||
"type": "shell",
|
||||
"problemMatcher": []
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"script": "cmd",
|
||||
"label": "cmd",
|
||||
"type": "npm",
|
||||
"problemMatcher": []
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"script": "build-test",
|
||||
"label": "build-test",
|
||||
"type": "npm",
|
||||
"problemMatcher": []
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
"script": "build-cmd",
|
||||
"label": "build-cmd",
|
||||
"type": "npm",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
"problemMatcher": [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
120
CHANGELOG.md
120
CHANGELOG.md
@@ -1,120 +0,0 @@
|
||||
# Change Log
|
||||
|
||||
## [1.3.1](https://github.com/chenasraf/simple-scaffold/compare/v1.3.0...v1.3.1) (2023-04-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* docs ([6e19a86](https://github.com/chenasraf/simple-scaffold/commit/6e19a86190dd924058a48448aa6463569ef1125f))
|
||||
* remove old peer-dep ([c7e2ef8](https://github.com/chenasraf/simple-scaffold/commit/c7e2ef862cb658feb1071ac120b185d8b34d6dd3))
|
||||
|
||||
## [1.3.0](https://github.com/chenasraf/simple-scaffold/compare/v1.2.0...v1.3.0) (2023-04-25)
|
||||
|
||||
### Features
|
||||
|
||||
- load scaffold config from files
|
||||
([c398976](https://github.com/chenasraf/simple-scaffold/commit/c3989769fee445c9183ff5e5b3892c4e9fb66a9e))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- config option should not be mandatory
|
||||
([3db6a91](https://github.com/chenasraf/simple-scaffold/commit/3db6a918f13d9300efa2fcb4a356d004475ab91c))
|
||||
- export config file type
|
||||
([4302eb5](https://github.com/chenasraf/simple-scaffold/commit/4302eb5ce35ed6cf1dc80dfb92790c3fdd96f963))
|
||||
|
||||
## [1.2.0](https://github.com/chenasraf/simple-scaffold/compare/v1.1.4...v1.2.0) (2023-04-24)
|
||||
|
||||
### Features
|
||||
|
||||
- append-data cli flag
|
||||
([3c5c2de](https://github.com/chenasraf/simple-scaffold/commit/3c5c2ded02f61ff086e81ea4a7f40529bdff1c9d))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- ci node version
|
||||
([767d34c](https://github.com/chenasraf/simple-scaffold/commit/767d34c684516d4cea865b25e87c27c779bb79ce))
|
||||
- semantic-release build dir
|
||||
([f7956dd](https://github.com/chenasraf/simple-scaffold/commit/f7956ddc786018905c48ccf1f21a3bb4657c3d75))
|
||||
- support quote wrapping in append-data
|
||||
([4fecca8](https://github.com/chenasraf/simple-scaffold/commit/4fecca848347312d45d704f82f2bcb3822da9b06))
|
||||
|
||||
## [1.1.4](https://github.com/chenasraf/simple-scaffold/compare/v1.1.3...v1.1.4) (2023-03-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- github action node version
|
||||
([7c19c53](https://github.com/chenasraf/simple-scaffold/commit/7c19c533376dc6904231e5cc51c7a4b2658c66e0))
|
||||
- github action node version
|
||||
([94fec76](https://github.com/chenasraf/simple-scaffold/commit/94fec766165f7540c578dbf2d0aeeb6ea3969ad8))
|
||||
|
||||
### Misc
|
||||
|
||||
- update typedoc version
|
||||
([c334396](https://github.com/chenasraf/simple-scaffold/commit/c334396d74414cbe0aba305c66dfad7fdeb88669))
|
||||
- update dependencies
|
||||
([20400bd](https://github.com/chenasraf/simple-scaffold/commit/20400bd81dd43d457427675286c9964a8bc0d5f6))
|
||||
- bump version number
|
||||
([8e432bf](https://github.com/chenasraf/simple-scaffold/commit/8e432bfb0b410dc0655c3924031bea2648a42ad0))
|
||||
|
||||
## [1.1.3](https://github.com/chenasraf/simple-scaffold/compare/v1.1.2...v1.1.3) (2023-03-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- base path
|
||||
([943717a](https://github.com/chenasraf/simple-scaffold/commit/943717a76998ec0609f2072c886df6b4775f2ea2))
|
||||
- binary files + add tests
|
||||
([e450ad2](https://github.com/chenasraf/simple-scaffold/commit/e450ad242ed70ae928b19964da38cdcb1b6cf659))
|
||||
|
||||
## [1.1.0](https://github.com/chenasraf/simple-scaffold/compare/v1.0.4...v1.1.0) (2022-04-21)
|
||||
|
||||
## [1.0.3](https://github.com/chenasraf/simple-scaffold/compare/v1.0.2...v1.0.3) (2022-03-03)
|
||||
|
||||
## [1.0.1-pre.1](https://github.com/chenasraf/simple-scaffold/compare/v1.0.0...v1.0.1-pre.1) (2022-02-17)
|
||||
|
||||
## [0.7.5](https://github.com/chenasraf/simple-scaffold/compare/v0.7.4...v0.7.5) (2021-09-26)
|
||||
|
||||
## [0.7.4](https://github.com/chenasraf/simple-scaffold/compare/v0.7.3...v0.7.4) (2021-09-26)
|
||||
|
||||
## [0.7.3](https://github.com/chenasraf/simple-scaffold/compare/v0.7.2...v0.7.3) (2021-09-26)
|
||||
|
||||
## [0.7.2](https://github.com/chenasraf/simple-scaffold/compare/v0.6.1...v0.7.2) (2021-04-19)
|
||||
|
||||
## [0.6.1](https://github.com/chenasraf/simple-scaffold/compare/v0.6.0...v0.6.1) (2021-02-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- binary files
|
||||
([7c0c347](https://github.com/chenasraf/simple-scaffold/commit/7c0c3470020d7c166ea68a8effa6df65ec38f2c8))
|
||||
|
||||
## [0.6.0](https://github.com/chenasraf/simple-scaffold/compare/v0.5.0...v0.6.0) (2021-02-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- support deeper file structure
|
||||
([4afafa5](https://github.com/chenasraf/simple-scaffold/commit/4afafa5a4af2e3f4b0af54f20811ecb2c8d98560))
|
||||
|
||||
## [0.5.0](https://github.com/chenasraf/simple-scaffold/compare/v0.4.5...v0.5.0) (2019-02-27)
|
||||
|
||||
## [0.4.5](https://github.com/chenasraf/simple-scaffold/compare/v0.4.4...v0.4.5) (2019-02-27)
|
||||
|
||||
## [0.4.4](https://github.com/chenasraf/simple-scaffold/compare/v0.4.3...v0.4.4) (2019-02-27)
|
||||
|
||||
## [0.4.3](https://github.com/chenasraf/simple-scaffold/compare/v0.4.2...v0.4.3) (2019-02-27)
|
||||
|
||||
## [0.4.2](https://github.com/chenasraf/simple-scaffold/compare/v0.4.1...v0.4.2) (2019-02-25)
|
||||
|
||||
## [0.4.1](https://github.com/chenasraf/simple-scaffold/compare/v0.3.1...v0.4.1) (2019-02-25)
|
||||
|
||||
## [0.3.1](https://github.com/chenasraf/simple-scaffold/compare/v0.3.0...v0.3.1) (2018-01-15)
|
||||
|
||||
## [0.3.0](https://github.com/chenasraf/simple-scaffold/compare/v0.2.0...v0.3.0) (2018-01-15)
|
||||
|
||||
## [0.2.0](https://github.com/chenasraf/simple-scaffold/compare/v0.1.5...v0.2.0) (2018-01-05)
|
||||
|
||||
## [0.1.5](https://github.com/chenasraf/simple-scaffold/compare/v0.1.4...v0.1.5) (2018-01-01)
|
||||
|
||||
## [0.1.4](https://github.com/chenasraf/simple-scaffold/compare/v0.1.3...v0.1.4) (2018-01-01)
|
||||
|
||||
## [0.1.3](https://github.com/chenasraf/simple-scaffold/compare/v0.1.2...v0.1.3) (2018-01-01)
|
||||
|
||||
## 0.1.2 (2018-01-01)
|
||||
@@ -1,20 +1,16 @@
|
||||
# Migrating from 0.x to 1.0
|
||||
|
||||
In Simple Scaffold v1.0, the entire codebase was overhauled, yet usage remains mostly the same
|
||||
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.
|
||||
- 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
|
||||
|
||||
- `locals` has been renamed to `data`. The appropriate command line args have been updated as well
|
||||
to `--data` | `-d`.
|
||||
- Additional options have been added to both CLI and Node interfaces. See
|
||||
[Command Line Interface (CLI) usage](https://chenasraf.github.io/simple-scaffold/pages/cli.html)
|
||||
and [Node.js usage](https://chenasraf.github.io/simple-scaffold/pages/node.html) for more
|
||||
information.
|
||||
- `locals` has been renamed to `data`. The appropriate command line args have been updated as
|
||||
well to `--data` | `-d`.
|
||||
|
||||
## Template syntax changes
|
||||
|
||||
398
README.md
398
README.md
@@ -1,92 +1,361 @@
|
||||
<h1 align="center">Simple Scaffold</h1>
|
||||
# Simple Scaffold
|
||||
|
||||
<h2 align="center">
|
||||
Simple Scaffold allows you to generate any set of files in the easiest way possible with simple commands.
|
||||
|
||||
[GitHub](https://github.com/chenasraf/simple-scaffold) |
|
||||
[Documentation](https://casraf.dev/simple-scaffold) |
|
||||
[NPM](https://npmjs.com/package/simple-scaffold) | [casraf.dev](https://casraf.dev)
|
||||
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.
|
||||
|
||||
</h2>
|
||||
<br />
|
||||
|
||||
Looking to streamline your workflow and get your projects up and running quickly? Look no further
|
||||
than Simple Scaffold - the easy-to-use NPM package that simplifies the process of organizing and
|
||||
copying your commonly-created files.
|
||||
<details>
|
||||
<summary>Table of contents</summary>
|
||||
|
||||
With its agnostic and un-opinionated approach, Simple Scaffold can handle anything from a few simple
|
||||
files to an entire app boilerplate setup. Plus, with the power of **Handlebars.js** syntax, you can
|
||||
easily replace custom data and personalize your files to fit your exact needs. But that's not all -
|
||||
you can also use it to loop through data, use conditions, and write custom functions using helpers.
|
||||
- [Simple Scaffold](#simple-scaffold)
|
||||
- [Install](#install)
|
||||
- [Use as a command line tool](#use-as-a-command-line-tool)
|
||||
- [Command Line Options](#command-line-options)
|
||||
- [Use in Node.js](#use-in-nodejs)
|
||||
- [Node-specific options](#node-specific-options)
|
||||
- [Preparing files](#preparing-files)
|
||||
- [Template files](#template-files)
|
||||
- [Variable/token replacement](#variabletoken-replacement)
|
||||
- [Built-in Helpers](#built-in-helpers)
|
||||
- [Capitalization Helpers](#capitalization-helpers)
|
||||
- [Date helpers](#date-helpers)
|
||||
- [Custom Helpers](#custom-helpers)
|
||||
- [Examples](#examples)
|
||||
- [Command Example](#command-example)
|
||||
- [Example Scaffold Input](#example-scaffold-input)
|
||||
- [Input Directory structure](#input-directory-structure)
|
||||
- [Contents of `project/scaffold/{{Name}}.jsx`](#contents-of-projectscaffoldnamejsx)
|
||||
- [Example Scaffold Output](#example-scaffold-output)
|
||||
- [Output directory structure](#output-directory-structure)
|
||||
- [Contents of `project/scaffold/MyComponent/MyComponent.jsx`](#contents-of-projectscaffoldmycomponentmycomponentjsx)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
Don't waste any more time manually copying and pasting files - let Simple Scaffold do the heavy
|
||||
lifting for you and start building your projects faster and more efficiently today!
|
||||
|
||||
<div align="center">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
## Install
|
||||
|
||||
The fastest way to get started is to use `npx` to immediately start a scaffold process.
|
||||
You can either use it as a command line tool or import into your own code and run from there.
|
||||
|
||||
Prepare any templates you want to use - for example, in the directory `templates/component`; and use
|
||||
that in the CLI args. Here is a simple example file:
|
||||
```bash
|
||||
# npm
|
||||
npm install [-g] simple-scaffold
|
||||
# yarn
|
||||
yarn [global] add simple-scaffold
|
||||
# run without installing
|
||||
npx simple-scaffold@latest <...args>
|
||||
```
|
||||
|
||||
`templates/component/{{ pascalName name }}.tsx`
|
||||
## Use as a command line tool
|
||||
|
||||
```tsx
|
||||
// Created: {{ now | 'yyyy-MM-dd' }}
|
||||
import React from 'react'
|
||||
### Command Line Options
|
||||
|
||||
export default {{pascalCase name}}: React.FC = (props) => {
|
||||
return (
|
||||
<div className="{{camelCase name}}">{{pascalCase name}} Component</div>
|
||||
)
|
||||
```plaintext
|
||||
Usage: simple-scaffold [options]
|
||||
|
||||
Create structured files based on templates.
|
||||
|
||||
Options:
|
||||
|
||||
--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.
|
||||
|
||||
--output|-o Path to output to. If --create-sub-folder is enabled,
|
||||
the subfolder will be created inside this path.
|
||||
(default: current dir)
|
||||
|
||||
--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.
|
||||
|
||||
--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.
|
||||
|
||||
--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)
|
||||
|
||||
--verbose|-v Determine amount of logs to display. The values are:
|
||||
0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4
|
||||
(error). The provided level will display messages of
|
||||
the same level or higher. (default:
|
||||
2)
|
||||
|
||||
--dry-run|-dr Don't emit files. This is good for testing your
|
||||
scaffolds and making sure they don't fail, without having to
|
||||
write actual file contents or create directories.
|
||||
(default: false)
|
||||
```
|
||||
|
||||
You can also add this as a script in your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"scaffold": "npx simple-scaffold@latest -t scaffolds/component/**/* -o src/components -d '{\"myProp\": \"propName\", \"myVal\": 123}'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To generate the template output, run:
|
||||
## Use in Node.js
|
||||
|
||||
```shell
|
||||
# generate single component
|
||||
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.
|
||||
The config takes similar arguments to the command line:
|
||||
|
||||
```typescript
|
||||
import Scaffold from "simple-scaffold"
|
||||
|
||||
const config = {
|
||||
name: "component",
|
||||
templates: [path.join(__dirname, "scaffolds", "component")],
|
||||
output: path.join(__dirname, "src", "components"),
|
||||
createSubFolder: true,
|
||||
subFolderNameHelper: "upperCase"
|
||||
data: {
|
||||
property: "value",
|
||||
},
|
||||
helpers: {
|
||||
twice: (text) => [text, text].join(" ")
|
||||
},
|
||||
beforeWrite: (content, rawContent, outputPath) => content.toString().toUpperCase()
|
||||
}
|
||||
|
||||
const scaffold = Scaffold(config)
|
||||
```
|
||||
|
||||
### Node-specific options
|
||||
|
||||
In addition to all the options available in the command line, there are some Node/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. 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. |
|
||||
|
||||
## Preparing files
|
||||
|
||||
### Template files
|
||||
|
||||
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 | Output path(s) |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| `./templates/{{ name }}.txt` | `src/AppName.txt` |
|
||||
| `./templates/directory` <br /><br /> Directory contents:<br /> <ol><li>`outer/{{name}}.txt`</li></li><li>`outer2/inner/{{name.txt}}`</li></ol> | `src/outer/AppName.txt`,<br />`src/outer2/inner/AppName.txt` |
|
||||
| `./templates/others/**/*.txt` <br /><br /> Directory contents:<br /> <ol><li>`outer/{{name}}.jpg`</li></li><li>`outer2/inner/{{name.txt}}`</li></ol> | `src/outer2/inner/AppName.txt` |
|
||||
|
||||
### Variable/token replacement
|
||||
|
||||
Scaffolding will replace `{{ varName }}` in both the file name and its contents and put the
|
||||
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 \
|
||||
-t templates/component -o src/components PageWrapper
|
||||
--templates templates/components/{{name}}.jsx \
|
||||
--output src/components \
|
||||
-create-sub-folder true \
|
||||
MyComponent
|
||||
```
|
||||
|
||||
This will immediately create the following file: `src/components/PageWrapper.tsx`
|
||||
Will output a file with the path:
|
||||
|
||||
```tsx
|
||||
// Created: 2077/01/01
|
||||
```plaintext
|
||||
<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
|
||||
|
||||
> Simple-Scaffold uses [Handlebars.js](https://handlebarsjs.com/) for outputting the file contents.
|
||||
> 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.
|
||||
|
||||
### Built-in Helpers
|
||||
|
||||
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
|
||||
|
||||
| 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 |
|
||||
|
||||
#### 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
|
||||
|
||||
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
|
||||
the transformation functions. For example, `upperCase` is implemented like so:
|
||||
|
||||
```typescript
|
||||
config.helpers = {
|
||||
upperCase: (text) => text.toUpperCase(),
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Examples
|
||||
|
||||
### Command Example
|
||||
|
||||
```bash
|
||||
simple-scaffold MyComponent \
|
||||
-t project/scaffold/**/* \
|
||||
-o src/components \
|
||||
-d '{"className": "myClassName"}'
|
||||
MyComponent
|
||||
```
|
||||
|
||||
### Example Scaffold Input
|
||||
|
||||
#### Input Directory structure
|
||||
|
||||
```plaintext
|
||||
- project
|
||||
- scaffold
|
||||
- {{Name}}.js
|
||||
- src
|
||||
- components
|
||||
- ...
|
||||
```
|
||||
|
||||
#### Contents of `project/scaffold/{{Name}}.jsx`
|
||||
|
||||
```typescriptreact
|
||||
import React from 'react'
|
||||
|
||||
export default PageWrapper: React.FC = (props) => {
|
||||
export default {{camelCase ame}}: React.FC = (props) => {
|
||||
return (
|
||||
<div className="pageWrapper">PageWrapper Component</div>
|
||||
<div className="{{className}}">{{camelCase name}} Component</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
### Example Scaffold Output
|
||||
|
||||
See full documentation [here](https://chenasraf.github.io/simple-scaffold).
|
||||
### Output directory structure
|
||||
|
||||
- [Command Line Interface (CLI) usage](https://chenasraf.github.io/simple-scaffold/pages/cli.html)
|
||||
- [Node.js usage](https://chenasraf.github.io/simple-scaffold/pages/node.html)
|
||||
- [Templates](https://chenasraf.github.io/simple-scaffold/pages/templates.html)
|
||||
- [Configuration Files](https://chenasraf.github.io/simple-scaffold/pages/configuration_files.html)
|
||||
- [Migrating v0.x to v1.x](https://chenasraf.github.io/simple-scaffold/pages/migration.html)
|
||||
```plaintext
|
||||
- project
|
||||
- src
|
||||
- components
|
||||
- MyComponent
|
||||
- MyComponent.js
|
||||
- ...
|
||||
```
|
||||
|
||||
With `createSubFolder = false`:
|
||||
|
||||
```plaintext
|
||||
- project
|
||||
- src
|
||||
- components
|
||||
- MyComponent.js
|
||||
- ...
|
||||
```
|
||||
|
||||
#### Contents of `project/scaffold/MyComponent/MyComponent.jsx`
|
||||
|
||||
```typescriptreact
|
||||
import React from 'react'
|
||||
|
||||
export default MyComponent: React.FC = (props) => {
|
||||
return (
|
||||
<div className="myClassName">MyComponent Component</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## 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 are feeling incredibly generous and would like to donate
|
||||
just a small amount to help sustain this project, I would be very very thankful!
|
||||
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;'
|
||||
@@ -111,20 +380,11 @@ Some tips on getting around the code:
|
||||
|
||||
- Use `yarn dev` for development - it runs TypeScript compile in watch mode, allowing you to make
|
||||
changes and immediately be able to try them using `yarn cmd`.
|
||||
- Use `yarn build` to build the output once
|
||||
- Use `yarn build` to build the output
|
||||
- Use `yarn test` to run tests
|
||||
- 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.
|
||||
- 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. From here you have several
|
||||
> options:
|
||||
>
|
||||
> - Run `yarn dev` to watch for file changes and build automatically
|
||||
> - Run `yarn build` before running this to trigger a one-time build
|
||||
> - Run `yarn build-cmd` which triggers a build right before running `yarn cmd` automatically with
|
||||
> the rest of the given arguments.
|
||||
|
||||
- Use `yarn build-docs` to build the documentation once
|
||||
- Use `yarn watch-docs` to start docs in watch mode
|
||||
- To see the documentation, currently you have to serve the directory yourself with a static web
|
||||
server (like node's built in serve, VS code's "Go Live" mode, etc)
|
||||
> 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.
|
||||
|
||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: ["@babel/plugin-proposal-nullish-coalescing-operator"],
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB |
@@ -1,13 +0,0 @@
|
||||
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
|
||||
module.exports = {
|
||||
default: {
|
||||
templates: ["examples/test-input/Component"],
|
||||
output: "examples/test-output",
|
||||
data: { property: "myProp", value: "10" },
|
||||
},
|
||||
component: {
|
||||
templates: ["examples/test-input/Component"],
|
||||
output: "examples/test-output",
|
||||
data: { property: "myProp", value: "10" },
|
||||
},
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export default {
|
||||
// The directory where Jest should store its cached dependency information
|
||||
// cacheDirectory: "/private/var/folders/q9/0mns8fgd00b4t5j5lq2wh2yh0000gn/T/jest_dx",
|
||||
|
||||
// Automatically clear mock calls, instances, contexts and results before every test
|
||||
// Automatically clear mock calls and instances between every test
|
||||
clearMocks: true,
|
||||
|
||||
// Indicates whether the coverage information should be collected while executing the test
|
||||
@@ -50,11 +50,6 @@ export default {
|
||||
// Make calling deprecated APIs throw helpful error messages
|
||||
// errorOnDeprecated: false,
|
||||
|
||||
// The default configuration for fake timers
|
||||
// fakeTimers: {
|
||||
// "enableGlobally": false
|
||||
// },
|
||||
|
||||
// Force coverage collection from ignored files using an array of glob patterns
|
||||
// forceCoverageMatch: [],
|
||||
|
||||
@@ -78,8 +73,6 @@ export default {
|
||||
// An array of file extensions your modules use
|
||||
// moduleFileExtensions: [
|
||||
// "js",
|
||||
// "mjs",
|
||||
// "cjs",
|
||||
// "jsx",
|
||||
// "ts",
|
||||
// "tsx",
|
||||
@@ -89,11 +82,6 @@ export default {
|
||||
|
||||
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
|
||||
// moduleNameMapper: {},
|
||||
// moduleNameMapper: {
|
||||
// chalk: "<rootDir>/node_modules/chalk/source/index.js",
|
||||
// "#ansi-styles": "<rootDir>/node_modules/chalk/source/vendor/ansi-styles/index.js",
|
||||
// "#supports-color": "<rootDir>/node_modules/chalk/source/vendor/supports-color/index.js",
|
||||
// },
|
||||
|
||||
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||
modulePathIgnorePatterns: ["<rootDir>/dist"],
|
||||
@@ -113,7 +101,7 @@ export default {
|
||||
// Use this configuration option to add custom reporters to Jest
|
||||
// reporters: undefined,
|
||||
|
||||
// Automatically reset mock state before every test
|
||||
// Automatically reset mock state between every test
|
||||
// resetMocks: false,
|
||||
|
||||
// Reset the module registry before running each individual test
|
||||
@@ -122,7 +110,7 @@ export default {
|
||||
// A path to a custom resolver
|
||||
// resolver: undefined,
|
||||
|
||||
// Automatically restore mock state and implementation before every test
|
||||
// Automatically restore mock state between every test
|
||||
// restoreMocks: false,
|
||||
|
||||
// The root directory that Jest should scan for tests and modules within
|
||||
@@ -149,7 +137,7 @@ export default {
|
||||
// snapshotSerializers: [],
|
||||
|
||||
// The test environment that will be used for testing
|
||||
// testEnvironment: "jest-environment-node",
|
||||
testEnvironment: "node",
|
||||
|
||||
// Options that will be passed to the testEnvironment
|
||||
// testEnvironmentOptions: {},
|
||||
@@ -177,8 +165,14 @@ export default {
|
||||
// This option allows use of a custom test runner
|
||||
// testRunner: "jest-circus/runner",
|
||||
|
||||
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||
// testURL: "http://localhost",
|
||||
|
||||
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||
// timers: "real",
|
||||
|
||||
// A map from regular expressions to paths to transformers
|
||||
// transform: undefined,
|
||||
// transform: {},
|
||||
|
||||
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||
// transformIgnorePatterns: [
|
||||
@@ -186,11 +180,6 @@ export default {
|
||||
// "\\.pnp\\.[^\\/]+$"
|
||||
// ],
|
||||
|
||||
// transform: {
|
||||
// "^.+\\.ts?$": "ts-jest",
|
||||
// },
|
||||
// transformIgnorePatterns: ["<rootDir>/node_modules/"],
|
||||
|
||||
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||
// unmockedModulePathPatterns: undefined,
|
||||
|
||||
@@ -202,4 +191,6 @@ export default {
|
||||
|
||||
// Whether to use watchman for file crawling
|
||||
// watchman: true,
|
||||
|
||||
// extensionsToTreatAsEsm: [".ts"],
|
||||
}
|
||||
|
||||
BIN
media/intro.gif
BIN
media/intro.gif
Binary file not shown.
|
Before Width: | Height: | Size: 385 KiB |
46
package.json
46
package.json
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "simple-scaffold",
|
||||
"version": "1.3.1",
|
||||
"description": "A simple command to generate any file structure, from single components to entire app boilerplates.",
|
||||
"homepage": "https://casraf.dev/simple-scaffold",
|
||||
"version": "1.1.0-alpha.5",
|
||||
"description": "Create files based on templates",
|
||||
"repository": "https://github.com/chenasraf/simple-scaffold.git",
|
||||
"author": "Chen Asraf <inbox@casraf.com>",
|
||||
"license": "MIT",
|
||||
@@ -24,44 +23,31 @@
|
||||
"build": "yarn clean && tsc && chmod -R +x ./dist && cp ./package.json ./README.md ./dist/",
|
||||
"dev": "tsc --watch",
|
||||
"start": "node dist/scaffold.js",
|
||||
"test": "jest",
|
||||
"test": "jest --verbose",
|
||||
"cmd": "node --trace-warnings dist/cmd.js",
|
||||
"build-test": "yarn build && yarn test",
|
||||
"build-cmd": "yarn build && yarn cmd",
|
||||
"build-docs": "typedoc",
|
||||
"watch-docs": "yarn typedoc --watch",
|
||||
"audit-fix": "npm_config_yes=true npx yarn-audit-fix --flow=convert",
|
||||
"changelog": "conventional-changelog -p conventionalcommits -i CHANGELOG.md -s -r 0"
|
||||
"build-cmd": "yarn build && yarn cmd"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"date-fns": "^2.29.3",
|
||||
"glob": "^9.2.1",
|
||||
"date-fns": "^2.28.0",
|
||||
"glob": "^7.1.3",
|
||||
"handlebars": "^4.7.7",
|
||||
"lodash": "^4.17.21",
|
||||
"massarg": "^1.0.7-pre.1",
|
||||
"semantic-release-conventional-commits": "^3.0.0",
|
||||
"massarg": "^1.0.5",
|
||||
"util.promisify": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@knodes/typedoc-plugin-pages": "^0.23.4",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/release-notes-generator": "^10.0.3",
|
||||
"@types/jest": "^29.5.1",
|
||||
"@types/glob": "^7.1.1",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/lodash": "^4.14.171",
|
||||
"@types/mock-fs": "^4.13.1",
|
||||
"@types/node": "^18.16.0",
|
||||
"conventional-changelog": "^3.1.25",
|
||||
"conventional-changelog-cli": "^2.2.2",
|
||||
"conventional-changelog-conventionalcommits": "^5.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"mock-fs": "^5.2.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"semantic-release": "^21.0.1",
|
||||
"ts-jest": "^29.1.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typedoc": "^0.24.6",
|
||||
"typescript": "^5.0.4"
|
||||
"@types/node": "^14.14.22",
|
||||
"jest": "^27.0.6",
|
||||
"mock-fs": "^5.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-jest": "^27.0.3",
|
||||
"ts-node": "^10.1.0",
|
||||
"typescript": "^4.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
See full documentation [here](https://chenasraf.github.io/simple-scaffold).
|
||||
|
||||
- [Command Line Interface (CLI) usage](https://chenasraf.github.io/simple-scaffold/pages/cli.html)
|
||||
- [Node.js usage](https://chenasraf.github.io/simple-scaffold/pages/node.html)
|
||||
- [Templates](https://chenasraf.github.io/simple-scaffold/pages/templates.html)
|
||||
- [Configuration Files](https://chenasraf.github.io/simple-scaffold/pages/configuration_files.html)
|
||||
- [Migrating v0.x to v1.x](https://chenasraf.github.io/simple-scaffold/pages/migration.html)
|
||||
74
pages/cli.md
74
pages/cli.md
@@ -1,74 +0,0 @@
|
||||
## Available flags
|
||||
|
||||
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
|
||||
Usage: simple-scaffold [options]
|
||||
|
||||
Create structured files based on templates.
|
||||
|
||||
Options:
|
||||
|
||||
--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.
|
||||
|
||||
--config|-c Filename to load config from instead of passing
|
||||
arguments to CLI or using a Node.js script. You may pass a
|
||||
JSON or JS file, with a relative or absolute path.
|
||||
|
||||
--output|-o Path to output to. If --create-sub-folder is enabled,
|
||||
the subfolder will be created inside this path.
|
||||
(default: current dir)
|
||||
|
||||
--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.
|
||||
|
||||
--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.
|
||||
|
||||
--append-data|-D Append additional custom data to the templates, which
|
||||
will overwrite --data, using an alternate syntax, which is
|
||||
easier to use with CLI: -D key1=string -D key2:=raw
|
||||
|
||||
--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)
|
||||
|
||||
--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)
|
||||
```
|
||||
|
||||
> See
|
||||
> [Configuration Files](https://chenasraf.githun.io/simple-scaffold/pages/docs/configuration_files.md)
|
||||
> for organizing multiple scaffold types into easy-to-maintain files
|
||||
|
||||
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}'"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,86 +0,0 @@
|
||||
If you want to have reusable configurations which are complex and don't fit into command lines
|
||||
easily, or just want to manage your templates easier, you can use configuration files to load your
|
||||
scaffolding configurations.
|
||||
|
||||
## Creating config files
|
||||
|
||||
Configuration files should be valid `.js`/`.json` files that contain valid Scaffold configurations.
|
||||
|
||||
Each file hold multiple scaffolds. Each scaffold is a key, and its value is the configuration. For
|
||||
example:
|
||||
|
||||
```json
|
||||
{
|
||||
"component": {
|
||||
"templates": ["templates/component"],
|
||||
"output": "src/components"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The configuration contents are identical to the
|
||||
[Node.js configuration structure](https://chenasraf.github.io/simple-scaffold/pages/node.md):
|
||||
|
||||
```ts
|
||||
interface ScaffoldConfig {
|
||||
name: string
|
||||
templates: string[]
|
||||
output: FileResponse<string>
|
||||
createSubFolder?: boolean
|
||||
data?: Record<string, any>
|
||||
overwrite?: FileResponse<boolean>
|
||||
quiet?: boolean
|
||||
verbose?: LogLevel
|
||||
dryRun?: boolean
|
||||
helpers?: Record<string, Helper>
|
||||
subFolderNameHelper?: DefaultHelpers | string
|
||||
beforeWrite?(
|
||||
content: Buffer,
|
||||
rawContent: Buffer,
|
||||
outputPath: string,
|
||||
): string | Buffer | undefined | Promise<string | Buffer | undefined>
|
||||
}
|
||||
```
|
||||
|
||||
If you want to supply functions inside the configurations, you must use a `.js` file as JSON does
|
||||
not support non-primitives.
|
||||
|
||||
A `.js` file is just like a `.json` file, make sure to export the final configuration:
|
||||
|
||||
```js
|
||||
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
|
||||
module.exports = {
|
||||
component: {
|
||||
templates: ["templates/component"],
|
||||
output: "src/components",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Using a config file
|
||||
|
||||
Once your config is created, you can use it by providing the file name to the `--config` (or `-c`
|
||||
for brevity), followed by a colon, then your scaffold config name. For example:
|
||||
|
||||
```shell
|
||||
simple-scaffold -c scaffold.json:component MyComponentName
|
||||
```
|
||||
|
||||
If you don't want to supply a template/config name (e.g. `component`), you can omit the colon and
|
||||
the name, and it will use the configuration named `default`:
|
||||
|
||||
```js
|
||||
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
|
||||
module.exports = {
|
||||
default: {
|
||||
// ...
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
And then:
|
||||
|
||||
```shell
|
||||
# will use 'default' template
|
||||
simple-scaffold -c scaffold.json MyComponentName
|
||||
```
|
||||
@@ -1,53 +0,0 @@
|
||||
You can build the scaffold yourself, if you want to create more complex arguments, scaffold groups,
|
||||
etc - simply pass a config object to the Scaffold function when you are ready to start.
|
||||
|
||||
The config takes similar arguments to the command line. The full type definitions can be found in
|
||||
[src/types.ts](https://github.com/chenasraf/simple-scaffold/blob/develop/src/types.ts#L13).
|
||||
|
||||
See the full
|
||||
[documentation](https://chenasraf.github.io/simple-scaffold/interfaces/ScaffoldConfig.html) for the
|
||||
configuration options and their behavior.
|
||||
|
||||
```ts
|
||||
interface ScaffoldConfig {
|
||||
name: string
|
||||
templates: string[]
|
||||
output: FileResponse<string>
|
||||
createSubFolder?: boolean
|
||||
data?: Record<string, any>
|
||||
overwrite?: FileResponse<boolean>
|
||||
quiet?: boolean
|
||||
verbose?: LogLevel
|
||||
dryRun?: boolean
|
||||
helpers?: Record<string, Helper>
|
||||
subFolderNameHelper?: DefaultHelpers | string
|
||||
beforeWrite?(
|
||||
content: Buffer,
|
||||
rawContent: Buffer,
|
||||
outputPath: string,
|
||||
): string | Buffer | undefined | Promise<string | Buffer | undefined>
|
||||
}
|
||||
```
|
||||
|
||||
This is an example of loading a complete scaffold via Node.js:
|
||||
|
||||
```typescript
|
||||
import Scaffold from "simple-scaffold"
|
||||
|
||||
const config = {
|
||||
name: "component",
|
||||
templates: [path.join(__dirname, "scaffolds", "component")],
|
||||
output: path.join(__dirname, "src", "components"),
|
||||
createSubFolder: true,
|
||||
subFolderNameHelper: "upperCase"
|
||||
data: {
|
||||
property: "value",
|
||||
},
|
||||
helpers: {
|
||||
twice: (text) => [text, text].join(" ")
|
||||
},
|
||||
beforeWrite: (content, rawContent, outputPath) => content.toString().toUpperCase()
|
||||
}
|
||||
|
||||
const scaffold = Scaffold(config)
|
||||
```
|
||||
@@ -1,224 +0,0 @@
|
||||
# Preparing template files
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
> Simple-Scaffold uses [Handlebars.js](https://handlebarsjs.com/) for outputting the file contents.
|
||||
> 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.
|
||||
|
||||
## Helpers
|
||||
|
||||
### Built-in Helpers
|
||||
|
||||
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
|
||||
|
||||
| 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 |
|
||||
|
||||
#### 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
|
||||
|
||||
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 the
|
||||
transformation functions. For example, `upperCase` is implemented like so:
|
||||
|
||||
```typescript
|
||||
config.helpers = {
|
||||
upperCase: (text) => text.toUpperCase(),
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
```bash
|
||||
simple-scaffold MyComponent \
|
||||
-t project/scaffold/**/* \
|
||||
-o src/components \
|
||||
-d '{"className": "myClassName","author": "Chen Asraf"}'
|
||||
MyComponent
|
||||
```
|
||||
|
||||
### Equivalent Node Module Example
|
||||
|
||||
```typescript
|
||||
import Scaffold from "simple-scaffold"
|
||||
|
||||
async function main() {
|
||||
await Scaffold({
|
||||
name: "MyComponent",
|
||||
templates: ["project/scaffold/**/*"],
|
||||
output: ["src/components"],
|
||||
data: {
|
||||
className: "myClassName",
|
||||
author: "Chen Asraf",
|
||||
},
|
||||
})
|
||||
console.log("Done.")
|
||||
}
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
### Input
|
||||
|
||||
- Input file path:
|
||||
|
||||
```text
|
||||
project → scaffold → {{Name}}.js → src → components
|
||||
```
|
||||
|
||||
- Input file contents:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Author: {{ author }}
|
||||
* Date: {{ now "yyyy-MM-dd" }}
|
||||
*/
|
||||
import React from 'react'
|
||||
|
||||
export default {{camelCase name}}: React.FC = (props) => {
|
||||
return (
|
||||
<div className="{{className}}">{{camelCase name}} Component</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
- Output file path:
|
||||
|
||||
- With `createSubFolder = false` (default):
|
||||
|
||||
```text
|
||||
project → src → components → MyComponent.js
|
||||
```
|
||||
|
||||
- With `createSubFolder = true`:
|
||||
|
||||
```text
|
||||
project → src → components → MyComponent → MyComponent.js
|
||||
```
|
||||
|
||||
- With `createSubFolder = true` and `subFolderNameHelper = 'upperCase'`:
|
||||
|
||||
```text
|
||||
project → src → components → MYCOMPONENT → MyComponent.js
|
||||
```
|
||||
|
||||
- Output file contents:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* Author: Chen Asraf
|
||||
* Date: 2077-01-01
|
||||
*/
|
||||
import React from 'react'
|
||||
|
||||
export default MyComponent: React.FC = (props) => {
|
||||
return (
|
||||
<div className="myClassName">MyComponent Component</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
@@ -1,76 +0,0 @@
|
||||
const releaseRules = [
|
||||
{ type: "feat", section: "Features", release: "minor" },
|
||||
{ type: "docs", section: "Misc", release: "patch" },
|
||||
{ type: "fix", section: "Bug Fixes", release: "patch" },
|
||||
{ type: "refactor", section: "Misc", release: "patch" },
|
||||
{ type: "docs", section: "Build", release: "patch" },
|
||||
{ type: "perf", section: "Misc", release: "patch" },
|
||||
{ type: "build", section: "Build", release: "patch" },
|
||||
{ type: "chore", section: "Misc", release: "patch" },
|
||||
{ type: "test", section: "Misc", release: "patch" },
|
||||
]
|
||||
|
||||
/** @type {import('semantic-release').Options} */
|
||||
module.exports = {
|
||||
branches: [
|
||||
"+([0-9])?(.{+([0-9]),x}).x",
|
||||
"master",
|
||||
"next",
|
||||
"next-major",
|
||||
{ name: "beta", prerelease: true },
|
||||
{ name: "alpha", prerelease: true },
|
||||
],
|
||||
analyzeCommits: {
|
||||
path: "semantic-release-conventional-commits",
|
||||
majorTypes: ["major", "breaking"],
|
||||
minorTypes: ["minor", "feat", "feature"],
|
||||
patchTypes: ["patch", "fix", "bugfix", "refactor", "perf", "revert"],
|
||||
},
|
||||
plugins: [
|
||||
[
|
||||
"@semantic-release/commit-analyzer",
|
||||
{
|
||||
preset: "conventionalcommits",
|
||||
parserOpts: {
|
||||
noteKeywords: ["breaking:", "breaking-fix:", "breaking-feat:"],
|
||||
},
|
||||
releaseRules: releaseRules,
|
||||
},
|
||||
],
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
preset: "conventionalcommits",
|
||||
parserOpts: {
|
||||
noteKeywords: ["breaking"],
|
||||
types: releaseRules,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"@semantic-release/changelog",
|
||||
{
|
||||
changelogFile: "CHANGELOG.md",
|
||||
changelogTitle: "# Change Log",
|
||||
},
|
||||
],
|
||||
[
|
||||
"@semantic-release/npm",
|
||||
{
|
||||
packageRoot: "dist",
|
||||
},
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
assets: ["package.tgz"],
|
||||
},
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
assets: ["CHANGELOG.md", "package.json"],
|
||||
},
|
||||
],
|
||||
],
|
||||
}
|
||||
44
src/cmd.ts
44
src/cmd.ts
@@ -3,20 +3,11 @@ 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"
|
||||
import { parseAppendData, parseConfig } from "./utils"
|
||||
|
||||
export async function parseCliArgs(args = process.argv.slice(2)) {
|
||||
const pkg = JSON.parse((await fs.readFile(path.join(__dirname, "package.json"))).toString())
|
||||
const isConfig = args.includes("--config") || args.includes("-c")
|
||||
|
||||
export function parseCliArgs(args = process.argv.slice(2)) {
|
||||
return (
|
||||
massarg<ScaffoldCmdConfig>()
|
||||
.main((config) => {
|
||||
const _config = parseConfig(config)
|
||||
return Scaffold(_config)
|
||||
})
|
||||
massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
|
||||
.main(Scaffold)
|
||||
.option({
|
||||
name: "name",
|
||||
aliases: ["n"],
|
||||
@@ -25,17 +16,11 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
|
||||
isDefault: true,
|
||||
required: true,
|
||||
})
|
||||
.option({
|
||||
name: "config",
|
||||
aliases: ["c"],
|
||||
description:
|
||||
"Filename to load config from instead of passing arguments to CLI or using a Node.js script. You may pass a JSON or JS file, with a relative or absolute path.",
|
||||
})
|
||||
.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: !isConfig,
|
||||
required: true,
|
||||
})
|
||||
.option({
|
||||
name: "templates",
|
||||
@@ -44,7 +29,7 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
|
||||
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: !isConfig,
|
||||
required: true,
|
||||
})
|
||||
.option({
|
||||
name: "overwrite",
|
||||
@@ -59,13 +44,6 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
|
||||
description: "Add custom data to the templates. By default, only your app name is included.",
|
||||
parse: (v) => JSON.parse(v),
|
||||
})
|
||||
.option({
|
||||
name: "append-data",
|
||||
aliases: ["D"],
|
||||
description:
|
||||
"Append additional custom data to the templates, which will overwrite --data, using an alternate syntax, which is easier to use with CLI: -D key1=string -D key2:=raw",
|
||||
parse: parseAppendData,
|
||||
})
|
||||
.option({
|
||||
name: "create-sub-folder",
|
||||
aliases: ["s"],
|
||||
@@ -89,10 +67,7 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
|
||||
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.",
|
||||
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({
|
||||
@@ -113,12 +88,9 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
|
||||
binName: "simple-scaffold",
|
||||
useGlobalColumns: true,
|
||||
usageExample: "[options]",
|
||||
header: [`Create structured files based on templates.`].join("\n"),
|
||||
header: "Create structured files based on templates.",
|
||||
footer: [
|
||||
`Version: ${pkg.version}`,
|
||||
`Copyright © Chen Asraf 2017-${new Date().getFullYear()}`,
|
||||
``,
|
||||
`Documentation: ${chalk.underline`https://casraf.dev/simple-scaffold`}`,
|
||||
`Copyright © Chen Asraf 2021`,
|
||||
`NPM: ${chalk.underline`https://npmjs.com/package/simple-scaffold`}`,
|
||||
`GitHub: ${chalk.underline`https://github.com/chenasraf/simple-scaffold`}`,
|
||||
].join("\n"),
|
||||
|
||||
55
src/docs.css
55
src/docs.css
@@ -1,55 +0,0 @@
|
||||
.tsd-typography table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tsd-typography table td,
|
||||
.tsd-typography table th {
|
||||
vertical-align: top;
|
||||
border: 1px solid var(--color-accent);
|
||||
padding: 6px;
|
||||
}
|
||||
.tsd-typography h1 + pre,
|
||||
.tsd-typography h2 + pre,
|
||||
.tsd-typography h3 + pre,
|
||||
.tsd-typography h4 + pre,
|
||||
.tsd-typography h5 + pre,
|
||||
.tsd-typography h6 + pre,
|
||||
/* */
|
||||
.tsd-typography h1 + table,
|
||||
.tsd-typography h2 + table,
|
||||
.tsd-typography h3 + table,
|
||||
.tsd-typography h4 + table,
|
||||
.tsd-typography h5 + table,
|
||||
.tsd-typography h6 + table {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.tsd-typography pre + a + h1,
|
||||
.tsd-typography pre + a + h2,
|
||||
.tsd-typography pre + a + h3,
|
||||
.tsd-typography pre + a + h4,
|
||||
.tsd-typography pre + a + h5,
|
||||
.tsd-typography pre + a + h6,
|
||||
/* */
|
||||
.tsd-typography table + a + h1,
|
||||
.tsd-typography table + a + h2,
|
||||
.tsd-typography table + a + h3,
|
||||
.tsd-typography table + a + h4,
|
||||
.tsd-typography table + a + h5,
|
||||
.tsd-typography table + a + h6 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
.tsd-index-accordion[data-key*="Configuration."] ul.tsd-nested-navigation,
|
||||
.tsd-index-accordion[data-key*="Configuration."] .tsd-accordion-summary > svg,
|
||||
.tsd-index-accordion[data-key="Changelog"] ul.tsd-nested-navigation,
|
||||
.tsd-index-accordion[data-key="Changelog"] .tsd-accordion-summary > svg,
|
||||
.tsd-index-accordion[data-key="Configuration"] li:nth-child(n + 6) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tsd-index-accordion[data-key*="Configuration."],
|
||||
.tsd-index-accordion[data-key="Changelog"] {
|
||||
margin-left: 0;
|
||||
}
|
||||
@@ -1,9 +1,3 @@
|
||||
/**
|
||||
* @module
|
||||
* Simple Scaffold
|
||||
*
|
||||
* See [readme](README.md)
|
||||
*/
|
||||
import path from "path"
|
||||
|
||||
import {
|
||||
@@ -37,47 +31,46 @@ import { LogLevel, ScaffoldConfig } from "./types"
|
||||
* 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.
|
||||
* pre-define the data and use a duplicated key. Common cases are transforming name-case format
|
||||
* (e.g. `MyName` → `my_name`), so these have been provided as defaults:
|
||||
*
|
||||
* | 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 |
|
||||
*
|
||||
* 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 DefaultHelpers}.
|
||||
*
|
||||
* @param {ScaffoldConfig} config The main configuration object
|
||||
*
|
||||
* @see {@link DefaultHelpers}
|
||||
* @see {@link CaseHelpers}
|
||||
* @see {@link DateHelpers}
|
||||
*
|
||||
* @category Main
|
||||
*/
|
||||
export async function Scaffold(config: ScaffoldConfig): Promise<void> {
|
||||
config.output ??= process.cwd()
|
||||
export async function Scaffold({ ...options }: ScaffoldConfig): Promise<void> {
|
||||
options.output ??= process.cwd()
|
||||
|
||||
registerHelpers(config)
|
||||
registerHelpers(options)
|
||||
try {
|
||||
config.data = { name: config.name, Name: pascalCase(config.name), ...config.data }
|
||||
logInitStep(config)
|
||||
for (let _template of config.templates) {
|
||||
options.data = { name: options.name, Name: pascalCase(options.name), ...options.data }
|
||||
logInitStep(options)
|
||||
for (let _template of options.templates) {
|
||||
try {
|
||||
const { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template } = await getTemplateGlobInfo(
|
||||
config,
|
||||
_template,
|
||||
options,
|
||||
_template
|
||||
)
|
||||
const files = await getFileList(config, template)
|
||||
const files = await getFileList(options, template)
|
||||
for (const inputFilePath of files) {
|
||||
if (await isDir(inputFilePath)) {
|
||||
continue
|
||||
}
|
||||
const relPath = makeRelativePath(path.dirname(removeGlob(inputFilePath).replace(nonGlobTemplate, "")))
|
||||
const basePath = getBasePath(relPath)
|
||||
logInputFile(config, {
|
||||
logInputFile(options, {
|
||||
origTemplate,
|
||||
relPath,
|
||||
template,
|
||||
@@ -87,7 +80,7 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
|
||||
isDirOrGlob,
|
||||
isGlob,
|
||||
})
|
||||
await handleTemplateFile(config, {
|
||||
await handleTemplateFile(options, options.data, {
|
||||
templatePath: inputFilePath,
|
||||
basePath,
|
||||
})
|
||||
@@ -97,24 +90,25 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
|
||||
}
|
||||
}
|
||||
} 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 },
|
||||
options: ScaffoldConfig,
|
||||
data: Record<string, string>,
|
||||
{ templatePath, basePath }: { templatePath: string; basePath: string }
|
||||
): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const { inputPath, outputPathOpt, outputDir, outputPath, exists } = await getTemplateFileInfo(config, {
|
||||
const { inputPath, outputPathOpt, outputDir, outputPath, exists } = await getTemplateFileInfo(options, data, {
|
||||
templatePath,
|
||||
basePath,
|
||||
})
|
||||
const overwrite = getOptionValueForFile(config, inputPath, config.overwrite ?? false)
|
||||
const overwrite = getOptionValueForFile(options, inputPath, options.overwrite ?? false)
|
||||
|
||||
log(
|
||||
config,
|
||||
options,
|
||||
LogLevel.Debug,
|
||||
`\nParsing ${templatePath}`,
|
||||
`\nBase path: ${basePath}`,
|
||||
@@ -122,13 +116,13 @@ async function handleTemplateFile(
|
||||
`\nOutput Path Opt: ${outputPathOpt}`,
|
||||
`\nFull output dir: ${outputDir}`,
|
||||
`\nFull output path: ${outputPath}`,
|
||||
`\n`,
|
||||
`\n`
|
||||
)
|
||||
|
||||
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}`)
|
||||
await copyFileTransformed(options, { exists, overwrite, outputPath, inputPath })
|
||||
resolve()
|
||||
} catch (e: any) {
|
||||
handleErr(e)
|
||||
|
||||
301
src/types.ts
301
src/types.ts
@@ -1,15 +1,31 @@
|
||||
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>
|
||||
|
||||
export type DefaultHelperKeys =
|
||||
| "camelCase"
|
||||
| "date"
|
||||
| "hyphenCase"
|
||||
| "kebabCase"
|
||||
| "lowerCase"
|
||||
| "now"
|
||||
| "pascalCase"
|
||||
| "snakeCase"
|
||||
| "startCase"
|
||||
| "upperCase"
|
||||
|
||||
export type HelperKeys<T> = DefaultHelperKeys | T
|
||||
|
||||
export type Helper = Handlebars.HelperDelegate
|
||||
|
||||
/**
|
||||
* The config object for defining a scaffolding group.
|
||||
*
|
||||
* @see https://github.com/chenasraf/simple-scaffold#readme
|
||||
* @see {@link DefaultHelpers}
|
||||
* @see {@link CaseHelpers}
|
||||
* @see {@link DateHelpers}
|
||||
*
|
||||
* @category Config
|
||||
*/
|
||||
export interface ScaffoldConfig {
|
||||
/**
|
||||
* Name to be passed to the generated files. `{{name}}` and `{{Name}}` inside contents and file names will be replaced
|
||||
@@ -19,77 +35,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
|
||||
|
||||
@@ -97,7 +82,7 @@ export interface ScaffoldConfig {
|
||||
* Additional helpers to add to the template parser. Provide an object whose keys are the name of the function to add,
|
||||
* and the value is the helper function itself. The signature of helpers is as follows:
|
||||
* ```typescript
|
||||
* (text: string, ...args: any[]) => string
|
||||
* (text: string) => string
|
||||
* ```
|
||||
*
|
||||
* A full example might be:
|
||||
@@ -106,47 +91,30 @@ export interface ScaffoldConfig {
|
||||
* Scaffold({
|
||||
* //...
|
||||
* helpers: {
|
||||
* upperKebabCase: (text) => kebabCase(text).toUpperCase()
|
||||
* upperCamelCase: (text) => camelCase(text).toUpperCase()
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* Which will allow:
|
||||
*
|
||||
* ```
|
||||
* {{ upperKebabCase "my value" }}
|
||||
* ```
|
||||
*
|
||||
* To transform to:
|
||||
*
|
||||
* ```
|
||||
* MY-VALUE
|
||||
* ```
|
||||
*
|
||||
* See {@link DefaultHelpers} for a list of all the built-in available helpers.
|
||||
*
|
||||
* Simple Scaffold uses Handlebars.js, so all the syntax from there is supported. See
|
||||
* [their docs](https://handlebarsjs.com/guide/#custom-helpers) for more information.
|
||||
*
|
||||
* @see {@link DefaultHelpers}
|
||||
* @see {@link CaseHelpers}
|
||||
* @see {@link DateHelpers}
|
||||
* @see https://casraf.dev/simple-scaffold#helpers
|
||||
* @see https://casraf.dev/simple-scaffold#built-in-helpers
|
||||
* @see https://handlebarsjs.com/guide/#custom-helpers
|
||||
* Here are the built-in helpers available for use:
|
||||
* | 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 |
|
||||
*/
|
||||
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 CaseHelpers}
|
||||
* @see {@link DefaultHelpers}
|
||||
* helpers, or a custom one you provide to `helpers`. Defaults to `undefined`, which means no transformation is done.
|
||||
*/
|
||||
subFolderNameHelper?: DefaultHelpers | string
|
||||
subFolderNameHelper?: DefaultHelperKeys | string
|
||||
|
||||
/**
|
||||
* This callback runs right before content is being written to the disk. If you supply this function, you may return
|
||||
@@ -159,184 +127,23 @@ export interface ScaffoldConfig {
|
||||
* @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()`)
|
||||
* @returns `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,
|
||||
outputPath: string
|
||||
): string | Buffer | undefined | Promise<string | Buffer | undefined>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 DefaultHelpers}
|
||||
* @see {@link DateHelpers}
|
||||
* @see {@link ScaffoldConfig}
|
||||
* @see {@link ScaffoldConfig.subFolderNameHelper}
|
||||
*
|
||||
* @category Helpers
|
||||
*/
|
||||
export type CaseHelpers =
|
||||
| "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` |
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @see {@link DefaultHelpers}
|
||||
* @see {@link CaseHelpers}
|
||||
* @see {@link ScaffoldConfig}
|
||||
*
|
||||
* @category Helpers
|
||||
*/
|
||||
export type DateHelpers = "date" | "now"
|
||||
|
||||
/**
|
||||
* The names of all the available helper functions in templates.
|
||||
* Simple-Scaffold provides some built-in text transformation filters usable by Handlebars.js.
|
||||
*
|
||||
* 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 CaseHelpers}
|
||||
* @see {@link DateHelpers}
|
||||
* @see {@link ScaffoldConfig}
|
||||
*
|
||||
* @category Helpers
|
||||
*/
|
||||
export type DefaultHelpers = CaseHelpers | DateHelpers
|
||||
|
||||
/**
|
||||
* 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[]
|
||||
output: string
|
||||
createSubFolder: boolean
|
||||
data?: Record<string, string>
|
||||
appendData?: Record<string, string>
|
||||
overwrite: boolean
|
||||
quiet: boolean
|
||||
verbose: LogLevel
|
||||
dryRun: boolean
|
||||
config?: string
|
||||
}
|
||||
|
||||
export type ScaffoldConfigFile = Record<string, ScaffoldConfig>
|
||||
|
||||
203
src/utils.ts
203
src/utils.ts
@@ -1,15 +1,6 @@
|
||||
import path from "path"
|
||||
import { F_OK } from "constants"
|
||||
import {
|
||||
DefaultHelpers,
|
||||
FileResponse,
|
||||
FileResponseHandler,
|
||||
Helper,
|
||||
LogLevel,
|
||||
ScaffoldCmdConfig,
|
||||
ScaffoldConfig,
|
||||
ScaffoldConfigFile,
|
||||
} from "./types"
|
||||
import { DefaultHelperKeys, FileResponse, FileResponseFn, Helper, LogLevel, ScaffoldConfig } from "./types"
|
||||
import camelCase from "lodash/camelCase"
|
||||
import snakeCase from "lodash/snakeCase"
|
||||
import kebabCase from "lodash/kebabCase"
|
||||
@@ -21,8 +12,6 @@ const { stat, access, mkdir } = fsPromises
|
||||
import dtAdd from "date-fns/add"
|
||||
import dtFormat from "date-fns/format"
|
||||
import dtParseISO from "date-fns/parseISO"
|
||||
import { glob, hasMagic } from "glob"
|
||||
import { OptionsBase } from "massarg/types"
|
||||
|
||||
const dateFns = {
|
||||
add: dtAdd,
|
||||
@@ -30,9 +19,11 @@ const dateFns = {
|
||||
parseISO: dtParseISO,
|
||||
}
|
||||
|
||||
import { glob } from "glob"
|
||||
import { promisify } from "util"
|
||||
const { readFile, writeFile } = fsPromises
|
||||
|
||||
export const defaultHelpers: Record<DefaultHelpers, Helper> = {
|
||||
export const defaultHelpers: Record<DefaultHelperKeys, Helper> = {
|
||||
camelCase,
|
||||
snakeCase,
|
||||
startCase,
|
||||
@@ -50,13 +41,13 @@ export function _dateHelper(
|
||||
date: Date,
|
||||
formatString: string,
|
||||
durationDifference: number,
|
||||
durationType: keyof Duration,
|
||||
durationType: keyof Duration
|
||||
): string
|
||||
export function _dateHelper(
|
||||
date: Date,
|
||||
formatString: string,
|
||||
durationDifference?: number,
|
||||
durationType?: keyof Duration,
|
||||
durationType?: keyof Duration
|
||||
): string {
|
||||
if (durationType && durationDifference !== undefined) {
|
||||
return dateFns.format(dateFns.add(date, { [durationType]: durationDifference }), formatString)
|
||||
@@ -75,22 +66,22 @@ export function dateHelper(
|
||||
date: string,
|
||||
formatString: string,
|
||||
durationDifference: number,
|
||||
durationType: keyof Duration,
|
||||
durationType: keyof Duration
|
||||
): string
|
||||
|
||||
export function dateHelper(
|
||||
date: string,
|
||||
formatString: string,
|
||||
durationDifference?: number,
|
||||
durationType?: keyof Duration,
|
||||
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): void {
|
||||
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])
|
||||
}
|
||||
}
|
||||
@@ -99,8 +90,8 @@ export function handleErr(err: NodeJS.ErrnoException | null): void {
|
||||
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[]): void {
|
||||
if (options.quiet || options.verbose === LogLevel.None || level < (options.verbose ?? LogLevel.Info)) {
|
||||
return
|
||||
}
|
||||
const levelColor: Record<LogLevel, keyof typeof chalk> = {
|
||||
@@ -119,21 +110,21 @@ export function log(config: ScaffoldConfig, level: LogLevel, ...obj: any[]): voi
|
||||
? chalkFn(i, JSON.stringify(i, undefined, 1), i.stack)
|
||||
: typeof i === "object"
|
||||
? chalkFn(JSON.stringify(i, undefined, 1))
|
||||
: chalkFn(i),
|
||||
),
|
||||
: chalkFn(i)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -146,27 +137,27 @@ export async function createDirIfNotExists(dir: string, config: ScaffoldConfig):
|
||||
}
|
||||
|
||||
export function getOptionValueForFile<T>(
|
||||
config: ScaffoldConfig,
|
||||
options: ScaffoldConfig,
|
||||
filePath: string,
|
||||
fn: FileResponse<T>,
|
||||
defaultValue?: 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(options, filePath, { isPath: true }).toString()),
|
||||
path.basename(handlebarsParse(options, filePath, { isPath: true }).toString())
|
||||
)
|
||||
}
|
||||
|
||||
export function handlebarsParse(
|
||||
config: ScaffoldConfig,
|
||||
options: ScaffoldConfig,
|
||||
templateBuffer: Buffer | string,
|
||||
{ isPath = false }: { isPath?: boolean } = {},
|
||||
{ isPath = false }: { isPath?: boolean } = {}
|
||||
): Buffer {
|
||||
const { data } = config
|
||||
const { data } = options
|
||||
try {
|
||||
let str = templateBuffer.toString()
|
||||
if (isPath) {
|
||||
@@ -179,8 +170,8 @@ export function handlebarsParse(
|
||||
}
|
||||
return Buffer.from(outputContents)
|
||||
} catch (e) {
|
||||
log(config, LogLevel.Debug, e)
|
||||
log(config, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content")
|
||||
log(options, LogLevel.Debug, e)
|
||||
log(options, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content")
|
||||
return Buffer.from(templateBuffer)
|
||||
}
|
||||
}
|
||||
@@ -221,12 +212,12 @@ export function getBasePath(relPath: string): string {
|
||||
.replace(process.cwd(), "")
|
||||
}
|
||||
|
||||
export async function getFileList(config: ScaffoldConfig, template: string): Promise<string[]> {
|
||||
export async function getFileList(options: ScaffoldConfig, template: string): Promise<string[]> {
|
||||
return (
|
||||
await glob(template, {
|
||||
await promisify(glob)(template, {
|
||||
dot: true,
|
||||
debug: options.verbose === LogLevel.Debug,
|
||||
nodir: true,
|
||||
// debug: config.verbose === LogLevel.Debug,
|
||||
})
|
||||
).map((f) => f.replace(/\//g, path.sep))
|
||||
}
|
||||
@@ -239,14 +230,13 @@ export interface GlobInfo {
|
||||
template: string
|
||||
}
|
||||
|
||||
export async function getTemplateGlobInfo(config: ScaffoldConfig, template: string): Promise<GlobInfo> {
|
||||
const isGlob = hasMagic(template)
|
||||
log(config, LogLevel.Debug, "before isDir", "isGlob:", isGlob, template)
|
||||
export async function getTemplateGlobInfo(options: ScaffoldConfig, template: string): Promise<GlobInfo> {
|
||||
const isGlob = glob.hasMagic(template)
|
||||
log(options, LogLevel.Debug, "before isDir", "isGlob:", isGlob, template)
|
||||
let _template = template
|
||||
let nonGlobTemplate = isGlob ? removeGlob(template) : template
|
||||
nonGlobTemplate = path.normalize(nonGlobTemplate)
|
||||
const nonGlobTemplate = isGlob ? removeGlob(template) : template
|
||||
const isDirOrGlob = isGlob ? true : await isDir(template)
|
||||
log(config, LogLevel.Debug, "after isDir", isDirOrGlob)
|
||||
log(options, LogLevel.Debug, "after isDir", isDirOrGlob)
|
||||
const _shouldAddGlob = !isGlob && isDirOrGlob
|
||||
const origTemplate = template
|
||||
if (_shouldAddGlob) {
|
||||
@@ -264,13 +254,14 @@ export interface OutputFileInfo {
|
||||
}
|
||||
|
||||
export async function getTemplateFileInfo(
|
||||
config: ScaffoldConfig,
|
||||
{ templatePath, basePath }: { templatePath: string; basePath: string },
|
||||
options: ScaffoldConfig,
|
||||
data: Record<string, string>,
|
||||
{ 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)), {
|
||||
const outputPathOpt = getOptionValueForFile(options, inputPath, options.output)
|
||||
const outputDir = getOutputDir(options, outputPathOpt, basePath)
|
||||
const outputPath = handlebarsParse(options, path.join(outputDir, path.basename(inputPath)), {
|
||||
isPath: true,
|
||||
}).toString()
|
||||
const exists = await pathExists(outputPath)
|
||||
@@ -278,7 +269,7 @@ export async function getTemplateFileInfo(
|
||||
}
|
||||
|
||||
export async function copyFileTransformed(
|
||||
config: ScaffoldConfig,
|
||||
options: ScaffoldConfig,
|
||||
{
|
||||
exists,
|
||||
overwrite,
|
||||
@@ -289,46 +280,47 @@ export async function copyFileTransformed(
|
||||
overwrite: boolean
|
||||
outputPath: string
|
||||
inputPath: string
|
||||
},
|
||||
}
|
||||
): Promise<void> {
|
||||
if (!exists || overwrite) {
|
||||
if (exists && overwrite) {
|
||||
log(config, LogLevel.Info, `File ${outputPath} exists, overwriting`)
|
||||
log(options, 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
|
||||
const unprocessedOutputContents = handlebarsParse(options, templateBuffer)
|
||||
const finalOutputContents = (
|
||||
(await options.beforeWrite?.(unprocessedOutputContents, templateBuffer, outputPath)) ?? unprocessedOutputContents
|
||||
).toString()
|
||||
|
||||
if (!config.dryRun) {
|
||||
if (!options.dryRun) {
|
||||
await writeFile(outputPath, finalOutputContents)
|
||||
log(config, LogLevel.Info, "Done.")
|
||||
log(options, LogLevel.Info, "Done.")
|
||||
} else {
|
||||
log(config, LogLevel.Info, "Content output:")
|
||||
log(config, LogLevel.Info, finalOutputContents)
|
||||
log(options, LogLevel.Info, "Content output:")
|
||||
log(options, LogLevel.Info, finalOutputContents)
|
||||
}
|
||||
} else if (exists) {
|
||||
log(config, LogLevel.Info, `File ${outputPath} already exists, skipping`)
|
||||
log(options, LogLevel.Info, `File ${outputPath} already exists, skipping`)
|
||||
}
|
||||
}
|
||||
|
||||
export function getOutputDir(config: ScaffoldConfig, outputPathOpt: string, basePath: string): string {
|
||||
export function getOutputDir(options: 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
|
||||
options.createSubFolder
|
||||
? options.subFolderNameHelper
|
||||
? handlebarsParse(options, `{{ ${options.subFolderNameHelper} name }}`).toString()
|
||||
: options.name
|
||||
: undefined,
|
||||
].filter(Boolean) as string[]),
|
||||
].filter(Boolean) as string[])
|
||||
)
|
||||
}
|
||||
|
||||
export function logInputFile(
|
||||
config: ScaffoldConfig,
|
||||
options: ScaffoldConfig,
|
||||
{
|
||||
origTemplate,
|
||||
relPath,
|
||||
@@ -347,10 +339,10 @@ export function logInputFile(
|
||||
basePath: string
|
||||
isDirOrGlob: boolean
|
||||
isGlob: boolean
|
||||
},
|
||||
}
|
||||
): void {
|
||||
log(
|
||||
config,
|
||||
options,
|
||||
LogLevel.Debug,
|
||||
`\nprocess.cwd(): ${process.cwd()}`,
|
||||
`\norigTemplate: ${origTemplate}`,
|
||||
@@ -361,63 +353,24 @@ export function logInputFile(
|
||||
`\nbasePath: ${basePath}`,
|
||||
`\nisDirOrGlob: ${isDirOrGlob}`,
|
||||
`\nisGlob: ${isGlob}`,
|
||||
`\n`,
|
||||
`\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!,
|
||||
export function logInitStep(options: ScaffoldConfig): void {
|
||||
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,
|
||||
subFolderTransformHelper: options.subFolderNameHelper,
|
||||
helpers: Object.keys(options.helpers ?? {}),
|
||||
verbose: `${options.verbose} (${Object.keys(LogLevel).find(
|
||||
(k) => (LogLevel[k as any] as unknown as number) === options.verbose!
|
||||
)})`,
|
||||
})
|
||||
log(config, LogLevel.Info, "Data:", config.data)
|
||||
}
|
||||
|
||||
export function parseAppendData(value: string, options: ScaffoldCmdConfig & OptionsBase): unknown {
|
||||
const data = options.data ?? {}
|
||||
const [key, val] = value.split(/\:?=/)
|
||||
// raw
|
||||
if (value.includes(":=") && !val.includes(":=")) {
|
||||
return { ...data, [key]: JSON.parse(val) }
|
||||
}
|
||||
return { ...data, [key]: isWrappedWithQuotes(val) ? val.substring(1, val.length - 1) : val }
|
||||
}
|
||||
|
||||
function isWrappedWithQuotes(string: string): boolean {
|
||||
return (string.startsWith('"') && string.endsWith('"')) || (string.startsWith("'") && string.endsWith("'"))
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function parseConfig(config: ScaffoldCmdConfig & OptionsBase): ScaffoldConfig {
|
||||
let c: ScaffoldConfig = config
|
||||
|
||||
if (config.config) {
|
||||
const [configFile, template = "default"] = config.config.split(":")
|
||||
const configImport: ScaffoldConfigFile = require(path.resolve(process.cwd(), configFile))
|
||||
if (!configImport[template]) {
|
||||
throw new Error(`Template "${template}" not found in ${configFile}`)
|
||||
}
|
||||
c = {
|
||||
...config,
|
||||
...configImport[template],
|
||||
data: {
|
||||
...configImport[template].data,
|
||||
...config.data,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
c.data = { ...c.data, ...config.appendData }
|
||||
delete config.appendData
|
||||
return c
|
||||
log(options, LogLevel.Info, "Data:", options.data)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ 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 +13,6 @@ const fileStructNormal = {
|
||||
},
|
||||
output: {},
|
||||
}
|
||||
const fileStructWithBinary = {
|
||||
input: {
|
||||
"{{name}}.txt": "Hello, my app is {{name}}",
|
||||
"{{name}}.bin": crypto.randomBytes(10000),
|
||||
},
|
||||
output: {},
|
||||
}
|
||||
|
||||
const fileStructWithData = {
|
||||
input: {
|
||||
@@ -53,7 +45,7 @@ const fileStructHelpers = {
|
||||
input: {
|
||||
defaults: defaultHelperNames.reduce<Record<string, string>>(
|
||||
(all, cur) => ({ ...all, [cur + ".txt"]: `{{ ${cur} name }}` }),
|
||||
{},
|
||||
{}
|
||||
),
|
||||
custom: {
|
||||
"add1.txt": "{{ add1 name }}",
|
||||
@@ -117,25 +109,7 @@ describe("Scaffold", () => {
|
||||
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"])
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
describe(
|
||||
@@ -183,7 +157,7 @@ describe("Scaffold", () => {
|
||||
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
|
||||
expect(data.toString()).toEqual("Hello, my value is 2")
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
describe(
|
||||
@@ -206,7 +180,7 @@ describe("Scaffold", () => {
|
||||
templates: ["non-existing-input"],
|
||||
data: { value: "1" },
|
||||
verbose: 0,
|
||||
}),
|
||||
})
|
||||
).rejects.toThrow()
|
||||
|
||||
await expect(
|
||||
@@ -216,12 +190,12 @@ describe("Scaffold", () => {
|
||||
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(
|
||||
@@ -248,7 +222,7 @@ describe("Scaffold", () => {
|
||||
|
||||
expect(() => readFileSync(join(process.cwd(), "output", "app_name.txt"))).toThrow()
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
describe(
|
||||
@@ -265,7 +239,7 @@ describe("Scaffold", () => {
|
||||
const data = readFileSync(join(process.cwd(), "/custom-output/app_name/app_name.txt"))
|
||||
expect(data.toString()).toEqual("Hello, my app is app_name")
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
describe(
|
||||
@@ -294,7 +268,7 @@ describe("Scaffold", () => {
|
||||
expect(oneDeepFile.toString()).toEqual("Hello, my value is 1")
|
||||
expect(twoDeepFile.toString()).toEqual("Hi! My value is actually NOT 1!")
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
describe(
|
||||
@@ -328,7 +302,7 @@ describe("Scaffold", () => {
|
||||
expect(file.toString()).toEqual(results[key as keyof typeof results])
|
||||
}
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
describe(
|
||||
"date helpers",
|
||||
@@ -355,19 +329,19 @@ describe("Scaffold", () => {
|
||||
// "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")}`,
|
||||
`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")}`,
|
||||
`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",
|
||||
)}`,
|
||||
"HH:mm"
|
||||
)}`
|
||||
)
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
describe(
|
||||
"custom helpers",
|
||||
@@ -392,7 +366,7 @@ describe("Scaffold", () => {
|
||||
expect(file.toString()).toEqual(results[key as keyof typeof results])
|
||||
}
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
describe(
|
||||
"transform subfolder",
|
||||
@@ -440,7 +414,7 @@ describe("Scaffold", () => {
|
||||
const data = readFileSync(join(process.cwd(), "output", "REPLACED", "app_name.txt"))
|
||||
expect(data.toString()).toEqual("Hello, my app is app_name")
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
describe(
|
||||
"before write",
|
||||
@@ -479,7 +453,7 @@ describe("Scaffold", () => {
|
||||
"Hello, my app is app_name".toUpperCase(),
|
||||
fileStructNormal.input["{{name}}.txt"],
|
||||
join(process.cwd(), "output", "app_name.txt"),
|
||||
].join(", "),
|
||||
].join(", ")
|
||||
)
|
||||
})
|
||||
test("should work with undefined response custom callback", async () => {
|
||||
@@ -497,6 +471,6 @@ describe("Scaffold", () => {
|
||||
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
|
||||
expect(data.toString()).toEqual("Hello, my app is app_name")
|
||||
})
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { dateHelper, handlebarsParse, nowHelper, parseAppendData } from "../src/utils"
|
||||
import { ScaffoldCmdConfig, ScaffoldConfig } from "../src/types"
|
||||
import { dateHelper, handlebarsParse, nowHelper } from "../src/utils"
|
||||
import { ScaffoldConfig } from "../src/types"
|
||||
import path from "path"
|
||||
import * as dateFns from "date-fns"
|
||||
import { OptionsBase } from "massarg/types"
|
||||
|
||||
const blankConf: ScaffoldConfig = {
|
||||
verbose: 0,
|
||||
@@ -12,20 +11,6 @@ const blankConf: ScaffoldConfig = {
|
||||
data: { name: "test" },
|
||||
}
|
||||
|
||||
const blankCliConf: ScaffoldCmdConfig & OptionsBase = {
|
||||
verbose: 0,
|
||||
name: "",
|
||||
output: "",
|
||||
templates: [],
|
||||
data: { name: "test" },
|
||||
overwrite: false,
|
||||
createSubFolder: false,
|
||||
dryRun: false,
|
||||
quiet: false,
|
||||
extras: [],
|
||||
help: false,
|
||||
}
|
||||
|
||||
describe("Utils", () => {
|
||||
describe("handlebarsParse", () => {
|
||||
let origSep: any
|
||||
@@ -39,7 +24,7 @@ describe("Utils", () => {
|
||||
})
|
||||
test("should work for windows paths", async () => {
|
||||
expect(handlebarsParse(blankConf, "C:\\exports\\{{name}}.txt", { isPath: true })).toEqual(
|
||||
Buffer.from("C:\\exports\\test.txt"),
|
||||
Buffer.from("C:\\exports\\test.txt")
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -53,7 +38,7 @@ describe("Utils", () => {
|
||||
})
|
||||
test("should work for non-windows paths", async () => {
|
||||
expect(handlebarsParse(blankConf, "/home/test/{{name}}.txt", { isPath: true })).toEqual(
|
||||
Buffer.from("/home/test/test.txt"),
|
||||
Buffer.from("/home/test/test.txt")
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -64,8 +49,8 @@ describe("Utils", () => {
|
||||
"/home/test/{{name}} \\{{escaped}}.txt",
|
||||
{
|
||||
isPath: false,
|
||||
},
|
||||
),
|
||||
}
|
||||
)
|
||||
).toEqual(Buffer.from("/home/test/test {{escaped}}.txt"))
|
||||
})
|
||||
})
|
||||
@@ -94,31 +79,13 @@ describe("Utils", () => {
|
||||
const fmt = "yyyy-MM-dd HH:mm"
|
||||
|
||||
expect(dateHelper(now.toISOString(), fmt, -1, "days")).toEqual(
|
||||
dateFns.format(dateFns.add(now, { days: -1 }), fmt),
|
||||
dateFns.format(dateFns.add(now, { days: -1 }), fmt)
|
||||
)
|
||||
expect(dateHelper(now.toISOString(), fmt, 1, "months")).toEqual(
|
||||
dateFns.format(dateFns.add(now, { months: 1 }), fmt),
|
||||
dateFns.format(dateFns.add(now, { months: 1 }), fmt)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("parseAppendData", () => {
|
||||
test('works for "key=value"', () => {
|
||||
expect(parseAppendData("key=value", blankCliConf)).toEqual({ key: "value", name: "test" })
|
||||
})
|
||||
|
||||
test('works for "key:=value"', () => {
|
||||
expect(parseAppendData("key:=123", blankCliConf)).toEqual({ key: 123, name: "test" })
|
||||
})
|
||||
|
||||
test("overwrites existing value", () => {
|
||||
expect(parseAppendData("name:=123", blankCliConf)).toEqual({ name: 123 })
|
||||
})
|
||||
|
||||
test("works with quotes", () => {
|
||||
expect(parseAppendData('key="value test"', blankCliConf)).toEqual({ key: "value test", name: "test" })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,13 +4,20 @@
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"lib": ["ES2019"],
|
||||
"lib": [
|
||||
"ES2019",
|
||||
],
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"removeComments": false
|
||||
"removeComments": false,
|
||||
},
|
||||
"include": ["src/index.ts", "src/cmd.ts"],
|
||||
"exclude": ["tests/*"]
|
||||
"include": [
|
||||
"src/index.ts",
|
||||
"src/cmd.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"tests/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
/** @type {import('typedoc').TypeDocOptions} */
|
||||
module.exports = {
|
||||
name: "Simple Scaffold",
|
||||
entryPoints: ["src/index.ts"],
|
||||
includeVersion: true,
|
||||
categorizeByGroup: false,
|
||||
sort: ["visibility"],
|
||||
categoryOrder: ["Main", "*"],
|
||||
media: "media",
|
||||
githubPages: true,
|
||||
entryPointStrategy: "expand",
|
||||
out: "docs",
|
||||
excludePrivate: true,
|
||||
excludeProtected: true,
|
||||
excludeInternal: true,
|
||||
gaID: "GTM-KHQS9TQ",
|
||||
validation: {
|
||||
invalidLink: true,
|
||||
},
|
||||
plugin: ["@knodes/typedoc-plugin-pages"],
|
||||
customCss: "src/docs.css",
|
||||
options: "typedoc.config.js",
|
||||
logLevel: "Verbose",
|
||||
pluginPages: {
|
||||
logLevel: "Verbose",
|
||||
pages: [
|
||||
{
|
||||
name: "Configuration",
|
||||
source: "README.md",
|
||||
childrenDir: path.join(process.cwd(), "pages"),
|
||||
childrenOutputDir: "./",
|
||||
children: [
|
||||
{
|
||||
name: "CLI usage",
|
||||
source: "cli.md",
|
||||
},
|
||||
{
|
||||
name: "Node.js usage",
|
||||
source: "node.md",
|
||||
},
|
||||
{
|
||||
name: "Templates",
|
||||
source: "templates.md",
|
||||
},
|
||||
{
|
||||
name: "Configuration Files",
|
||||
source: "configuration_files.md",
|
||||
},
|
||||
{
|
||||
name: "Migrating v0.x to v1.x",
|
||||
source: "migration.md",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Changelog",
|
||||
source: "./CHANGELOG.md",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user