Compare commits

..

78 Commits

Author SHA1 Message Date
semantic-release-bot
7da786a463 chore(release): 1.2.0 [skip ci]
## [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](3c5c2ded02))

### Bug Fixes

* ci node version ([767d34c](767d34c684))
* semantic-release build dir ([f7956dd](f7956ddc78))
* support quote wrapping in append-data ([4fecca8](4fecca8483))
2023-04-24 10:06:10 +00:00
Chen Asraf
4fecca8483 fix: support quote wrapping in append-data 2023-04-24 13:02:29 +03:00
Chen Asraf
222e1a04ca build: update github action versions 2023-04-24 13:02:29 +03:00
Chen Asraf
f7956ddc78 fix: semantic-release build dir 2023-04-24 13:02:29 +03:00
Chen Asraf
029f260a4c chore: bump version number [skip-ci] 2023-04-24 13:02:29 +03:00
Chen Asraf
7a4c0abd33 build: fix docs build 2023-04-24 13:02:29 +03:00
Chen Asraf
ed385ecfee docs: update spacing 2023-04-24 13:02:29 +03:00
Chen Asraf
833ea9d404 docs: update table css 2023-04-24 13:02:29 +03:00
Chen Asraf
8fb508f32f docs: update typedoc & remove custom theme 2023-04-24 13:02:29 +03:00
Chen Asraf
75641e5e20 build: add missing dependencies 2023-04-24 13:02:29 +03:00
Chen Asraf
f4c745bfeb build: add missing dependencies 2023-04-24 13:02:29 +03:00
Chen Asraf
47b4c427ac build: fix build 2023-04-24 13:02:29 +03:00
Chen Asraf
7ef6d583a8 docs: update docs 2023-04-24 13:02:29 +03:00
Chen Asraf
59a46b0455 build: update dependencies & fix build 2023-04-24 13:02:29 +03:00
Chen Asraf
767d34c684 fix: ci node version 2023-04-24 13:02:29 +03:00
Chen Asraf
2050ea38d5 build: semantic-release 2023-04-24 13:02:29 +03:00
Chen Asraf
1bfcafad46 chore: update FUNDING.yml 2023-04-24 13:02:29 +03:00
Chen Asraf
3c5c2ded02 feat: append-data cli flag 2023-04-24 13:02:29 +03:00
Chen Asraf
8f5bee8da6 docs: update domain 2023-04-24 13:02:29 +03:00
Chen Asraf
7d1dc2a808 Merge pull request #50 from chenasraf/develop
v1.1.4
2023-03-13 21:29:42 +02:00
Chen Asraf
8e432bfb0b chore: bump version number 2023-03-13 21:25:30 +02:00
Chen Asraf
7c19c53337 fix: github action node version 2023-03-13 21:23:52 +02:00
Chen Asraf
94fec76616 fix: github action node version 2023-03-13 21:20:43 +02:00
Chen Asraf
20400bd81d chore: update dependencies 2023-03-13 21:16:20 +02:00
Chen Asraf
c334396d74 docs: update typedoc version 2023-03-13 09:53:33 +02:00
dependabot[bot]
78c8e693a8 Merge pull request #47 from chenasraf/dependabot/npm_and_yarn/json5-2.2.3 2023-03-13 07:47:54 +00:00
dependabot[bot]
7865f27471 Merge pull request #49 from chenasraf/dependabot/npm_and_yarn/minimatch-3.1.2 2023-03-13 07:47:43 +00:00
dependabot[bot]
ee4e52c4a0 Bump minimatch from 3.0.4 to 3.1.2
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.0.4 to 3.1.2.
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-11 21:34:44 +00:00
Chen Asraf
d7d2b13888 chore: bump version number [publish] 2023-03-11 23:34:09 +02:00
Chen Asraf
6b6baefbce Merge pull request #48 from locene/develop
fix: base path
2023-03-11 23:29:23 +02:00
locene
943717a769 fix: base path 2023-03-10 06:01:57 +08:00
dependabot[bot]
e28c4db323 Bump json5 from 2.2.0 to 2.2.3
Bumps [json5](https://github.com/json5/json5) from 2.2.0 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.0...v2.2.3)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-07 04:02:37 +00:00
Chen Asraf
5308ef9618 Update README.md 2022-12-27 12:45:14 +02:00
Chen Asraf
a8e9f71c11 Update FUNDING.yml 2022-08-29 00:02:20 +03:00
Chen Asraf
6fa2a8b03b Update README.md 2022-08-16 16:42:38 +03:00
Chen Asraf
7c69010e95 update docs + spell checker 2022-05-24 10:09:48 +03:00
Chen Asraf
b569f2b0a1 formatting updates 2022-05-24 00:36:17 +03:00
Chen Asraf
adba6490e8 Update README.md 2022-05-18 01:04:10 +03:00
Chen Asraf
1d0c20cba9 docs: fix typo 2022-05-15 14:32:22 +03:00
Chen Asraf
cd68ab41d4 Update README.md 2022-05-15 14:02:42 +03:00
Chen Asraf
1b37a8eb40 update intro gif 2022-05-06 01:25:34 +03:00
Chen Asraf
576798cfeb update intro gif 2022-05-06 01:24:30 +03:00
Chen Asraf
ac8af8e129 revert docs build in ci 2022-05-03 19:52:23 +03:00
Chen Asraf
96b93d8b60 try fix docs build process 2022-05-03 19:51:12 +03:00
Chen Asraf
11edb0d464 remove unnecessary install 2022-05-03 19:46:00 +03:00
Chen Asraf
35262b5a44 fix docs build process 2022-05-03 19:45:34 +03:00
Chen Asraf
3b77e6938c improve docs build process 2022-05-03 19:41:16 +03:00
Chen Asraf
3cf93598c3 improve docs build process 2022-05-03 19:38:12 +03:00
Chen Asraf
8957b595da improve docs build process 2022-05-03 19:35:36 +03:00
Chen Asraf
2841ebd3d2 update docs + formatting update 2022-05-03 19:24:18 +03:00
Chen Asraf
0364247904 fix build 2022-05-01 14:26:14 +03:00
Chen Asraf
ac2c0d7ffb fix build 2022-05-01 14:24:44 +03:00
Chen Asraf
a002402088 update docs 2022-05-01 14:23:03 +03:00
Chen Asraf
bdc23e7074 Update README.md 2022-05-01 13:56:20 +03:00
Chen Asraf
6160a04009 Update README.md 2022-05-01 13:53:57 +03:00
Chen Asraf
7414baf2ab Merge pull request #42 from chenasraf/master
v1.1.2 back merge
2022-04-29 10:19:26 +03:00
Chen Asraf
c45d870233 v1.1.2
Fix binary file copy
2022-04-29 10:14:48 +03:00
Chen Asraf
8c8cedee48 update help command 2022-04-29 10:10:53 +03:00
Chen Asraf
40606edaee update test desc 2022-04-29 09:56:59 +03:00
Chen Asraf
e450ad242e fix: binary files + add tests 2022-04-29 09:54:06 +03:00
Chen Asraf
df60922154 Merge pull request #39 from chenasraf/master
Merge pull request #38 from chenasraf/develop
2022-04-24 17:41:55 +03:00
Chen Asraf
256d919253 Merge pull request #38 from chenasraf/develop
v1.1.1 - security fix
2022-04-24 17:30:10 +03:00
Chen Asraf
f8c31a1ecf Merge branch 'master' into develop 2022-04-24 17:28:39 +03:00
Chen Asraf
5571aba8dc npm audit fix 2022-04-24 17:27:03 +03:00
Chen Asraf
12f8bca7b5 fix doc deps 2022-04-23 11:29:09 +03:00
Chen Asraf
643431d333 fix gtag 2022-04-23 11:24:02 +03:00
Chen Asraf
0042c12ee7 fix docs build 2022-04-23 11:19:41 +03:00
Chen Asraf
869911bba0 update docs GA 2022-04-23 11:18:23 +03:00
Chen Asraf
9bd6219aa5 add gaid to docs 2022-04-23 09:45:48 +03:00
Chen Asraf
27a6ba4dda update docs 2022-04-22 23:00:11 +03:00
Chen Asraf
923b5316be update docs 2022-04-21 15:37:44 +03:00
Chen Asraf
bf10fb8ec3 update docs 2022-04-21 15:34:03 +03:00
Chen Asraf
4e2fa01bed update docs [skip publish] 2022-04-21 14:45:58 +03:00
Chen Asraf
af65ecadb9 try to fix docs [skip publish] 2022-04-21 13:07:19 +03:00
Chen Asraf
0d359b1917 fix workflow [skip publish] 2022-04-21 12:37:01 +03:00
Chen Asraf
f2a75c9fb0 Typdoc (#37) [skip publish]
* update package.json

* generate typedocs

* update docs
2022-04-21 12:35:32 +03:00
Chen Asraf
3c57638903 update package.json 2022-04-21 03:04:08 +03:00
Chen Asraf
a3deda2d80 v1.1 (#35)
v1.1

### New features

- Enable custom transformations with `beforeWrite`
- Add built-in date formatting helpers

### Commit log

* add develop branch

* chore: remove unneeded dep

* Update issue templates [skip ci]

* update PR workflow

* implementation of before-write callback + tests

* clean up + update docs

* add explicit function return types

* bump version number

* docs: add table of contents

* correct version number

* update workflows + release alpha

* update README.md [skip ci]

* update README.md [skip ci]

* implement now & custom date helpers

* docs + bump version number

* docs cleanup

* update docs

* accept async beforeWrite callback

* fix docs

* remove redundent check

* fix type import

* update README.md [skip ci]

* bump version number [skip ci]

* update docs [skip ci]
2022-04-21 02:57:52 +03:00
33 changed files with 5546 additions and 1675 deletions

View File

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

5
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: chenasraf
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: casraf
@@ -9,4 +9,5 @@ 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: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom:
- "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22&currency_code=ILS&source=url"

View File

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

View File

@@ -1,20 +1,24 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
title: "[FEATURE] "
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.
#### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered, if
applicable.
#### Additional context
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,52 +0,0 @@
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 Normal file
View File

@@ -0,0 +1,20 @@
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 typedoc
- uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs

View File

@@ -8,10 +8,10 @@ jobs:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "12.x"
node-version: "18.x"
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn test

View File

@@ -1,50 +1,24 @@
name: Release
name: Semantic Release
on:
push:
branches: [master]
branches: [master, develop, feat/*, fix/*]
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
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "12.x"
node-version: "18.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
- run: yarn semantic-release
if: "!contains(github.event.head_commit.message, '[skip publish]')"
id: update_tag
with:
env:
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
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
View File

@@ -42,6 +42,9 @@ typings/
# Optional npm cache directory
.npm
# NPM
.npmrc
# Optional eslint cache
.eslintcache
@@ -61,3 +64,5 @@ examples/test-output/**/*
dist/
.DS_Store
tmp/
docs/
.nvmrc

View File

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

View File

@@ -1,5 +1,15 @@
{
"semi": false,
"trailingComma": "all",
"printWidth": 120,
"tabWidth": 2
"tabWidth": 2,
"overrides": [
{
"files": "*.md",
"options": {
"printWidth": 100,
"proseWrap": "always"
}
}
]
}

50
.vscode/launch.json vendored
View File

@@ -1,29 +1,27 @@
{
// 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,
// 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"
},
{
"type": "node",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickProcess}"
}
]
"sourceMaps": true
},
{
"type": "node",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickProcess}"
}
]
}

12
.vscode/settings.json vendored
View File

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

26
.vscode/tasks.json vendored
View File

@@ -5,49 +5,55 @@
"script": "build",
"label": "build",
"type": "npm",
"problemMatcher": [],
"problemMatcher": []
},
{
"script": "dev",
"label": "dev",
"type": "npm",
"problemMatcher": [],
"problemMatcher": []
},
{
"command": "yarn typedoc --watch",
"label": "typedoc --watch",
"type": "shell",
"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": []
}
]
}

92
CHANGELOG.md Normal file
View File

@@ -0,0 +1,92 @@
# Change Log
## [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-04-24)
### Features
- append-data cli flag
([2f4f7fc](https://github.com/chenasraf/simple-scaffold/commit/2f4f7fce47669e37ff49154fbec2acdfd0f5860e))
### 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))
### [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)

View File

@@ -1,16 +1,20 @@
# Migrating from 0.x to 1.0
# Migrating from 0.x to 1.x
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`.
- `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 the [readme](/README.md)
for more information.
## Template syntax changes

345
README.md
View File

@@ -1,41 +1,56 @@
# Simple Scaffold
<h1 align="center">Simple Scaffold</h1>
Simple Scaffold allows you to generate any set of files in the easiest way possible with simple commands.
<h2 align="center">
It is completely framework agnostic so you can use it for anything from a few simple files to an
entire app boilerplate setup.
[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)
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.
![version](https://img.shields.io/github/package-json/v/chenasraf/simple-scaffold/master?label=version)
![build](https://img.shields.io/github/actions/workflow/status/chenasraf/simple-scaffold/release.yml?branch=master)
</h2>
Generate any set of files in the easiest way possible with simple commands.
It is completely agnostic and un-opinionated 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.
<div align="center">
![sample animation](media/intro.gif)
</div>
<br />
<details>
<summary>Table of contents</summary>
- [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)
- [Install](#install)
- [Command Line Interface (CLI)](#command-line-interface-cli)
- [Available flags](#available-flags)
- [Node module](#node-module)
- [Node-specific options](#node-specific-options)
- [Preparing files](#preparing-files)
- [Template files](#template-files)
- [Variable/token replacement](#variabletoken-replacement)
- [Helpers](#helpers)
- [Built-in Helpers](#built-in-helpers)
- [Capitalization Helpers](#capitalization-helpers)
- [Date helpers](#date-helpers)
- [Custom Helpers](#custom-helpers)
- [Examples](#examples)
- [Examples](#examples)
- [Run](#run)
- [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)
- [Equivalent Node Module Example](#equivalent-node-module-example)
- [Files](#files)
- [Input](#input)
- [Output](#output)
- [Contributing](#contributing)
</details>
@@ -54,11 +69,14 @@ yarn [global] add simple-scaffold
npx simple-scaffold@latest <...args>
```
## Use as a command line tool
## Command Line Interface (CLI)
### Command Line Options
### Available flags
```plaintext
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.
@@ -67,26 +85,30 @@ 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.
--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.
--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.
--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)
--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`.
@@ -101,9 +123,9 @@ Options:
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)
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`:
@@ -116,10 +138,11 @@ You can also add this as a script in your `package.json`:
}
```
## Use in Node.js
## Node module
You can also build the scaffold yourself, if you want to create more complex arguments or scaffold
groups - simply pass a config object to the Scaffold function when you are ready to start.
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
@@ -148,11 +171,12 @@ const scaffold = Scaffold(config)
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. |
| Option | Type | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `output` | | In addition to being passed the same as CLI, it may also be passed a function for each input file to output into a dynamic path: `{ output: (fullPath, baseDir, baseName) => path.resolve(baseDir, baseName) }` |
| `helpers` | `Record<string, (string) => string>` | Helpers are simple functions that transform your `data` variables into other values. See [Helpers](#helpers) for the list of default helpers, or add your own to be loaded into the template parser. |
| `beforeWrite` | `(content: Buffer, rawContent: Buffer, outputPath: string) => Promise<String \| Buffer \| undefined> \| String \| Buffer \| undefined` | Supply this function to override the final output contents of each of your files, allowing you to add more pre-processing to your generator pipeline. The return value of this function will replace the output content of the respective file, which you may discriminate (if needed) using the `outputPath` argument. |
| `append-data` | | Does not exist in Node.JS options, use `data` instead. |
## Preparing files
@@ -170,11 +194,11 @@ 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` |
| 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
@@ -190,13 +214,13 @@ For example, using the following command:
npx simple-scaffold@latest \
--templates templates/components/{{name}}.jsx \
--output src/components \
-create-sub-folder true \
--create-sub-folder true \
MyComponent
```
Will output a file with the path:
```plaintext
```text
<working_dir>/src/components/MyComponent.jsx
```
@@ -208,19 +232,21 @@ Your `data` will be pre-populated with the following:
- `{{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
> 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
### Helpers
Simple-Scaffold provides some built-in text transformation filters usable by handleBars.
#### 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
##### Capitalization Helpers
| Helper name | Example code | Example output |
| ------------ | ----------------------- | -------------- |
@@ -234,41 +260,41 @@ replace `My Name` with `my_name` when producing the final value.
| `upperCase` | `{{ upperCase name }}` | MY NAME |
| `lowerCase` | `{{ lowerCase name }}` | my name |
#### Date helpers
##### 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 |
| 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
- 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"
)
```
```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.
- **The now helper** (for current time) takes the same arguments, minus the first one (`date`) as it
is implicitly the current date.
### Custom Helpers
#### 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:
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 = {
@@ -279,83 +305,111 @@ config.helpers = {
All of the above helpers (built in and custom) will also be available to you when using
`subFolderNameHelper` (`--sub-folder-name-helper`/`-sh`) as a possible value.
> To see more information on how helpers work and more features, see
> [Handlebars.js docs](https://handlebarsjs.com/guide/#custom-helpers).
## Examples
### Command Example
### Run
#### Command Example
```bash
simple-scaffold MyComponent \
-t project/scaffold/**/* \
-o src/components \
-d '{"className": "myClassName"}'
-d '{"className": "myClassName","author": "Chen Asraf"}'
MyComponent
```
### Example Scaffold Input
#### Equivalent Node Module Example
#### Input Directory structure
```typescript
import Scaffold from "simple-scaffold"
```plaintext
- project
- scaffold
- {{Name}}.js
- src
- components
- ...
```
#### Contents of `project/scaffold/{{Name}}.jsx`
```typescriptreact
import React from 'react'
export default {{camelCase ame}}: React.FC = (props) => {
return (
<div className="{{className}}">{{camelCase name}} Component</div>
)
async function main() {
await Scaffold({
name: "MyComponent",
templates: ["project/scaffold/**/*"],
output: ["src/components"],
data: {
className: "myClassName",
author: "Chen Asraf",
},
})
console.log("Done.")
}
```
### Example Scaffold Output
### Files
### Output directory structure
#### Input
```plaintext
- project
- src
- components
- MyComponent
- MyComponent.js
- ...
```
- Input file path:
With `createSubFolder = false`:
```text
project → scaffold → {{Name}}.js → src → components
```
```plaintext
- project
- src
- components
- MyComponent.js
- ...
```
- Input file contents:
#### Contents of `project/scaffold/MyComponent/MyComponent.jsx`
```typescript
/**
* Author: {{ author }}
* Date: {{ now "yyyy-MM-dd" }}
*/
import React from 'react'
```typescriptreact
import React from 'react'
export default {{camelCase name}}: React.FC = (props) => {
return (
<div className="{{className}}">{{camelCase name}} Component</div>
)
}
```
export default MyComponent: React.FC = (props) => {
return (
<div className="myClassName">MyComponent 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: 2022-01-01
*/
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 would like to donate a bit to help keep the project
alive, I would be 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 are feeling incredibly generous and would like to donate
just a small amount to help sustain this project, I would be very very thankful!
<a href='https://ko-fi.com/casraf' target='_blank'>
<img height='36' style='border:0px;height:36px;'
@@ -380,11 +434,20 @@ 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
- Use `yarn build` to build the output once
- 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. Either use `yarn dev` to watch
> for changes and build, or `yarn build` before running this, or use `yarn build-cmd` instead,
> which triggers a build right before running the command with the rest of the given arguments.
> This requires an updated build, and does not trigger one itself. 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)

View File

@@ -1,3 +0,0 @@
module.exports = {
plugins: ["@babel/plugin-proposal-nullish-coalescing-operator"],
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -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 and instances between every test
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
@@ -50,6 +50,11 @@ 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: [],
@@ -73,6 +78,8 @@ export default {
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "tsx",
@@ -82,6 +89,11 @@ 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"],
@@ -101,7 +113,7 @@ export default {
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
@@ -110,7 +122,7 @@ export default {
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
@@ -137,7 +149,7 @@ export default {
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "node",
// testEnvironment: "jest-environment-node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
@@ -165,14 +177,8 @@ 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: {},
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
@@ -180,6 +186,11 @@ 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,
@@ -191,6 +202,4 @@ export default {
// Whether to use watchman for file crawling
// watchman: true,
// extensionsToTreatAsEsm: [".ts"],
}

BIN
media/intro.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

View File

@@ -1,7 +1,8 @@
{
"name": "simple-scaffold",
"version": "1.1.0-alpha.6",
"description": "Create files based on templates",
"version": "1.2.0",
"description": "A simple command to generate any file structure, from single components to entire app boilerplates.",
"homepage": "https://casraf.dev/simple-scaffold",
"repository": "https://github.com/chenasraf/simple-scaffold.git",
"author": "Chen Asraf <inbox@casraf.com>",
"license": "MIT",
@@ -23,31 +24,46 @@
"build": "yarn clean && tsc && chmod -R +x ./dist && cp ./package.json ./README.md ./dist/",
"dev": "tsc --watch",
"start": "node dist/scaffold.js",
"test": "jest --verbose",
"test": "jest",
"cmd": "node --trace-warnings dist/cmd.js",
"build-test": "yarn build && yarn test",
"build-cmd": "yarn build && yarn cmd"
"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"
},
"dependencies": {
"chalk": "^4.1.2",
"date-fns": "^2.28.0",
"glob": "^7.1.3",
"date-fns": "^2.29.3",
"glob": "^9.2.1",
"handlebars": "^4.7.7",
"lodash": "^4.17.21",
"massarg": "^1.0.5",
"massarg": "^1.0.7-pre.1",
"semantic-release-conventional-commits": "^3.0.0",
"util.promisify": "^1.1.1"
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/jest": "^26.0.24",
"@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/lodash": "^4.14.171",
"@types/mock-fs": "^4.13.1",
"@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"
"@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"
},
"peerDependencies": {
"doc-theme": "file:./doc-theme"
}
}

84
release.config.js Normal file
View File

@@ -0,0 +1,84 @@
/** @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: [
{ 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: "Misc", release: "patch" },
{ type: "perf", section: "Misc", release: "patch" },
{ type: "build", section: "Misc", release: "patch" },
{ type: "chore", section: "Misc", release: "patch" },
{ type: "test", section: "Misc", release: "patch" },
],
},
],
[
"@semantic-release/release-notes-generator",
{
preset: "conventionalcommits",
parserOpts: {
noteKeywords: ["breaking"],
types: [
{ 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: "Misc", release: "patch" },
{ type: "perf", section: "Misc", release: "patch" },
{ type: "build", section: "Misc", release: "patch" },
{ type: "chore", section: "Misc", release: "patch" },
{ type: "test", section: "Misc", release: "patch" },
],
},
},
],
[
"@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"],
},
],
],
}

View File

@@ -3,11 +3,20 @@ 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 } from "./utils"
export async function parseCliArgs(args = process.argv.slice(2)) {
const pkg = JSON.parse((await fs.readFile(path.join(__dirname, "package.json"))).toString())
export function parseCliArgs(args = process.argv.slice(2)) {
return (
massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
.main(Scaffold)
massarg<ScaffoldCmdConfig>()
.main((config) => {
config.data = { ...config.data, ...config.appendData }
delete config.appendData
return Scaffold(config)
})
.option({
name: "name",
aliases: ["n"],
@@ -44,6 +53,13 @@ export 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"],
@@ -67,7 +83,10 @@ export 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({
@@ -88,9 +107,12 @@ export function parseCliArgs(args = process.argv.slice(2)) {
binName: "simple-scaffold",
useGlobalColumns: true,
usageExample: "[options]",
header: "Create structured files based on templates.",
header: [`Create structured files based on templates.`].join("\n"),
footer: [
`Copyright © Chen Asraf 2021`,
`Version: ${pkg.version}`,
`Copyright © Chen Asraf 2017-${new Date().getFullYear()}`,
``,
`Documentation: ${chalk.underline`https://casraf.dev/simple-scaffold`}`,
`NPM: ${chalk.underline`https://npmjs.com/package/simple-scaffold`}`,
`GitHub: ${chalk.underline`https://github.com/chenasraf/simple-scaffold`}`,
].join("\n"),

46
src/docs.css Normal file
View File

@@ -0,0 +1,46 @@
.tsd-typography table {
border-collapse: collapse;
width: 100%;
box-sizing
}
.tsd-typography table td,
.tsd-typography table th {
vertical-align: top;
border: 1px solid var(--color-accent);
padding: 6px;
}
.tsd-typography h1 + table,
.tsd-typography h2 + table,
.tsd-typography h3 + table,
.tsd-typography h4 + table,
.tsd-typography h5 + table,
.tsd-typography h6 + table,
.tsd-typography h1 + pre,
.tsd-typography h2 + pre,
.tsd-typography h3 + pre,
.tsd-typography h4 + pre,
.tsd-typography h5 + pre,
.tsd-typography h6 + pre {
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;
}

View File

@@ -1,3 +1,9 @@
/**
* @module
* Simple Scaffold
*
* See [readme](README.md)
*/
import path from "path"
import {
@@ -31,46 +37,47 @@ 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. Common cases are transforming name-case format
* (e.g. `MyName` &rarr; `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 |
* pre-define the data and use a duplicated key.
*
* Any functions you provide in `helpers` option will also be available to you to make custom formatting as you see fit
* (for example, formatting a date)
*
* For available default values, see {@link DefaultHelpers}.
*
* @param {ScaffoldConfig} config The main configuration object
*
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
*
* @category Main
*/
export async function Scaffold({ ...options }: ScaffoldConfig): Promise<void> {
options.output ??= process.cwd()
export async function Scaffold(config: ScaffoldConfig): Promise<void> {
config.output ??= process.cwd()
registerHelpers(options)
registerHelpers(config)
try {
options.data = { name: options.name, Name: pascalCase(options.name), ...options.data }
logInitStep(options)
for (let _template of options.templates) {
config.data = { name: config.name, Name: pascalCase(config.name), ...config.data }
logInitStep(config)
for (let _template of config.templates) {
try {
const { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template } = await getTemplateGlobInfo(
options,
_template
config,
_template,
)
const files = await getFileList(options, template)
const files = await getFileList(config, 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(options, {
logInputFile(config, {
origTemplate,
relPath,
template,
@@ -80,7 +87,7 @@ export async function Scaffold({ ...options }: ScaffoldConfig): Promise<void> {
isDirOrGlob,
isGlob,
})
await handleTemplateFile(options, options.data, {
await handleTemplateFile(config, {
templatePath: inputFilePath,
basePath,
})
@@ -90,25 +97,24 @@ export async function Scaffold({ ...options }: ScaffoldConfig): Promise<void> {
}
}
} catch (e: any) {
log(options, LogLevel.Error, e)
log(config, LogLevel.Error, e)
throw e
}
}
async function handleTemplateFile(
options: ScaffoldConfig,
data: Record<string, string>,
{ templatePath, basePath }: { templatePath: string; basePath: string }
config: ScaffoldConfig,
{ templatePath, basePath }: { templatePath: string; basePath: string },
): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const { inputPath, outputPathOpt, outputDir, outputPath, exists } = await getTemplateFileInfo(options, data, {
const { inputPath, outputPathOpt, outputDir, outputPath, exists } = await getTemplateFileInfo(config, {
templatePath,
basePath,
})
const overwrite = getOptionValueForFile(options, inputPath, options.overwrite ?? false)
const overwrite = getOptionValueForFile(config, inputPath, config.overwrite ?? false)
log(
options,
config,
LogLevel.Debug,
`\nParsing ${templatePath}`,
`\nBase path: ${basePath}`,
@@ -116,13 +122,13 @@ async function handleTemplateFile(
`\nOutput Path Opt: ${outputPathOpt}`,
`\nFull output dir: ${outputDir}`,
`\nFull output path: ${outputPath}`,
`\n`
`\n`,
)
await createDirIfNotExists(path.dirname(outputPath), options)
await createDirIfNotExists(path.dirname(outputPath), config)
log(options, LogLevel.Info, `Writing to ${outputPath}`)
await copyFileTransformed(options, { exists, overwrite, outputPath, inputPath })
log(config, LogLevel.Info, `Writing to ${outputPath}`)
await copyFileTransformed(config, { exists, overwrite, outputPath, inputPath })
resolve()
} catch (e: any) {
handleErr(e)

View File

@@ -1,33 +1,15 @@
import { HelperDelegate } from "handlebars/runtime"
export enum LogLevel {
None = 0,
Debug = 1,
Info = 2,
Warning = 3,
Error = 4,
}
export type FileResponseFn<T> = (fullPath: string, basedir: string, basename: string) => T
export type FileResponse<T> = T | FileResponseFn<T>
export type DefaultHelperKeys =
| "camelCase"
| "date"
| "hyphenCase"
| "kebabCase"
| "lowerCase"
| "now"
| "pascalCase"
| "snakeCase"
| "startCase"
| "upperCase"
export type HelperKeys<T> = DefaultHelperKeys | T
export type Helper = 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
@@ -37,46 +19,77 @@ 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. */
/**
* 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}
*/
output: FileResponse<string>
/**
* Create subfolder with the input name (default: `false`)
* 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`
*/
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, string>
data?: Record<string, any>
/**
* Enable to override output files, even if they already exist. (default: `false`)
* Enable to override output files, even if they already exist.
*
* 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`) */
/**
* Suppress output logs (Same as `verbose: 0` or `verbose: LogLevel.None`)
* @see {@link verbose}
*/
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. (default: `2 (info)`)
* @see LogLevel
* of the same level or higher.
*
* @see {@link LogLevel}
*
* @default `2 (info)`
*/
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
@@ -84,7 +97,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) => string
* (text: string, ...args: any[]) => string
* ```
*
* A full example might be:
@@ -93,30 +106,47 @@ export interface ScaffoldConfig {
* Scaffold({
* //...
* helpers: {
* upperCamelCase: (text) => camelCase(text).toUpperCase()
* upperKebabCase: (text) => kebabCase(text).toUpperCase()
* }
* })
* ```
*
* 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 |
* 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
*/
helpers?: Record<string, Helper>
/**
* Default transformer to apply to subfolder name when using `createSubFolder: true`. Can be one of the default
* helpers, or a custom one you provide to `helpers`. Defaults to `undefined`, which means no transformation is done.
* 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}
*/
subFolderNameHelper?: DefaultHelperKeys | string
subFolderNameHelper?: DefaultHelpers | string
/**
* This callback runs right before content is being written to the disk. If you supply this function, you may return
@@ -129,21 +159,179 @@ export interface ScaffoldConfig {
* @param rawContent The original template before token replacement
* @param outputPath The final output path of the processed file
*
* @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()`)
* @returns {Promise<String | Buffer | undefined> | String | Buffer | undefined} The final output of the file
* contents-only, after further modifications - or `undefined` to use the original content (i.e. `content.toString()`)
*/
beforeWrite?(
content: Buffer,
rawContent: Buffer,
outputPath: string
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

View File

@@ -1,6 +1,14 @@
import path from "path"
import { F_OK } from "constants"
import { DefaultHelperKeys, FileResponse, FileResponseFn, Helper, LogLevel, ScaffoldConfig } from "./types"
import {
DefaultHelpers,
FileResponse,
FileResponseHandler,
Helper,
LogLevel,
ScaffoldCmdConfig,
ScaffoldConfig,
} from "./types"
import camelCase from "lodash/camelCase"
import snakeCase from "lodash/snakeCase"
import kebabCase from "lodash/kebabCase"
@@ -12,6 +20,8 @@ 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,
@@ -19,11 +29,9 @@ const dateFns = {
parseISO: dtParseISO,
}
import { glob } from "glob"
import { promisify } from "util"
const { readFile, writeFile } = fsPromises
export const defaultHelpers: Record<DefaultHelperKeys, Helper> = {
export const defaultHelpers: Record<DefaultHelpers, Helper> = {
camelCase,
snakeCase,
startCase,
@@ -41,13 +49,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)
@@ -66,22 +74,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(options: ScaffoldConfig): void {
const _helpers = { ...defaultHelpers, ...options.helpers }
export function registerHelpers(config: ScaffoldConfig): void {
const _helpers = { ...defaultHelpers, ...config.helpers }
for (const helperName in _helpers) {
log(options, LogLevel.Debug, `Registering helper: ${helperName}`)
log(config, LogLevel.Debug, `Registering helper: ${helperName}`)
Handlebars.registerHelper(helperName, _helpers[helperName as keyof typeof _helpers])
}
}
@@ -90,8 +98,8 @@ export function handleErr(err: NodeJS.ErrnoException | null): void {
if (err) throw err
}
export function log(options: ScaffoldConfig, level: LogLevel, ...obj: any[]): void {
if (options.quiet || options.verbose === LogLevel.None || level < (options.verbose ?? LogLevel.Info)) {
export function log(config: ScaffoldConfig, level: LogLevel, ...obj: any[]): void {
if (config.quiet || config.verbose === LogLevel.None || level < (config.verbose ?? LogLevel.Info)) {
return
}
const levelColor: Record<LogLevel, keyof typeof chalk> = {
@@ -110,21 +118,21 @@ export function log(options: ScaffoldConfig, level: LogLevel, ...obj: any[]): vo
? 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, options: ScaffoldConfig): Promise<void> {
export async function createDirIfNotExists(dir: string, config: ScaffoldConfig): Promise<void> {
const parentDir = path.dirname(dir)
if (!(await pathExists(parentDir))) {
await createDirIfNotExists(parentDir, options)
await createDirIfNotExists(parentDir, config)
}
if (!(await pathExists(dir))) {
try {
log(options, LogLevel.Debug, `Creating dir ${dir}`)
log(config, LogLevel.Debug, `Creating dir ${dir}`)
await mkdir(dir)
return
} catch (e: any) {
@@ -137,27 +145,27 @@ export async function createDirIfNotExists(dir: string, options: ScaffoldConfig)
}
export function getOptionValueForFile<T>(
options: ScaffoldConfig,
config: ScaffoldConfig,
filePath: string,
fn: FileResponse<T>,
defaultValue?: T
defaultValue?: T,
): T {
if (typeof fn !== "function") {
return defaultValue ?? (fn as T)
}
return (fn as FileResponseFn<T>)(
return (fn as FileResponseHandler<T>)(
filePath,
path.dirname(handlebarsParse(options, filePath, { isPath: true }).toString()),
path.basename(handlebarsParse(options, filePath, { isPath: true }).toString())
path.dirname(handlebarsParse(config, filePath, { isPath: true }).toString()),
path.basename(handlebarsParse(config, filePath, { isPath: true }).toString()),
)
}
export function handlebarsParse(
options: ScaffoldConfig,
config: ScaffoldConfig,
templateBuffer: Buffer | string,
{ isPath = false }: { isPath?: boolean } = {}
{ isPath = false }: { isPath?: boolean } = {},
): Buffer {
const { data } = options
const { data } = config
try {
let str = templateBuffer.toString()
if (isPath) {
@@ -170,8 +178,8 @@ export function handlebarsParse(
}
return Buffer.from(outputContents)
} catch (e) {
log(options, LogLevel.Debug, e)
log(options, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content")
log(config, LogLevel.Debug, e)
log(config, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content")
return Buffer.from(templateBuffer)
}
}
@@ -212,12 +220,12 @@ export function getBasePath(relPath: string): string {
.replace(process.cwd(), "")
}
export async function getFileList(options: ScaffoldConfig, template: string): Promise<string[]> {
export async function getFileList(config: ScaffoldConfig, template: string): Promise<string[]> {
return (
await promisify(glob)(template, {
await glob(template, {
dot: true,
debug: options.verbose === LogLevel.Debug,
nodir: true,
// debug: config.verbose === LogLevel.Debug,
})
).map((f) => f.replace(/\//g, path.sep))
}
@@ -230,13 +238,14 @@ export interface GlobInfo {
template: string
}
export async function getTemplateGlobInfo(options: ScaffoldConfig, template: string): Promise<GlobInfo> {
const isGlob = glob.hasMagic(template)
log(options, LogLevel.Debug, "before isDir", "isGlob:", isGlob, template)
export async function getTemplateGlobInfo(config: ScaffoldConfig, template: string): Promise<GlobInfo> {
const isGlob = hasMagic(template)
log(config, LogLevel.Debug, "before isDir", "isGlob:", isGlob, template)
let _template = template
const nonGlobTemplate = isGlob ? removeGlob(template) : template
let nonGlobTemplate = isGlob ? removeGlob(template) : template
nonGlobTemplate = path.normalize(nonGlobTemplate)
const isDirOrGlob = isGlob ? true : await isDir(template)
log(options, LogLevel.Debug, "after isDir", isDirOrGlob)
log(config, LogLevel.Debug, "after isDir", isDirOrGlob)
const _shouldAddGlob = !isGlob && isDirOrGlob
const origTemplate = template
if (_shouldAddGlob) {
@@ -254,14 +263,13 @@ export interface OutputFileInfo {
}
export async function getTemplateFileInfo(
options: ScaffoldConfig,
data: Record<string, string>,
{ templatePath, basePath }: { templatePath: string; basePath: string }
config: ScaffoldConfig,
{ templatePath, basePath }: { templatePath: string; basePath: string },
): Promise<OutputFileInfo> {
const inputPath = path.resolve(process.cwd(), templatePath)
const outputPathOpt = getOptionValueForFile(options, inputPath, options.output)
const outputDir = getOutputDir(options, outputPathOpt, basePath)
const outputPath = handlebarsParse(options, path.join(outputDir, path.basename(inputPath)), {
const outputPathOpt = getOptionValueForFile(config, inputPath, config.output)
const outputDir = getOutputDir(config, outputPathOpt, basePath)
const outputPath = handlebarsParse(config, path.join(outputDir, path.basename(inputPath)), {
isPath: true,
}).toString()
const exists = await pathExists(outputPath)
@@ -269,7 +277,7 @@ export async function getTemplateFileInfo(
}
export async function copyFileTransformed(
options: ScaffoldConfig,
config: ScaffoldConfig,
{
exists,
overwrite,
@@ -280,47 +288,46 @@ export async function copyFileTransformed(
overwrite: boolean
outputPath: string
inputPath: string
}
},
): Promise<void> {
if (!exists || overwrite) {
if (exists && overwrite) {
log(options, LogLevel.Info, `File ${outputPath} exists, overwriting`)
log(config, LogLevel.Info, `File ${outputPath} exists, overwriting`)
}
const templateBuffer = await readFile(inputPath)
const unprocessedOutputContents = handlebarsParse(options, templateBuffer)
const finalOutputContents = (
(await options.beforeWrite?.(unprocessedOutputContents, templateBuffer, outputPath)) ?? unprocessedOutputContents
).toString()
const unprocessedOutputContents = handlebarsParse(config, templateBuffer)
const finalOutputContents =
(await config.beforeWrite?.(unprocessedOutputContents, templateBuffer, outputPath)) ?? unprocessedOutputContents
if (!options.dryRun) {
if (!config.dryRun) {
await writeFile(outputPath, finalOutputContents)
log(options, LogLevel.Info, "Done.")
log(config, LogLevel.Info, "Done.")
} else {
log(options, LogLevel.Info, "Content output:")
log(options, LogLevel.Info, finalOutputContents)
log(config, LogLevel.Info, "Content output:")
log(config, LogLevel.Info, finalOutputContents)
}
} else if (exists) {
log(options, LogLevel.Info, `File ${outputPath} already exists, skipping`)
log(config, LogLevel.Info, `File ${outputPath} already exists, skipping`)
}
}
export function getOutputDir(options: ScaffoldConfig, outputPathOpt: string, basePath: string): string {
export function getOutputDir(config: ScaffoldConfig, outputPathOpt: string, basePath: string): string {
return path.resolve(
process.cwd(),
...([
outputPathOpt,
basePath,
options.createSubFolder
? options.subFolderNameHelper
? handlebarsParse(options, `{{ ${options.subFolderNameHelper} name }}`).toString()
: options.name
config.createSubFolder
? config.subFolderNameHelper
? handlebarsParse(config, `{{ ${config.subFolderNameHelper} name }}`).toString()
: config.name
: undefined,
].filter(Boolean) as string[])
].filter(Boolean) as string[]),
)
}
export function logInputFile(
options: ScaffoldConfig,
config: ScaffoldConfig,
{
origTemplate,
relPath,
@@ -339,10 +346,10 @@ export function logInputFile(
basePath: string
isDirOrGlob: boolean
isGlob: boolean
}
},
): void {
log(
options,
config,
LogLevel.Debug,
`\nprocess.cwd(): ${process.cwd()}`,
`\norigTemplate: ${origTemplate}`,
@@ -353,24 +360,38 @@ export function logInputFile(
`\nbasePath: ${basePath}`,
`\nisDirOrGlob: ${isDirOrGlob}`,
`\nisGlob: ${isGlob}`,
`\n`
`\n`,
)
}
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!
export function logInitStep(config: ScaffoldConfig): void {
log(config, LogLevel.Debug, "Full config:", {
name: config.name,
templates: config.templates,
output: config.output,
createSubfolder: config.createSubFolder,
data: config.data,
overwrite: config.overwrite,
quiet: config.quiet,
subFolderTransformHelper: config.subFolderNameHelper,
helpers: Object.keys(config.helpers ?? {}),
verbose: `${config.verbose} (${Object.keys(LogLevel).find(
(k) => (LogLevel[k as any] as unknown as number) === config.verbose!,
)})`,
})
log(options, LogLevel.Info, "Data:", options.data)
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("'"))
}

View File

@@ -6,6 +6,7 @@ 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: {
@@ -13,6 +14,13 @@ const fileStructNormal = {
},
output: {},
}
const fileStructWithBinary = {
input: {
"{{name}}.txt": "Hello, my app is {{name}}",
"{{name}}.bin": crypto.randomBytes(10000),
},
output: {},
}
const fileStructWithData = {
input: {
@@ -45,7 +53,7 @@ const fileStructHelpers = {
input: {
defaults: defaultHelperNames.reduce<Record<string, string>>(
(all, cur) => ({ ...all, [cur + ".txt"]: `{{ ${cur} name }}` }),
{}
{},
),
custom: {
"add1.txt": "{{ add1 name }}",
@@ -109,7 +117,25 @@ 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(
@@ -157,7 +183,7 @@ describe("Scaffold", () => {
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my value is 2")
})
})
}),
)
describe(
@@ -180,7 +206,7 @@ describe("Scaffold", () => {
templates: ["non-existing-input"],
data: { value: "1" },
verbose: 0,
})
}),
).rejects.toThrow()
await expect(
@@ -190,12 +216,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(
@@ -222,7 +248,7 @@ describe("Scaffold", () => {
expect(() => readFileSync(join(process.cwd(), "output", "app_name.txt"))).toThrow()
})
})
}),
)
describe(
@@ -239,7 +265,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(
@@ -268,7 +294,7 @@ describe("Scaffold", () => {
expect(oneDeepFile.toString()).toEqual("Hello, my value is 1")
expect(twoDeepFile.toString()).toEqual("Hi! My value is actually NOT 1!")
})
})
}),
)
describe(
@@ -302,7 +328,7 @@ describe("Scaffold", () => {
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
})
}),
)
describe(
"date helpers",
@@ -329,19 +355,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",
@@ -366,7 +392,7 @@ describe("Scaffold", () => {
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
})
}),
)
describe(
"transform subfolder",
@@ -414,7 +440,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",
@@ -453,7 +479,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 () => {
@@ -471,6 +497,6 @@ describe("Scaffold", () => {
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
})
}),
)
})

View File

@@ -1,7 +1,8 @@
import { dateHelper, handlebarsParse, nowHelper } from "../src/utils"
import { ScaffoldConfig } from "../src/types"
import { dateHelper, handlebarsParse, nowHelper, parseAppendData } from "../src/utils"
import { ScaffoldCmdConfig, ScaffoldConfig } from "../src/types"
import path from "path"
import * as dateFns from "date-fns"
import { OptionsBase } from "massarg/types"
const blankConf: ScaffoldConfig = {
verbose: 0,
@@ -11,6 +12,20 @@ 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
@@ -24,7 +39,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"),
)
})
})
@@ -38,7 +53,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"),
)
})
})
@@ -49,8 +64,8 @@ describe("Utils", () => {
"/home/test/{{name}} \\{{escaped}}.txt",
{
isPath: false,
}
)
},
),
).toEqual(Buffer.from("/home/test/test {{escaped}}.txt"))
})
})
@@ -79,13 +94,31 @@ 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" })
})
})
})

View File

@@ -4,20 +4,13 @@
"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/*"]
}

27
typedoc.json Normal file
View File

@@ -0,0 +1,27 @@
{
"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
},
"customCss": "src/docs.css",
}

5514
yarn.lock

File diff suppressed because it is too large Load Diff