Compare commits

..

96 Commits

Author SHA1 Message Date
semantic-release-bot
87934fb3d1 chore(release): 1.7.0-develop.1 [skip ci]
## [1.7.0-develop.1](https://github.com/chenasraf/simple-scaffold/compare/v1.6.0...v1.7.0-develop.1) (2023-05-09)

### Features

* function config file ([02a8ba1](02a8ba16cd))

### Bug Fixes

* use path.normalize ([565090a](565090a951))
2023-05-09 21:15:15 +00:00
Chen Asraf
19b7ed5f06 build: fix build 2023-05-10 00:13:10 +03:00
Chen Asraf
c027e37d9a chore: cleanups 2023-05-10 00:10:21 +03:00
Chen Asraf
2251a9c727 chore: cleanups 2023-05-10 00:08:45 +03:00
Chen Asraf
02a8ba16cd feat: function config file 2023-05-09 22:51:40 +03:00
Chen Asraf
565090a951 fix: use path.normalize 2023-05-08 00:28:47 +03:00
Chen Asraf
943aad1564 docs: update docs 2023-05-06 02:23:03 +03:00
Chen Asraf
9fb4762c7b test: move scaffold.config.js 2023-05-06 02:22:42 +03:00
Chen Asraf
be92047d65 docs: update readme + author 2023-05-06 01:38:23 +03:00
semantic-release-bot
0940e843ac chore(release): 1.6.0 [skip ci]
## [1.6.0](https://github.com/chenasraf/simple-scaffold/compare/v1.5.0...v1.6.0) (2023-05-05)

### Features

* node.js function for remote configs ([ce5adbe](ce5adbe0f8))

### Bug Fixes

* move dependency to dev dependency ([d916d88](d916d88384))
2023-05-05 22:33:58 +00:00
Chen Asraf
a880bc9445 Merge pull request #56 from chenasraf/develop
Release 1.6.0
2023-05-06 01:32:25 +03:00
Chen Asraf
6c8eb02cbb build: fix package.json version update 2023-05-04 23:21:16 +03:00
Chen Asraf
08b048845f build: use pnpm instead of yarn 2023-05-04 23:00:24 +03:00
Chen Asraf
77e477e07f chore(release): bump version number 2023-05-04 22:16:48 +03:00
Chen Asraf
5ba6034b2f docs: fix changelog title 2023-05-04 22:12:35 +03:00
Chen Asraf
95dafdf839 docs: update docs 2023-05-04 22:08:39 +03:00
semantic-release-bot
4c3f1c97d1 chore(release): 1.6.0-develop.1 [skip ci]
## [1.6.0-develop.1](https://github.com/chenasraf/simple-scaffold/compare/v1.5.0...v1.6.0-develop.1) (2023-05-04)

### Features

* node.js function for remote configs ([ce5adbe](ce5adbe0f8))

### Bug Fixes

* move dependency to dev dependency ([d916d88](d916d88384))
2023-05-04 19:04:57 +00:00
Chen Asraf
1799971949 docs: update package description 2023-05-04 22:03:08 +03:00
semantic-release-bot
4bf96742b3 chore(release): 1.5.0-develop.3 [skip ci]
## [1.5.0-develop.3](https://github.com/chenasraf/simple-scaffold/compare/v1.5.0-develop.2...v1.5.0-develop.3) (2023-05-04)

### Features

* node.js function for remote configs ([7e9022f](7e9022f433))
2023-05-04 22:00:38 +03:00
Chen Asraf
ce5adbe0f8 feat: node.js function for remote configs 2023-05-04 22:00:38 +03:00
Chen Asraf
9489f14db2 build: separate test & build 2023-05-04 22:00:38 +03:00
Chen Asraf
99318f7654 build: remove unnecessary dependency 2023-05-04 22:00:38 +03:00
semantic-release-bot
81743f1fff chore(release): 1.5.0-develop.2 [skip ci]
## [1.5.0-develop.2](https://github.com/chenasraf/simple-scaffold/compare/v1.5.0-develop.1...v1.5.0-develop.2) (2023-05-03)

### Bug Fixes

* move dependency to dev dependency ([408a940](408a940853))
2023-05-04 22:00:38 +03:00
Chen Asraf
d916d88384 fix: move dependency to dev dependency 2023-05-04 22:00:07 +03:00
Chen Asraf
260ce6a8f3 docs: update docs 2023-05-04 22:00:07 +03:00
Chen Asraf
ba984b6621 docs: fix link 2023-05-04 22:00:07 +03:00
Chen Asraf
7b6260caf8 docs: update docs 2023-05-04 22:00:07 +03:00
Chen Asraf
2ca91a7109 docs: fix cli.md page 2023-05-04 22:00:07 +03:00
semantic-release-bot
7be79fdcaf chore(release): 1.5.0 [skip ci]
## [1.5.0](https://github.com/chenasraf/simple-scaffold/compare/v1.4.0...v1.5.0) (2023-05-02)

### Features

* add github remote templates ([f961c13](f961c13da1))
* support for remote template configs ([05487f4](05487f4d1e))
2023-05-02 07:28:17 +00:00
Chen Asraf
6afbcdc258 Merge pull request #55 from chenasraf/develop
v1.5.0
2023-05-02 10:27:11 +03:00
Chen Asraf
1a3fd3d610 docs: update help text 2023-05-02 10:23:50 +03:00
Chen Asraf
7f10db0d6e chore: bump version number 2023-05-02 10:16:47 +03:00
semantic-release-bot
93f5b4a004 chore(release): 1.5.0-develop.1 [skip ci]
## [1.5.0-develop.1](https://github.com/chenasraf/simple-scaffold/compare/v1.4.0...v1.5.0-develop.1) (2023-05-02)

### Features

* add github remote templates ([f961c13](f961c13da1))
* support for remote template configs ([05487f4](05487f4d1e))
2023-05-02 07:12:11 +00:00
Chen Asraf
b74411ddb8 docs: add docs for remote templates 2023-05-02 10:10:50 +03:00
Chen Asraf
f961c13da1 feat: add github remote templates 2023-05-02 09:46:53 +03:00
Chen Asraf
05487f4d1e feat: support for remote template configs 2023-05-02 09:33:54 +03:00
Chen Asraf
c50518a19c build: fix git/github step order 2023-04-29 17:17:15 +03:00
Chen Asraf
10ea6b4132 chore: fix package version 2023-04-29 17:11:18 +03:00
Chen Asraf
ce399181ab build: always build docs 2023-04-29 17:05:50 +03:00
semantic-release-bot
83d38073f3 chore(release): 1.4.0 [skip ci]
## [1.4.0](https://github.com/chenasraf/simple-scaffold/compare/v1.3.2...v1.4.0) (2023-04-28)

### Features

* add `--key` | `-k` to config loader ([6c5ba0b](6c5ba0bc91))
2023-04-28 23:01:48 +00:00
Chen Asraf
6c5ba0bc91 feat: add --key | -k to config loader 2023-04-29 01:59:39 +03:00
semantic-release-bot
2c4eccd800 chore(release): 1.3.2 [skip ci]
## [1.3.2](https://github.com/chenasraf/simple-scaffold/compare/v1.3.1...v1.3.2) (2023-04-28)

### Bug Fixes

* release build ([2c23fa9](2c23fa9dbb))
* release build asset ([0bef2df](0bef2df5f3))
2023-04-28 22:08:51 +00:00
Chen Asraf
2c23fa9dbb fix: release build 2023-04-29 01:04:01 +03:00
Chen Asraf
0bef2df5f3 fix: release build asset 2023-04-29 00:59:54 +03:00
semantic-release-bot
398a5d723b chore(release): 1.3.1 [skip ci]
## [1.3.1](https://github.com/chenasraf/simple-scaffold/compare/v1.3.0...v1.3.1) (2023-04-28)

### Bug Fixes

* docs ([6e19a86](6e19a86190))
* remove old peer-dep ([c7e2ef8](c7e2ef862c))
2023-04-28 21:50:36 +00:00
Chen Asraf
c7e2ef862c fix: remove old peer-dep 2023-04-29 00:44:49 +03:00
Chen Asraf
36dd27e480 docs: fix doc links 2023-04-29 00:44:49 +03:00
Chen Asraf
6e19a86190 fix: docs 2023-04-29 00:44:49 +03:00
Chen Asraf
eba7897f84 chore: bump version number + fix changelog 2023-04-29 00:44:38 +03:00
semantic-release-bot
dfdbca59a7 chore(release): 1.3.0 [skip ci]
## [1.3.0](https://github.com/chenasraf/simple-scaffold/compare/v1.2.0...v1.3.0) (2023-04-25)

### Features

* load scaffold config from files ([c398976](c3989769fe))

### Bug Fixes

* config option should not be mandatory ([3db6a91](3db6a918f1))
* export config file type ([4302eb5](4302eb5ce3))
2023-04-25 07:27:13 +00:00
Chen Asraf
1d6643b7eb docs: clean up css 2023-04-25 10:26:24 +03:00
Chen Asraf
9b86499a04 docs: use js for typedoc config 2023-04-25 10:26:24 +03:00
Chen Asraf
f41ebfb1d0 docs: reorganize file structure 2023-04-25 10:26:24 +03:00
Chen Asraf
873fa77c9a docs: add changelog to typedoc 2023-04-25 10:26:24 +03:00
Chen Asraf
1ce4a41a97 build: update changelog sections 2023-04-25 10:26:24 +03:00
Chen Asraf
4302eb5ce3 fix: export config file type 2023-04-25 10:26:24 +03:00
Chen Asraf
146086869f docs: move migration to pages, fix urls 2023-04-25 10:26:24 +03:00
Chen Asraf
8a2207bec5 docs: add table of contents 2023-04-25 10:26:24 +03:00
Chen Asraf
42568e0fa1 docs: remove unnecessary nested menus 2023-04-25 10:26:24 +03:00
Chen Asraf
a4498f9539 docs: split into files 2023-04-25 10:26:24 +03:00
Chen Asraf
5a2b187115 docs: update config doc 2023-04-25 10:26:24 +03:00
Chen Asraf
3db6a918f1 fix: config option should not be mandatory 2023-04-25 10:26:24 +03:00
Chen Asraf
c3989769fe feat: load scaffold config from files 2023-04-25 10:26:24 +03:00
Chen Asraf
b3f7912760 build: remove unnecessary yarn pack 2023-04-25 10:26:24 +03:00
Chen Asraf
1e0b73190c build: only generate docs on master 2023-04-25 10:26:24 +03:00
Chen Asraf
0c8e6e7573 docs: fix CHANGELOG.md 2023-04-25 10:26:24 +03:00
Chen Asraf
f28280e815 docs: update readme 2023-04-25 10:26:24 +03:00
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
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
41 changed files with 6624 additions and 4245 deletions

3
.github/FUNDING.yml vendored
View File

@@ -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

@@ -16,7 +16,7 @@ Steps to reproduce the behavior:
1. Prepare templates:
```txt
```text
This is my {{ template }}
```

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, '[publish]')"
- uses: Klemensas/action-autotag@stable
if: "contains(github.event.head_commit.message, '[publish]')"
id: update_tag
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
tag_prefix: "v"
- name: Publish on NPM
uses: JS-DevTools/npm-publish@v1
if: "contains(github.event.head_commit.message, '[publish]')"
with:
package: ./dist/package.json
token: "${{ secrets.NPM_TOKEN }}"
- name: Create Release
if: steps.update_tag.outputs.tagname && contains(github.event.head_commit.message, '[publish]')
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prerelease: true
tag_name: ${{ steps.update_tag.outputs.tagname }}
release_name: Release ${{ steps.update_tag.outputs.tagname }}
- name: Upload Release Asset
if: steps.update_tag.outputs.tagname && contains(github.event.head_commit.message, '[publish]')
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

View File

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

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,34 @@
name: Release
name: Test & Build
on:
push:
branches: [master]
branches: [ master, develop, feat/*, fix/* ]
jobs:
build:
test:
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
if: "!contains(github.event.head_commit.message, '[skip test]')"
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
needs: test
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "18.x"
- run: yarn install --frozen-lockfile
- run: yarn build
- run: cd ./dist && yarn pack --filename=../package.tgz
- run: yarn semantic-release
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:
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

1
.gitignore vendored
View File

@@ -65,3 +65,4 @@ dist/
.DS_Store
tmp/
docs/
.nvmrc

174
CHANGELOG.md Normal file
View File

@@ -0,0 +1,174 @@
# Change Log
## [1.7.0-develop.1](https://github.com/chenasraf/simple-scaffold/compare/v1.6.0...v1.7.0-develop.1) (2023-05-09)
### Features
* function config file ([02a8ba1](https://github.com/chenasraf/simple-scaffold/commit/02a8ba16cd6ee31806532845cb5ddbe0f5abf7de))
### Bug Fixes
* use path.normalize ([565090a](https://github.com/chenasraf/simple-scaffold/commit/565090a951e13dd222f2f802df717e7cb6ca0a73))
## [1.6.0](https://github.com/chenasraf/simple-scaffold/compare/v1.5.0...v1.6.0) (2023-05-05)
### Features
* node.js function for remote configs ([ce5adbe](https://github.com/chenasraf/simple-scaffold/commit/ce5adbe0f898a86db6046d7f66d83dfcaa519ad2))
### Bug Fixes
* move dependency to dev dependency ([d916d88](https://github.com/chenasraf/simple-scaffold/commit/d916d88384054e6c6b40e6299073f1d1acb4d29d))
## Change Log
# [1.5.0](https://github.com/chenasraf/simple-scaffold/compare/v1.6.0-develop.1...v1.5.0) (2023-05-04)
## [1.6.0-develop.1](https://github.com/chenasraf/simple-scaffold/compare/v1.5.0...v1.6.0-develop.1) (2023-05-04)
### Features
- node.js function for remote configs
([ce5adbe](https://github.com/chenasraf/simple-scaffold/commit/ce5adbe0f898a86db6046d7f66d83dfcaa519ad2))
### Bug Fixes
- move dependency to dev dependency
([d916d88](https://github.com/chenasraf/simple-scaffold/commit/d916d88384054e6c6b40e6299073f1d1acb4d29d))
## [1.5.0](https://github.com/chenasraf/simple-scaffold/compare/v1.5.0-develop.1...v1.5.0) (2023-05-02)
## [1.5.0-develop.1](https://github.com/chenasraf/simple-scaffold/compare/v1.4.0...v1.5.0-develop.1) (2023-05-02)
### Features
- add github remote templates
([f961c13](https://github.com/chenasraf/simple-scaffold/commit/f961c13da15320b42540773ed958cdc3f97e4502))
- support for remote template configs
([05487f4](https://github.com/chenasraf/simple-scaffold/commit/05487f4d1e3b05f1d695242bb54427ee2fbdf247))
## [1.4.0](https://github.com/chenasraf/simple-scaffold/compare/v1.3.2...v1.4.0) (2023-04-28)
### Features
- add `--key` | `-k` to config loader
([6c5ba0b](https://github.com/chenasraf/simple-scaffold/commit/6c5ba0bc916fb1d59240d2eaa1abedc74527a974))
## [1.3.2](https://github.com/chenasraf/simple-scaffold/compare/v1.3.1...v1.3.2) (2023-04-28)
### Bug Fixes
- release build
([2c23fa9](https://github.com/chenasraf/simple-scaffold/commit/2c23fa9dbb310cd0a31f09606798f96b95d66779))
- release build asset
([0bef2df](https://github.com/chenasraf/simple-scaffold/commit/0bef2df5f3aa800ad5f1094c0996108db9acce51))
## [1.3.1](https://github.com/chenasraf/simple-scaffold/compare/v1.3.0...v1.3.1) (2023-04-28)
### Bug Fixes
- docs
([6e19a86](https://github.com/chenasraf/simple-scaffold/commit/6e19a86190dd924058a48448aa6463569ef1125f))
- remove old peer-dep
([c7e2ef8](https://github.com/chenasraf/simple-scaffold/commit/c7e2ef862cb658feb1071ac120b185d8b34d6dd3))
## [1.3.0](https://github.com/chenasraf/simple-scaffold/compare/v1.2.0...v1.3.0) (2023-04-25)
### Features
- load scaffold config from files
([c398976](https://github.com/chenasraf/simple-scaffold/commit/c3989769fee445c9183ff5e5b3892c4e9fb66a9e))
### Bug Fixes
- config option should not be mandatory
([3db6a91](https://github.com/chenasraf/simple-scaffold/commit/3db6a918f13d9300efa2fcb4a356d004475ab91c))
- export config file type
([4302eb5](https://github.com/chenasraf/simple-scaffold/commit/4302eb5ce35ed6cf1dc80dfb92790c3fdd96f963))
## [1.2.0](https://github.com/chenasraf/simple-scaffold/compare/v1.1.4...v1.2.0) (2023-04-24)
### Features
- append-data cli flag
([3c5c2de](https://github.com/chenasraf/simple-scaffold/commit/3c5c2ded02f61ff086e81ea4a7f40529bdff1c9d))
### Bug Fixes
- ci node version
([767d34c](https://github.com/chenasraf/simple-scaffold/commit/767d34c684516d4cea865b25e87c27c779bb79ce))
- 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))
- 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.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)

439
README.md
View File

@@ -3,20 +3,25 @@
<h2 align="center">
[GitHub](https://github.com/chenasraf/simple-scaffold) |
[Documentation](https://casraf.dev/simple-scaffold) |
[Documentation](https://chenasraf.github.io/simple-scaffold) |
[NPM](https://npmjs.com/package/simple-scaffold) | [casraf.dev](https://casraf.dev)
![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)
![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.
Looking to streamline your workflow and get your projects up and running quickly? Look no further
than Simple Scaffold - the easy-to-use NPM package that simplifies the process of organizing and
copying your commonly-created files.
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.
With its agnostic and un-opinionated approach, Simple Scaffold can handle anything from a few simple
files to an entire app boilerplate setup. Plus, with the power of **Handlebars.js** syntax, you can
easily replace custom data and personalize your files to fit your exact needs. But that's not all -
you can also use it to loop through data, use conditions, and write custom functions using helpers.
Don't waste any more time manually copying and pasting files - let Simple Scaffold do the heavy
lifting for you and start building your projects faster and more efficiently today!
<div align="center">
@@ -24,380 +29,130 @@ name, or other custom data) inside the paths or contents of the files using Hand
</div>
<br />
<details>
<summary>Table of contents</summary>
- [Install](#install)
- [Command Line Interface (CLI)](#command-line-interface-cli)
- [Available flags](#available-flags)
- [Node module](#node-module)
- [Node-specific options](#node-specific-options)
- [Preparing files](#preparing-files)
- [Template files](#template-files)
- [Variable/token replacement](#variabletoken-replacement)
- [Helpers](#helpers)
- [Built-in Helpers](#built-in-helpers)
- [Capitalization Helpers](#capitalization-helpers)
- [Date helpers](#date-helpers)
- [Custom Helpers](#custom-helpers)
- [Examples](#examples)
- [Run](#run)
- [Command Example](#command-example)
- [Equivalent Node Module Example](#equivalent-node-module-example)
- [Files](#files)
- [Input](#input)
- [Output](#output)
- [Contributing](#contributing)
</details>
---
## Install
## Quick Start
You can either use it as a command line tool or import into your own code and run from there.
### Local Templates
```bash
# npm
npm install [-g] simple-scaffold
# yarn
yarn [global] add simple-scaffold
# run without installing
npx simple-scaffold@latest <...args>
```
The fastest way to get started is to use `npx` to immediately start a scaffold process.
## Command Line Interface (CLI)
Prepare any templates you want to use - for example, in the directory `templates/component`; and use
that in the CLI args. Here is a simple example file:
### Available flags
Simple Scaffold will maintain any file and directory structure you try to generate.
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`.
`templates/component/{{ pascalName name }}.tsx`
```text
Usage: simple-scaffold [options]
```tsx
// Created: {{ now 'yyyy-MM-dd' }}
import React from 'react'
Create structured files based on templates.
Options:
--help|-h Display help information
--name|-n Name to be passed to the generated files. {{name}}
and {{Name}} inside contents and file names will be
replaced accordingly.
--output|-o Path to output to. If --create-sub-folder is enabled,
the subfolder will be created inside this path.
(default: current dir)
--templates|-t Template files to use as input. You may provide
multiple files, each of which can be a relative or
absolute path, or a glob pattern for multiple file
matching easily.
--overwrite|-w Enable to override output files, even if they already
exist. (default: false)
--data|-d Add custom data to the templates. By default, only
your app name is included.
--create-sub-folder|-s Create subfolder with the input name (default: false)
--sub-folder-name-helper|-sh Default helper to apply to subfolder name when using
`--create-sub-folder true`.
--quiet|-q Suppress output logs (Same as --verbose 0)
(default: false)
--verbose|-v Determine amount of logs to display. The values are:
0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4
(error). The provided level will display messages of
the same level or higher. (default:
2)
--dry-run|-dr Don't emit files. This is good for testing your
scaffolds and making sure they don't fail, without
having to write actual file contents or create
directories. (default: false)
```
You can also add this as a script in your `package.json`:
```json
{
"scripts": {
"scaffold": "npx simple-scaffold@latest -t scaffolds/component/**/* -o src/components -d '{\"myProp\": \"propName\", \"myVal\": 123}'"
}
}
```
## 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.
The config takes similar arguments to the command line:
```typescript
import Scaffold from "simple-scaffold"
const config = {
name: "component",
templates: [path.join(__dirname, "scaffolds", "component")],
output: path.join(__dirname, "src", "components"),
createSubFolder: true,
subFolderNameHelper: "upperCase"
data: {
property: "value",
},
helpers: {
twice: (text) => [text, text].join(" ")
},
beforeWrite: (content, rawContent, outputPath) => content.toString().toUpperCase()
}
const scaffold = Scaffold(config)
```
### Node-specific options
In addition to all the options available in the command line, there are some Node/JS-specific
options available:
| Option | Type | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `output` | | In addition to being passed the same as CLI, it may also be passed a function for each input file to output into a dynamic path: `{ output: (fullPath, baseDir, baseName) => path.resolve(baseDir, baseName) }` |
| `helpers` | `Record<string, (string) => string>` | Helpers are simple functions that transform your `data` variables into other values. See [Helpers](#helpers) for the list of default helpers, or add your own to be loaded into the template parser. |
| `beforeWrite` | `(content: Buffer, rawContent: Buffer, outputPath: string) => Promise<String \| Buffer \| undefined> \| String \| Buffer \| undefined` | Supply this function to override the final output contents of each of your files, 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. |
## Preparing files
### Template files
Put your template files anywhere, and fill them with tokens for replacement.
Each template (not file) in the config array is parsed individually, and copied to the output
directory. If a single template path contains multiple files (e.g. if you use a folder path or a
glob pattern), the first directory up the tree of that template will become the base inside the
defined output path for that template, while copying files recursively and maintaining their
relative structure.
Examples:
> In the following examples, the config `name` is `AppName`, and the config `output` is `src`.
| Input template | Files in template | Output path(s) |
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| `./templates/{{ name }}.txt` | `./templates/{{ name }}.txt` | `src/AppName.txt` |
| `./templates/directory` | `outer/{{name}}.txt`,<br />`outer2/inner/{{name}}.txt` | `src/outer/AppName.txt`,<br />`src/outer2/inner/AppName.txt` |
| `./templates/others/**/*.txt` | `outer/{{name}}.jpg`,<br />`outer2/inner/{{name}}.txt` | `src/outer2/inner/AppName.txt` |
### Variable/token replacement
Scaffolding will replace `{{ varName }}` in both the file name and its contents and put the
transformed files in the output directory.
The data available for the template parser is the data you pass to the `data` config option (or
`--data` argument in CLI).
For example, using the following command:
```bash
npx simple-scaffold@latest \
--templates templates/components/{{name}}.jsx \
--output src/components \
--create-sub-folder true \
MyComponent
```
Will output a file with the path:
```text
<working_dir>/src/components/MyComponent.jsx
```
The contents of the file will be transformed in a similar fashion.
Your `data` will be pre-populated with the following:
- `{{Name}}`: PascalCase of the component name
- `{{name}}`: raw name of the component as you entered it
> Simple-Scaffold uses [Handlebars.js](https://handlebarsjs.com/) for outputting the file contents.
> Any `data` you add in the config will be available for use with their names wrapped in `{{` and
> `}}`. Other Handlebars built-ins such as `each`, `if` and `with` are also supported, see
> [Handlebars.js Language Features](https://handlebarsjs.com/guide/#language-features) for more
> information.
### Helpers
#### Built-in Helpers
Simple-Scaffold provides some built-in text transformation filters usable by Handlebars.
For example, you may use `{{ snakeCase name }}` inside a template file or filename, and it will
replace `My Name` with `my_name` when producing the final value.
##### Capitalization Helpers
| Helper name | Example code | Example output |
| ------------ | ----------------------- | -------------- |
| [None] | `{{ name }}` | my name |
| `camelCase` | `{{ camelCase name }}` | myName |
| `snakeCase` | `{{ snakeCase name }}` | my_name |
| `startCase` | `{{ startCase name }}` | My Name |
| `kebabCase` | `{{ kebabCase name }}` | my-name |
| `hyphenCase` | `{{ hyphenCase name }}` | my-name |
| `pascalCase` | `{{ pascalCase name }}` | MyName |
| `upperCase` | `{{ upperCase name }}` | MY NAME |
| `lowerCase` | `{{ lowerCase name }}` | my name |
##### Date helpers
| Helper name | Description | Example code | Example output |
| -------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------ |
| `now` | Current date with format | `{{ now "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
| `now` (with offset) | Current date with format, and with offset | `{{ now "yyyy-MM-dd HH:mm" -1 "hours" }}` | `2042-01-01 14:00` |
| `date` | Custom date with format | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
| `date` (with offset) | Custom date with format, and with offset | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" -1 "days" }}` | `2041-31-12 15:00` |
| `date` (with date from `--data`) | Custom date with format, with data from the `data` config option | `{{ date myCustomDate "yyyy-MM-dd HH:mm" }}` | `2042-01-01 12:00` |
Further details:
- We use [`date-fns`](https://date-fns.org/docs/) for parsing/manipulating the dates. If you want
more information on the date tokens to use, refer to
[their format documentation](https://date-fns.org/docs/format).
- The date helper format takes the following arguments:
```typescript
(
date: string,
format: string,
offsetAmount?: number,
offsetType?: "years" | "months" | "weeks" | "days" | "hours" | "minutes" | "seconds"
export default {{pascalCase name}}: React.FC = (props) => {
return (
<div className="{{camelCase name}}">{{pascalCase name}} Component</div>
)
```
- **The now helper** (for current time) takes the same arguments, minus the first one (`date`) as it
is implicitly the current date.
#### Custom Helpers
You may also add your own custom helpers using the `helpers` options when using the JS API (rather
than the CLI). The `helpers` option takes an object whose keys are helper names, and values are the
transformation functions. For example, `upperCase` is implemented like so:
```typescript
config.helpers = {
upperCase: (text) => text.toUpperCase(),
}
```
All of the above helpers (built in and custom) will also be available to you when using
`subFolderNameHelper` (`--sub-folder-name-helper`/`-sh`) as a possible value.
To generate the template output, run:
> To see more information on how helpers work and more features, see
> [Handlebars.js docs](https://handlebarsjs.com/guide/#custom-helpers).
## Examples
### Run
#### Command Example
```bash
simple-scaffold MyComponent \
-t project/scaffold/**/* \
-o src/components \
-d '{"className": "myClassName","author": "Chen Asraf"}'
MyComponent
```shell
# generate single component
$ npx simple-scaffold@latest \
-t templates/component -o src/components PageWrapper
```
#### Equivalent Node Module Example
This will immediately create the following file: `src/components/PageWrapper.tsx`
```typescript
import Scaffold from "simple-scaffold"
```tsx
// Created: 2077/01/01
import React from 'react'
async function main() {
await Scaffold({
name: "MyComponent",
templates: ["project/scaffold/**/*"],
output: ["src/components"],
export default PageWrapper: React.FC = (props) => {
return (
<div className="pageWrapper">PageWrapper Component</div>
)
}
```
### Configuration Files
You can also use a config file to more easily maintain all your scaffold definitions.
`scaffold.config.js`
```js
module.exports = {
// use "default" to avoid needing to specify key
// in this case the key is "component"
component: {
templates: ["templates/component"],
output: "src/components",
data: {
className: "myClassName",
author: "Chen Asraf",
// ...
},
})
console.log("Done.")
},
}
```
### Files
Then call your scaffold like this:
#### Input
```shell
$ npx simple-scaffold@latest -c scaffold.config.js PageWrapper
```
- Input file path:
This will allow you to avoid needing to remember which configs are needed or to store them in a
1-liner in `packqge.json` which can get pretty long and messy, which is harder to maintain.
```text
project → scaffold → {{Name}}.js → src → components
```
Also, this allows you to define more complex scaffolds with logic without having to use the Node.js
API directly. (Of course you always have the option to still do so if you wish)
- Input file contents:
See more at the [CLI documentation](https://chenasraf.github.io/simple-scaffold/pages/cli.html) and
[Configuration Files](https://chenasraf.github.io/simple-scaffold/pages/configuration_files.html).
```typescript
/**
* Author: {{ author }}
* Date: {{ now "yyyy-MM-dd" }}
*/
import React from 'react'
### Remote Configurations
export default {{camelCase name}}: React.FC = (props) => {
return (
<div className="{{className}}">{{camelCase name}} Component</div>
)
}
```
Another quick way to start is to re-use someone else's (or your own) work using a template
repository.
#### Output
A remote config can be loaded in one of these ways:
- Output file path:
- If it's on GitHub, you can use `-gh user/repository_name`
- If it's on another git server (such as GitLab), you can use
`-c https://example.com/user/repository_name.git`
- With `createSubFolder = false` (default):
Configurations can hold multiple scaffold groups. Each group can be accessed using its key by
supplying the `--key` or `-k` argument, or by appending a hash and then the key name, like so:
`-gh user/repository_name#key_name` - this also works for the `-c` flag.
```text
project → src → components → MyComponent.js
```
Here is an example for loading the example component templates in this very repository:
- With `createSubFolder = true`:
```shell
$ npx simple-scaffold@latest \
-gh chenasraf/simple-scaffold#scaffold.config.js:component \
PageWrapper
```text
project → src → components → MyComponent → MyComponent.js
```
# equivalent to:
$ npx simple-scaffold@latest \
-c https://github.com/chenasraf/simple-scaffold.git#scaffold.config.js:component \
PageWrapper
```
- With `createSubFolder = true` and `subFolderNameHelper = 'upperCase'`:
When template name (`:component`) is omitted, `default` is used.
```text
project → src → components → MYCOMPONENT → MyComponent.js
```
See more at the [CLI documentation](https://chenasraf.github.io/simple-scaffold/pages/cli.html) and
[Configuration Files](https://chenasraf.github.io/simple-scaffold/pages/configuration_files.html).
- Output file contents:
## Documentation
```typescript
/**
* Author: Chen Asraf
* Date: 2022-01-01
*/
import React from 'react'
See full documentation [here](https://chenasraf.github.io/simple-scaffold).
export default MyComponent: React.FC = (props) => {
return (
<div className="myClassName">MyComponent Component</div>
)
}
```
- [Command Line Interface (CLI) usage](https://chenasraf.github.io/simple-scaffold/pages/cli.html)
- [Node.js usage](https://chenasraf.github.io/simple-scaffold/pages/node.html)
- [Templates](https://chenasraf.github.io/simple-scaffold/pages/templates.html)
- [Configuration Files](https://chenasraf.github.io/simple-scaffold/pages/configuration_files.html)
- [Migrating v0.x to v1.x](https://chenasraf.github.io/simple-scaffold/pages/migration.html)
## Contributing

View File

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

View File

@@ -1,2 +0,0 @@
node_modules/
build/

View File

@@ -1,18 +0,0 @@
{
"name": "doc-theme",
"version": "1.0.0",
"keywords": [
"typedocplugin"
],
"scripts": {
"build": "tsc && yarn copy-files && yarn post-build",
"copy-files": "cp package.json build/",
"post-build": "rimraf node_modules"
},
"main": "build/index.js",
"devDependencies": {
"rimraf": "^3.0.2",
"typedoc": "^0.22.15",
"typescript": "^4.6.3"
}
}

View File

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

View File

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

View File

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

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"],
}

View File

@@ -1,10 +1,10 @@
{
"name": "simple-scaffold",
"version": "1.1.3",
"description": "A simple command to generate any file structure, from single components to entire app boilerplates.",
"homepage": "https://casraf.blog/simple-scaffold",
"version": "1.7.0-develop.1",
"description": "Generate any file structure - from single components to entire app boilerplates, with a single command.",
"homepage": "https://chenasraf.github.io/simple-scaffold",
"repository": "https://github.com/chenasraf/simple-scaffold.git",
"author": "Chen Asraf <inbox@casraf.com>",
"author": "Chen Asraf <contact@casraf.dev>",
"license": "MIT",
"main": "index.js",
"bin": "cmd.js",
@@ -24,37 +24,43 @@
"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-docs": "yarn build-docs-theme && yarn install --frozen-lockfile && typedoc",
"build-docs-theme": "cd doc-theme && yarn install && yarn build && cd ..",
"build-docs": "typedoc",
"watch-docs": "yarn typedoc --watch",
"audit-fix": "npm_config_yes=true npx yarn-audit-fix --flow=convert"
"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",
"util.promisify": "^1.1.1"
"massarg": "^1.0.7-pre.1"
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/jest": "^26.0.24",
"@knodes/typedoc-plugin-pages": "^0.23.4",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/release-notes-generator": "^10.0.3",
"@types/jest": "^29.5.1",
"@types/lodash": "^4.14.171",
"@types/mock-fs": "^4.13.1",
"@types/node": "^14.14.22",
"doc-theme": "file:./doc-theme",
"jest": "^27.0.6",
"mock-fs": "^5.0.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.3",
"ts-node": "^10.1.0",
"typedoc": "^0.22.15",
"typescript": "^4.3.5"
"@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",
"semantic-release-conventional-commits": "^3.0.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typedoc": "^0.24.6",
"typescript": "^5.0.4"
}
}

7
pages/README.md Normal file
View File

@@ -0,0 +1,7 @@
See full documentation [here](https://chenasraf.github.io/simple-scaffold).
- [Command Line Interface (CLI) usage](https://chenasraf.github.io/simple-scaffold/pages/cli.html)
- [Node.js usage](https://chenasraf.github.io/simple-scaffold/pages/node.html)
- [Templates](https://chenasraf.github.io/simple-scaffold/pages/templates.html)
- [Configuration Files](https://chenasraf.github.io/simple-scaffold/pages/configuration_files.html)
- [Migrating v0.x to v1.x](https://chenasraf.github.io/simple-scaffold/pages/migration.html)

79
pages/cli.md Normal file
View File

@@ -0,0 +1,79 @@
## Available flags
```text
Usage: simple-scaffold [options]
```
To see this and more information anytime, add the `-h` or `--help` flag to your call, e.g.
`npx simple-scaffold@latest -h`.
| Command \| alias | |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--help`\|`-h` | Display help information |
| `--name`\|`-n` | Name to be passed to the generated files. {{name}} and {{Name}} inside contents and file names will be replaced accordingly. |
| `--config`\|`-c` | Filename or HTTPS git URL to load config from instead of passing arguments to CLI or using a Node.js script. |
| `--github`\|`-gh` | GitHub path to load config from instead of passing arguments to CLI or using a Node.js script. |
| `--key`\|`-k` | Key to load inside the config file. This overwrites the config key provided after the colon in --config (e.g. --config scaffold.cmd.js:component) |
| `--output`\|`-o` | Path to output to. If --create-sub-folder is enabled, the subfolder will be created inside this path. (default: current dir) |
| `--templates`\|`-t` | Template files to use as input. You may provide multiple files, each of which can be a relative or absolute path, or a glob pattern for multiple file matching easily. |
| `--overwrite`\|`-w` | Enable to override output files, even if they already exist. (default: false) |
| `--data`\|`-d` | Add custom data to the templates. By default, only your app name is included. |
| `--append-data`\|`-D` | Append additional custom data to the templates, which will overwrite --data, using an alternate syntax, which is easier to use with CLI: -D key1=string -D key2:=raw |
| `--create-sub-folder`\|`-s` | Create subfolder with the input name (default: false) |
| `--sub-folder-name-helper`\|`-sh` | Default helper to apply to subfolder name when using `--create-sub-folder true`. |
| `--quiet`\|`-q` | Suppress output logs (Same as --verbose 0) (default: false) |
| `--verbose`\|`-v` | Determine amount of logs to display. The values are: 0 (none) \| 1 (debug) \| 2 (info) \| 3 (warn) \| 4 (error). The provided level will display messages of the same level or higher. (default: 2) |
| `--dry-run`\|`-dr` | Don't emit files. This is good for testing your scaffolds and making sure they don't fail, without having to write actual file contents or create directories. (default: false) |
## Examples:
> See
> [Configuration Files](https://chenasraf.github.io/simple-scaffold/pages/docs/configuration_files.md)
> for organizing multiple scaffold types into easy-to-maintain files
Usage with config file
```shell
$ simple-scaffold -c scaffold.cmd.js --key component
```
Usage with GitHub config file
```shell
$ simple-scaffold -gh chenasraf/simple-scaffold --key component
```
Usage with https git URL (for non-GitHub)
```shell
$ simple-scaffold -c \
https://example.com/user/template.git#scaffold.cmd.js --key component
```
Full syntax with config path and template key (applicable to all above methods)
```shell
$ simple-scaffold -c scaffold.cmd.js:component MyComponent
```
Excluded template key, assumes 'default' key
```shell
$ simple-scaffold -c scaffold.cmd.js MyComponent
```
Shortest syntax for GitHub, assumes file 'scaffold.cmd.js' and template key 'default'
```shell
$ simple-scaffold -gh chenasraf/simple-scaffold MyComponent
```
You can also add this as a script in your `package.json`:
```json
{
"scripts": {
"scaffold": "npx simple-scaffold@latest -t scaffolds/component/**/* -o src/components -d '{\"myProp\": \"propName\", \"myVal\": 123}'"
}
}
```

View File

@@ -0,0 +1,155 @@
If you want to have reusable configurations which are complex and don't fit into command lines
easily, or just want to manage your templates easier, you can use configuration files to load your
scaffolding configurations.
## Creating config files
Configuration files should be valid `.js`/`.json` files that contain valid Scaffold configurations.
Each file hold multiple scaffolds. Each scaffold is a key, and its value is the configuration. For
example:
```json
{
"component": {
"templates": ["templates/component"],
"output": "src/components"
}
}
```
The configuration contents are identical to the
[Node.js configuration structure](https://chenasraf.github.io/simple-scaffold/pages/node.md):
```ts
interface ScaffoldConfig {
name: string
templates: string[]
output: FileResponse<string>
createSubFolder?: boolean
data?: Record<string, any>
overwrite?: FileResponse<boolean>
quiet?: boolean
verbose?: LogLevel
dryRun?: boolean
helpers?: Record<string, Helper>
subFolderNameHelper?: DefaultHelpers | string
beforeWrite?(
content: Buffer,
rawContent: Buffer,
outputPath: string,
): string | Buffer | undefined | Promise<string | Buffer | undefined>
}
```
If you want to supply functions inside the configurations, you must use a `.js` file as JSON does
not support non-primitives.
A `.js` file is just like a `.json` file, make sure to export the final configuration:
```js
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
module.exports = {
component: {
templates: ["templates/component"],
output: "src/components",
},
}
```
## Using a config file
Once your config is created, you can use it by providing the file name to the `--config` (or `-c`
for brevity), optionally followed by a colon, then your scaffold config name.
```shell
simple-scaffold -c <file>[:<template_key>]
```
For example:
```shell
simple-scaffold -c scaffold.json:component MyComponentName
```
If you don't want to supply a template/config name (e.g. `component`), you can omit the colon and
the name, and it will use the configuration named `default`:
```js
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
module.exports = {
default: {
// ...
},
}
```
And then:
```shell
# will use 'default' template
simple-scaffold -c scaffold.json MyComponentName
```
## Remote Templates
You can load template groups remotely, similar to how you would pass a config normally.
The main difference is the templates will be hosted on a remote location such as a git server, and
not locally in your project. This can be done to easily share & reuse templates.
When passing a git URL to `--config`, you will clone that repo and use the files there as template.
The syntax is as follows:
```shell
simple-scaffold -c <git_url>[#<git_file>][:<template_key>]
```
For example, to use this repository's example as base:
```shell
simple-scaffold -c https://github.com/chenasraf/simple-scaffold.git#examples/test-input/scaffold.config.js:component
```
When the filename is omitted, `/scaffold.config.js` will be used as default.
When the template_key is ommitted, `default` will be used as default.
### GitHub Templates
As a shorter alternative to the above example, you can use `--github` or `-gh` to reference a GitHub
URL without specifying the whole path.
The syntax is as follows:
```shell
simple-scaffold -gh <username>/<project_name>[#<git_file>][:<template_key>]
```
This example is equivalent to the above, just shorter to write:
```shell
simple-scaffold -c chenasraf/simple-scaffold#examples/test-input/scaffold.config.js:component
```
## Use In Node.js
You can also start a scaffold from Node.js with a remote file or URL config.
Just use the `Scaffold.fromConfig` function:
```ts
Scaffold.fromConfig(
"scaffold.config.js", // file or HTTPS git URL
{
// name of the generated component
name: "My Component",
// key to load from the config
key: "component",
},
{
// other config overrides
},
)
```

View File

@@ -1,5 +1,3 @@
# 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:
@@ -13,8 +11,10 @@ between versions. With these notable exceptions:
- `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.
- Additional options have been added to both CLI and Node interfaces. See
[Command Line Interface (CLI) usage](https://chenasraf.github.io/simple-scaffold/pages/cli.html)
and [Node.js usage](https://chenasraf.github.io/simple-scaffold/pages/node.html) for more
information.
## Template syntax changes

53
pages/node.md Normal file
View File

@@ -0,0 +1,53 @@
You can build the scaffold yourself, if you want to create more complex arguments, scaffold groups,
etc - simply pass a config object to the Scaffold function when you are ready to start.
The config takes similar arguments to the command line. The full type definitions can be found in
[src/types.ts](https://github.com/chenasraf/simple-scaffold/blob/develop/src/types.ts#L13).
See the full
[documentation](https://chenasraf.github.io/simple-scaffold/interfaces/ScaffoldConfig.html) for the
configuration options and their behavior.
```ts
interface ScaffoldConfig {
name: string
templates: string[]
output: FileResponse<string>
createSubFolder?: boolean
data?: Record<string, any>
overwrite?: FileResponse<boolean>
quiet?: boolean
verbose?: LogLevel
dryRun?: boolean
helpers?: Record<string, Helper>
subFolderNameHelper?: DefaultHelpers | string
beforeWrite?(
content: Buffer,
rawContent: Buffer,
outputPath: string,
): string | Buffer | undefined | Promise<string | Buffer | undefined>
}
```
This is an example of loading a complete scaffold via Node.js:
```typescript
import Scaffold from "simple-scaffold"
const config = {
name: "component",
templates: [path.join(__dirname, "scaffolds", "component")],
output: path.join(__dirname, "src", "components"),
createSubFolder: true,
subFolderNameHelper: "upperCase"
data: {
property: "value",
},
helpers: {
twice: (text) => [text, text].join(" ")
},
beforeWrite: (content, rawContent, outputPath) => content.toString().toUpperCase()
}
const scaffold = Scaffold(config)
```

224
pages/templates.md Normal file
View File

@@ -0,0 +1,224 @@
# Preparing template files
Put your template files anywhere, and fill them with tokens for replacement.
Each template (not file) in the config array is parsed individually, and copied to the output
directory. If a single template path contains multiple files (e.g. if you use a folder path or a
glob pattern), the first directory up the tree of that template will become the base inside the
defined output path for that template, while copying files recursively and maintaining their
relative structure.
Examples:
> In the following examples, the config `name` is `AppName`, and the config `output` is `src`.
| Input template | Files in template | Output path(s) |
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| `./templates/{{ name }}.txt` | `./templates/{{ name }}.txt` | `src/AppName.txt` |
| `./templates/directory` | `outer/{{name}}.txt`,<br />`outer2/inner/{{name}}.txt` | `src/outer/AppName.txt`,<br />`src/outer2/inner/AppName.txt` |
| `./templates/others/**/*.txt` | `outer/{{name}}.jpg`,<br />`outer2/inner/{{name}}.txt` | `src/outer2/inner/AppName.txt` |
## Variable/token replacement
Scaffolding will replace `{{ varName }}` in both the file name and its contents and put the
transformed files in the output directory.
The data available for the template parser is the data you pass to the `data` config option (or
`--data` argument in CLI).
For example, using the following command:
```bash
npx simple-scaffold@latest \
--templates templates/components/{{name}}.jsx \
--output src/components \
--create-sub-folder true \
MyComponent
```
Will output a file with the path:
```text
<working_dir>/src/components/MyComponent.jsx
```
The contents of the file will be transformed in a similar fashion.
Your `data` will be pre-populated with the following:
- `{{Name}}`: PascalCase of the component name
- `{{name}}`: raw name of the component as you entered it
> Simple-Scaffold uses [Handlebars.js](https://handlebarsjs.com/) for outputting the file contents.
> Any `data` you add in the config will be available for use with their names wrapped in `{{` and
> `}}`. Other Handlebars built-ins such as `each`, `if` and `with` are also supported, see
> [Handlebars.js Language Features](https://handlebarsjs.com/guide/#language-features) for more
> information.
## Helpers
### Built-in Helpers
Simple-Scaffold provides some built-in text transformation filters usable by Handlebars.
For example, you may use `{{ snakeCase name }}` inside a template file or filename, and it will
replace `My Name` with `my_name` when producing the final value.
#### Capitalization Helpers
| Helper name | Example code | Example output |
| ------------ | ----------------------- | -------------- |
| [None] | `{{ name }}` | my name |
| `camelCase` | `{{ camelCase name }}` | myName |
| `snakeCase` | `{{ snakeCase name }}` | my_name |
| `startCase` | `{{ startCase name }}` | My Name |
| `kebabCase` | `{{ kebabCase name }}` | my-name |
| `hyphenCase` | `{{ hyphenCase name }}` | my-name |
| `pascalCase` | `{{ pascalCase name }}` | MyName |
| `upperCase` | `{{ upperCase name }}` | MY NAME |
| `lowerCase` | `{{ lowerCase name }}` | my name |
#### Date helpers
| Helper name | Description | Example code | Example output |
| -------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------ |
| `now` | Current date with format | `{{ now "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
| `now` (with offset) | Current date with format, and with offset | `{{ now "yyyy-MM-dd HH:mm" -1 "hours" }}` | `2042-01-01 14:00` |
| `date` | Custom date with format | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
| `date` (with offset) | Custom date with format, and with offset | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" -1 "days" }}` | `2041-31-12 15:00` |
| `date` (with date from `--data`) | Custom date with format, with data from the `data` config option | `{{ date myCustomDate "yyyy-MM-dd HH:mm" }}` | `2042-01-01 12:00` |
Further details:
- We use [`date-fns`](https://date-fns.org/docs/) for parsing/manipulating the dates. If you want
more information on the date tokens to use, refer to
[their format documentation](https://date-fns.org/docs/format).
- The date helper format takes the following arguments:
```typescript
(
date: string,
format: string,
offsetAmount?: number,
offsetType?: "years" | "months" | "weeks" | "days" | "hours" | "minutes" | "seconds"
)
```
- **The now helper** (for current time) takes the same arguments, minus the first one (`date`) as it
is implicitly the current date.
### Custom Helpers
You may also add your own custom helpers using the `helpers` options when using the JS API (rather
than the CLI). The `helpers` option takes an object whose keys are helper names, and values are the
transformation functions. For example, `upperCase` is implemented like so:
```typescript
config.helpers = {
upperCase: (text) => text.toUpperCase(),
}
```
All of the above helpers (built in and custom) will also be available to you when using
`subFolderNameHelper` (`--sub-folder-name-helper`/`-sh`) as a possible value.
> To see more information on how helpers work and more features, see
> [Handlebars.js docs](https://handlebarsjs.com/guide/#custom-helpers).
# Examples
## Run
### Command Example
```bash
simple-scaffold MyComponent \
-t project/scaffold/**/* \
-o src/components \
-d '{"className": "myClassName","author": "Chen Asraf"}'
MyComponent
```
### Equivalent Node Module Example
```typescript
import Scaffold from "simple-scaffold"
async function main() {
await Scaffold({
name: "MyComponent",
templates: ["project/scaffold/**/*"],
output: ["src/components"],
data: {
className: "myClassName",
author: "Chen Asraf",
},
})
console.log("Done.")
}
```
## Files
### Input
- Input file path:
```text
project → scaffold → {{Name}}.js → src → components
```
- Input file contents:
```typescript
/**
* Author: {{ author }}
* Date: {{ now "yyyy-MM-dd" }}
*/
import React from 'react'
export default {{camelCase name}}: React.FC = (props) => {
return (
<div className="{{className}}">{{camelCase name}} Component</div>
)
}
```
### Output
- Output file path:
- With `createSubFolder = false` (default):
```text
project → src → components → MyComponent.js
```
- With `createSubFolder = true`:
```text
project → src → components → MyComponent → MyComponent.js
```
- With `createSubFolder = true` and `subFolderNameHelper = 'upperCase'`:
```text
project → src → components → MYCOMPONENT → MyComponent.js
```
- Output file contents:
```typescript
/**
* Author: Chen Asraf
* Date: 2077-01-01
*/
import React from 'react'
export default MyComponent: React.FC = (props) => {
return (
<div className="myClassName">MyComponent Component</div>
)
}
```

4764
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

83
release.config.js Normal file
View File

@@ -0,0 +1,83 @@
const releaseRules = [
{ type: "feat", section: "Features", release: "minor" },
{ type: "docs", section: "Build", release: false },
{ type: "fix", section: "Bug Fixes", release: "patch" },
{ type: "refactor", section: "Misc", release: "patch" },
{ type: "perf", section: "Misc", release: "patch" },
{ type: "build", section: "Build", release: "patch" },
{ type: "chore", section: "Misc", release: "patch" },
{ type: "test", section: "Tests", release: false },
]
/** @type {import('semantic-release').Options} */
module.exports = {
branches: [
"+([0-9])?(.{+([0-9]),x}).x",
"master",
"next",
"next-major",
{ name: "develop", prerelease: true },
{ name: "beta", prerelease: true },
{ name: "alpha", prerelease: true },
],
analyzeCommits: {
path: "semantic-release-conventional-commits",
majorTypes: ["major", "breaking"],
minorTypes: ["minor", "feat", "feature"],
patchTypes: ["patch", "fix", "bugfix", "refactor", "perf", "revert"],
},
plugins: [
[
"@semantic-release/commit-analyzer",
{
preset: "conventionalcommits",
parserOpts: {
noteKeywords: ["breaking:", "breaking-fix:", "breaking-feat:"],
},
releaseRules: releaseRules,
},
],
[
"@semantic-release/release-notes-generator",
{
preset: "conventionalcommits",
parserOpts: {
noteKeywords: ["breaking", "major"],
types: releaseRules,
},
},
],
[
"@semantic-release/changelog",
{
changelogFile: "CHANGELOG.md",
changelogTitle: "# Change Log",
},
],
[
"@semantic-release/npm",
{
npmPublish: false,
},
],
[
"@semantic-release/npm",
{
npmPublish: true,
pkgRoot: "dist",
},
],
[
"@semantic-release/git",
{
assets: ["CHANGELOG.md", "package.json"],
},
],
[
"@semantic-release/github",
{
assets: ["package.tgz"],
},
],
],
}

13
scaffold.config.js Normal file
View File

@@ -0,0 +1,13 @@
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
module.exports = {
default: {
templates: ["examples/test-input/Component"],
output: "examples/test-output",
data: { property: "myProp", value: "10" },
},
component: {
templates: ["examples/test-input/Component"],
output: "examples/test-output/component",
data: { property: "myProp", value: "10" },
},
}

View File

@@ -5,13 +5,18 @@ import { LogLevel, ScaffoldCmdConfig } from "./types"
import { Scaffold } from "./scaffold"
import path from "path"
import fs from "fs/promises"
import { parseAppendData, parseConfig } from "./config"
export async function parseCliArgs(args = process.argv.slice(2)) {
const pkg = JSON.parse((await fs.readFile(path.join(__dirname, "package.json"))).toString())
const isConfig = args.includes("--config") || args.includes("-c") || args.includes("--github") || args.includes("-gh")
return (
massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
.main(Scaffold)
massarg<ScaffoldCmdConfig>()
.main(async (config) => {
const _config = await parseConfig(config)
return Scaffold(_config)
})
.option({
name: "name",
aliases: ["n"],
@@ -20,11 +25,29 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
isDefault: true,
required: true,
})
.option({
name: "config",
aliases: ["c"],
description:
"Filename or https git URL to load config from instead of passing arguments to CLI or using a Node.js script. See examples for syntax.",
})
.option({
name: "github",
aliases: ["gh"],
description:
"GitHub path to load config from instead of passing arguments to CLI or using a Node.js script. See examples for syntax.",
})
.option({
name: "key",
aliases: ["k"],
description:
"Key to load inside the config file. This overwrites the config key provided after the colon in --config (e.g. --config scaffold.cmd.js:component)",
})
.option({
name: "output",
aliases: ["o"],
description: `Path to output to. If --create-sub-folder is enabled, the subfolder will be created inside this path. ${chalk.reset`${chalk.white`(default: current dir)`}`}`,
required: true,
required: !isConfig,
})
.option({
name: "templates",
@@ -33,7 +56,7 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
description:
"Template files to use as input. You may provide multiple files, each of which can be a relative or absolute path, " +
"or a glob pattern for multiple file matching easily.",
required: true,
required: !isConfig,
})
.option({
name: "overwrite",
@@ -48,6 +71,13 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
description: "Add custom data to the templates. By default, only your app name is included.",
parse: (v) => JSON.parse(v),
})
.option({
name: "append-data",
aliases: ["D"],
description:
"Append additional custom data to the templates, which will overwrite --data, using an alternate syntax, which is easier to use with CLI: -D key1=string -D key2:=raw",
parse: parseAppendData,
})
.option({
name: "create-sub-folder",
aliases: ["s"],
@@ -91,16 +121,41 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
// description: "Usage",
// output: "",
// })
.example({
description: "Usage with config file",
input: "simple-scaffold -c scaffold.cmd.js --key component",
})
.example({
description: "Usage with GitHub config file",
input: "simple-scaffold -gh chenasraf/simple-scaffold --key component",
})
.example({
description: "Usage with https git URL (for non-GitHub)",
input: "simple-scaffold -c https://example.com/user/template.git#scaffold.cmd.js --key component",
})
.example({
description: "Full syntax with config path and template key (applicable to all above methods)",
input: "simple-scaffold -c scaffold.cmd.js:component MyComponent",
})
.example({
description: "Excluded template key, assumes 'default' key",
input: "simple-scaffold -c scaffold.cmd.js MyComponent",
})
.example({
description: "Shortest syntax for GitHub, assumes file 'scaffold.cmd.js' and template key 'default'",
input: "simple-scaffold -gh chenasraf/simple-scaffold MyComponent",
})
.help({
binName: "simple-scaffold",
useGlobalColumns: true,
usageExample: "[options]",
printWidth: 100,
header: [`Create structured files based on templates.`].join("\n"),
footer: [
`Version: ${pkg.version}`,
`Copyright © Chen Asraf 2017-${new Date().getFullYear()}`,
``,
`Documentation: ${chalk.underline`https://casraf.blog/simple-scaffold`}`,
`Documentation: ${chalk.underline`https://chenasraf.github.io/simple-scaffold`}`,
`NPM: ${chalk.underline`https://npmjs.com/package/simple-scaffold`}`,
`GitHub: ${chalk.underline`https://github.com/chenasraf/simple-scaffold`}`,
].join("\n"),

168
src/config.ts Normal file
View File

@@ -0,0 +1,168 @@
import path from "path"
import {
AsyncResolver,
ConfigLoadConfig,
FileResponse,
FileResponseHandler,
LogConfig,
LogLevel,
Resolver,
ScaffoldCmdConfig,
ScaffoldConfig,
ScaffoldConfigFile,
ScaffoldConfigMap,
} from "./types"
import { OptionsBase } from "massarg/types"
import { spawn } from "node:child_process"
import os from "node:os"
import { handlebarsParse } from "./parser"
import { log } from "./logger"
import { resolve } from "./utils"
export function getOptionValueForFile<T>(
config: ScaffoldConfig,
filePath: string,
fn: FileResponse<T>,
defaultValue?: T,
): T {
if (typeof fn !== "function") {
return defaultValue ?? (fn as T)
}
return (fn as FileResponseHandler<T>)(
filePath,
path.dirname(handlebarsParse(config, filePath, { isPath: true }).toString()),
path.basename(handlebarsParse(config, filePath, { isPath: true }).toString()),
)
}
export function parseAppendData(value: string, options: ScaffoldCmdConfig & OptionsBase): unknown {
const data = options.data ?? {}
const [key, val] = value.split(/\:?=/)
// raw
if (value.includes(":=") && !val.includes(":=")) {
return { ...data, [key]: JSON.parse(val) }
}
return { ...data, [key]: isWrappedWithQuotes(val) ? val.substring(1, val.length - 1) : val }
}
function isWrappedWithQuotes(string: string): boolean {
return (string.startsWith('"') && string.endsWith('"')) || (string.startsWith("'") && string.endsWith("'"))
}
/** @internal */
export async function parseConfig(config: ScaffoldCmdConfig & OptionsBase): Promise<ScaffoldConfig> {
let c: ScaffoldConfig = config
if (config.github) {
log(config, LogLevel.Info, `Loading config from github ${config.github}`)
const gitUrl = new URL(`https://github.com/${config.github}`)
if (!gitUrl.pathname.endsWith(".git")) {
gitUrl.pathname += ".git"
}
config.config = gitUrl.toString()
}
if (config.config) {
const isUrl = config.config.includes("://")
const hasColonToken = (!isUrl && config.config.includes(":")) || (isUrl && count(config.config, ":") > 1)
const colonIndex = config.config.lastIndexOf(":")
const [configFile, templateKey = "default"] = hasColonToken
? [config.config.substring(0, colonIndex), config.config.substring(colonIndex + 1)]
: [config.config, undefined]
const key = (config.key ?? templateKey) || "default"
log(config, LogLevel.Info, `Loading config from ${configFile} with key ${key}`)
const configPromise = await getConfig({ config: configFile, quiet: config.quiet, verbose: config.verbose })
const configImport = await resolve(configPromise, config)
if (!configImport[key]) {
throw new Error(`Template "${key}" not found in ${configFile}`)
}
c = {
...config,
...configImport[key],
data: {
...configImport[key].data,
...config.data,
},
}
}
c.data = { ...c.data, ...config.appendData }
delete config.appendData
return c
}
function wrapNoopResolver<T, R = T>(value: Resolver<T, R>): Resolver<T, R> {
if (typeof value === "function") {
return value
}
return (_) => value
}
/** @internal */
export async function getConfig(config: ConfigLoadConfig): Promise<ScaffoldConfigFile> {
const { config: configFile, ...logConfig } = config as Required<typeof config>
const url = new URL(configFile)
if (url.protocol === "file:") {
log(logConfig, LogLevel.Info, `Loading config from file ${configFile}`)
const absolutePath = path.resolve(process.cwd(), configFile)
return wrapNoopResolver(import(absolutePath))
}
const isHttp = url.protocol === "http:" || url.protocol === "https:"
const isGit = url.protocol === "git:" || (isHttp && url.pathname.endsWith(".git"))
if (isHttp || isGit) {
if (isGit) {
return getGitConfig(url, logConfig)
}
throw new Error(`Unsupported protocol ${url.protocol}`)
}
return wrapNoopResolver(import(path.resolve(process.cwd(), configFile)))
}
async function getGitConfig(
url: URL,
logConfig: LogConfig,
): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
const repoUrl = `${url.protocol}//${url.host}${url.pathname}`
log(logConfig, LogLevel.Info, `Cloning git repo ${repoUrl}`)
const tmpPath = path.resolve(os.tmpdir(), `scaffold-config-${Date.now()}`)
return new Promise((resolve, reject) => {
const clone = spawn("git", ["clone", "--depth", "1", repoUrl, tmpPath])
clone.on("error", reject)
clone.on("close", async (code) => {
if (code === 0) {
log(logConfig, LogLevel.Info, `Loading config from git repo: ${repoUrl}`)
const hashPath = url.hash?.replace("#", "") || "scaffold.config.js"
const absolutePath = path.resolve(tmpPath, hashPath)
const loadedConfig = (await import(absolutePath)).default as ScaffoldConfigMap
log(logConfig, LogLevel.Info, `Loaded config from git`)
log(logConfig, LogLevel.Debug, `Raw config:`, loadedConfig)
const fixedConfig: ScaffoldConfigMap = Object.fromEntries(
Object.entries(loadedConfig).map(([k, v]) => [
k,
// use absolute paths for template as config is necessarily in another directory
{ ...v, templates: v.templates.map((t) => path.resolve(tmpPath, t)) },
]),
)
resolve(wrapNoopResolver(fixedConfig))
} else {
reject(new Error(`Git clone failed with code ${code}`))
}
})
})
}
function count(string: string, substring: string): number {
return string.split(substring).length - 1
}

55
src/docs.css Normal file
View File

@@ -0,0 +1,55 @@
.tsd-typography table {
border-collapse: collapse;
width: 100%;
}
.tsd-typography table td,
.tsd-typography table th {
vertical-align: top;
border: 1px solid var(--color-accent);
padding: 6px;
}
.tsd-typography h1 + pre,
.tsd-typography h2 + pre,
.tsd-typography h3 + pre,
.tsd-typography h4 + pre,
.tsd-typography h5 + pre,
.tsd-typography h6 + pre,
/* */
.tsd-typography h1 + table,
.tsd-typography h2 + table,
.tsd-typography h3 + table,
.tsd-typography h4 + table,
.tsd-typography h5 + table,
.tsd-typography h6 + table {
margin-top: 1em;
}
.tsd-typography pre + a + h1,
.tsd-typography pre + a + h2,
.tsd-typography pre + a + h3,
.tsd-typography pre + a + h4,
.tsd-typography pre + a + h5,
.tsd-typography pre + a + h6,
/* */
.tsd-typography table + a + h1,
.tsd-typography table + a + h2,
.tsd-typography table + a + h3,
.tsd-typography table + a + h4,
.tsd-typography table + a + h5,
.tsd-typography table + a + h6 {
margin-top: 2em;
}
.tsd-index-accordion[data-key*="Configuration."] ul.tsd-nested-navigation,
.tsd-index-accordion[data-key*="Configuration."] .tsd-accordion-summary > svg,
.tsd-index-accordion[data-key="Changelog"] ul.tsd-nested-navigation,
.tsd-index-accordion[data-key="Changelog"] .tsd-accordion-summary > svg,
.tsd-index-accordion[data-key="Configuration"] li:nth-child(n + 6) {
display: none;
}
.tsd-index-accordion[data-key*="Configuration."],
.tsd-index-accordion[data-key="Changelog"] {
margin-left: 0;
}

207
src/file.ts Normal file
View File

@@ -0,0 +1,207 @@
import path from "path"
import { F_OK } from "constants"
import { LogLevel, ScaffoldConfig } from "./types"
import { promises as fsPromises } from "fs"
const { stat, access, mkdir } = fsPromises
import { glob, hasMagic } from "glob"
import { log } from "./logger"
import { getOptionValueForFile } from "./config"
import { handlebarsParse } from "./parser"
import { handleErr } from "./utils"
const { readFile, writeFile } = fsPromises
export async function createDirIfNotExists(dir: string, config: ScaffoldConfig): Promise<void> {
const parentDir = path.dirname(dir)
if (!(await pathExists(parentDir))) {
await createDirIfNotExists(parentDir, config)
}
if (!(await pathExists(dir))) {
try {
log(config, LogLevel.Debug, `Creating dir ${dir}`)
await mkdir(dir)
return
} catch (e: any) {
if (e.code !== "EEXIST") {
throw e
}
return
}
}
}
export async function pathExists(filePath: string): Promise<boolean> {
try {
await access(filePath, F_OK)
return true
} catch (e: any) {
if (e.code === "ENOENT") {
return false
}
throw e
}
}
export async function isDir(path: string): Promise<boolean> {
const tplStat = await stat(path)
return tplStat.isDirectory()
}
export function removeGlob(template: string): string {
return template.replace(/\*/g, "").replace(/(\/\/|\\\\)/g, path.sep)
}
export function makeRelativePath(str: string): string {
return str.startsWith(path.sep) ? str.slice(1) : str
}
export function getBasePath(relPath: string): string {
return path
.resolve(process.cwd(), relPath)
.replace(process.cwd() + path.sep, "")
.replace(process.cwd(), "")
}
export async function getFileList(_config: ScaffoldConfig, template: string): Promise<string[]> {
return (
await glob(template, {
dot: true,
nodir: true,
// debug: config.verbose === LogLevel.Debug,
})
).map(path.normalize)
}
export interface GlobInfo {
nonGlobTemplate: string
origTemplate: string
isDirOrGlob: boolean
isGlob: boolean
template: string
}
export async function getTemplateGlobInfo(config: ScaffoldConfig, template: string): Promise<GlobInfo> {
const isGlob = hasMagic(template)
log(config, LogLevel.Debug, "before isDir", "isGlob:", isGlob, template)
let _template = template
let nonGlobTemplate = isGlob ? removeGlob(template) : template
nonGlobTemplate = path.normalize(nonGlobTemplate)
const isDirOrGlob = isGlob ? true : await isDir(template)
log(config, LogLevel.Debug, "after isDir", isDirOrGlob)
const _shouldAddGlob = !isGlob && isDirOrGlob
const origTemplate = template
if (_shouldAddGlob) {
_template = path.join(template, "**", "*")
}
return { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template: _template }
}
export interface OutputFileInfo {
inputPath: string
outputPathOpt: string
outputDir: string
outputPath: string
exists: boolean
}
export async function getTemplateFileInfo(
config: ScaffoldConfig,
{ templatePath, basePath }: { templatePath: string; basePath: string },
): Promise<OutputFileInfo> {
const inputPath = path.resolve(process.cwd(), templatePath)
const outputPathOpt = getOptionValueForFile(config, inputPath, config.output)
const outputDir = getOutputDir(config, outputPathOpt, basePath)
const outputPath = handlebarsParse(config, path.join(outputDir, path.basename(inputPath)), {
isPath: true,
}).toString()
const exists = await pathExists(outputPath)
return { inputPath, outputPathOpt, outputDir, outputPath, exists }
}
export async function copyFileTransformed(
config: ScaffoldConfig,
{
exists,
overwrite,
outputPath,
inputPath,
}: {
exists: boolean
overwrite: boolean
outputPath: string
inputPath: string
},
): Promise<void> {
if (!exists || overwrite) {
if (exists && overwrite) {
log(config, LogLevel.Info, `File ${outputPath} exists, overwriting`)
}
const templateBuffer = await readFile(inputPath)
const unprocessedOutputContents = handlebarsParse(config, templateBuffer)
const finalOutputContents =
(await config.beforeWrite?.(unprocessedOutputContents, templateBuffer, outputPath)) ?? unprocessedOutputContents
if (!config.dryRun) {
await writeFile(outputPath, finalOutputContents)
log(config, LogLevel.Info, "Done.")
} else {
log(config, LogLevel.Info, "Dry Run. Output should be:")
log(config, LogLevel.Info, finalOutputContents)
}
} else if (exists) {
log(config, LogLevel.Info, `File ${outputPath} already exists, skipping`)
}
}
export function getOutputDir(config: ScaffoldConfig, outputPathOpt: string, basePath: string): string {
return path.resolve(
process.cwd(),
...([
outputPathOpt,
basePath,
config.createSubFolder
? config.subFolderNameHelper
? handlebarsParse(config, `{{ ${config.subFolderNameHelper} name }}`).toString()
: config.name
: undefined,
].filter(Boolean) as string[]),
)
}
export async function handleTemplateFile(
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(config, {
templatePath,
basePath,
})
const overwrite = getOptionValueForFile(config, inputPath, config.overwrite ?? false)
log(
config,
LogLevel.Debug,
`\nParsing ${templatePath}`,
`\nBase path: ${basePath}`,
`\nFull input path: ${inputPath}`,
`\nOutput Path Opt: ${outputPathOpt}`,
`\nFull output dir: ${outputDir}`,
`\nFull output path: ${outputPath}`,
`\n`,
)
await createDirIfNotExists(path.dirname(outputPath), config)
log(config, LogLevel.Info, `Writing to ${outputPath}`)
await copyFileTransformed(config, { exists, overwrite, outputPath, inputPath })
resolve()
} catch (e: any) {
handleErr(e)
reject(e)
}
})
}

87
src/logger.ts Normal file
View File

@@ -0,0 +1,87 @@
import { LogConfig, LogLevel, ScaffoldConfig } from "./types"
import chalk from "chalk"
export function log(config: LogConfig, 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> = {
[LogLevel.None]: "reset",
[LogLevel.Debug]: "blue",
[LogLevel.Info]: "dim",
[LogLevel.Warning]: "yellow",
[LogLevel.Error]: "red",
}
const chalkFn: any = chalk[levelColor[level]]
const key: "log" | "warn" | "error" = level === LogLevel.Error ? "error" : level === LogLevel.Warning ? "warn" : "log"
const logFn: any = console[key]
logFn(
...obj.map((i) =>
i instanceof Error
? chalkFn(i, JSON.stringify(i, undefined, 1), i.stack)
: typeof i === "object"
? chalkFn(JSON.stringify(i, undefined, 1))
: chalkFn(i),
),
)
}
export function logInputFile(
config: ScaffoldConfig,
{
origTemplate,
relPath,
template,
inputFilePath,
nonGlobTemplate,
basePath,
isDirOrGlob,
isGlob,
}: {
origTemplate: string
relPath: string
template: string
inputFilePath: string
nonGlobTemplate: string
basePath: string
isDirOrGlob: boolean
isGlob: boolean
},
): void {
log(
config,
LogLevel.Debug,
`\nprocess.cwd(): ${process.cwd()}`,
`\norigTemplate: ${origTemplate}`,
`\nrelPath: ${relPath}`,
`\ntemplate: ${template}`,
`\ninputFilePath: ${inputFilePath}`,
`\nnonGlobTemplate: ${nonGlobTemplate}`,
`\nbasePath: ${basePath}`,
`\nisDirOrGlob: ${isDirOrGlob}`,
`\nisGlob: ${isGlob}`,
`\n`,
)
}
export function logInitStep(config: ScaffoldConfig): void {
log(config, LogLevel.Debug, "Full config:", {
name: config.name,
templates: config.templates,
output: config.output,
createSubFolder: config.createSubFolder,
data: config.data,
overwrite: config.overwrite,
quiet: config.quiet,
subFolderNameHelper: 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!,
)})`,
dryRun: config.dryRun,
beforeWrite: config.beforeWrite,
} as Record<keyof ScaffoldConfig, unknown>)
log(config, LogLevel.Info, "Data:", config.data)
}

108
src/parser.ts Normal file
View File

@@ -0,0 +1,108 @@
import path from "path"
import { DefaultHelpers, Helper, LogLevel, ScaffoldConfig } from "./types"
import camelCase from "lodash/camelCase"
import snakeCase from "lodash/snakeCase"
import kebabCase from "lodash/kebabCase"
import startCase from "lodash/startCase"
import Handlebars from "handlebars"
import dtAdd from "date-fns/add"
import dtFormat from "date-fns/format"
import dtParseISO from "date-fns/parseISO"
import { log } from "./logger"
const dateFns = {
add: dtAdd,
format: dtFormat,
parseISO: dtParseISO,
}
export const defaultHelpers: Record<DefaultHelpers, Helper> = {
camelCase,
snakeCase,
startCase,
kebabCase,
hyphenCase: kebabCase,
pascalCase,
lowerCase: (text) => text.toLowerCase(),
upperCase: (text) => text.toUpperCase(),
now: nowHelper,
date: dateHelper,
}
export function _dateHelper(date: Date, formatString: string): string
export function _dateHelper(
date: Date,
formatString: string,
durationDifference: number,
durationType: keyof Duration,
): string
export function _dateHelper(
date: Date,
formatString: string,
durationDifference?: number,
durationType?: keyof Duration,
): string {
if (durationType && durationDifference !== undefined) {
return dateFns.format(dateFns.add(date, { [durationType]: durationDifference }), formatString)
}
return dateFns.format(date, formatString)
}
export function nowHelper(formatString: string): string
export function nowHelper(formatString: string, durationDifference: number, durationType: keyof Duration): string
export function nowHelper(formatString: string, durationDifference?: number, durationType?: keyof Duration): string {
return _dateHelper(new Date(), formatString, durationDifference!, durationType!)
}
export function dateHelper(date: string, formatString: string): string
export function dateHelper(
date: string,
formatString: string,
durationDifference: number,
durationType: keyof Duration,
): string
export function dateHelper(
date: string,
formatString: string,
durationDifference?: number,
durationType?: keyof Duration,
): string {
return _dateHelper(dateFns.parseISO(date), formatString, durationDifference!, durationType!)
}
export function pascalCase(s: string): string {
return startCase(s).replace(/\s+/g, "")
}
export function registerHelpers(config: ScaffoldConfig): void {
const _helpers = { ...defaultHelpers, ...config.helpers }
for (const helperName in _helpers) {
log(config, LogLevel.Debug, `Registering helper: ${helperName}`)
Handlebars.registerHelper(helperName, _helpers[helperName as keyof typeof _helpers])
}
}
export function handlebarsParse(
config: ScaffoldConfig,
templateBuffer: Buffer | string,
{ isPath = false }: { isPath?: boolean } = {},
): Buffer {
const { data } = config
try {
let str = templateBuffer.toString()
if (isPath) {
str = str.replace(/\\/g, "/")
}
const parser = Handlebars.compile(str, { noEscape: true })
let outputContents = parser(data)
if (isPath && path.sep !== "/") {
outputContents = outputContents.replace(/\//g, "\\")
}
return Buffer.from(outputContents)
} catch (e) {
log(config, LogLevel.Debug, e)
log(config, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content")
return Buffer.from(templateBuffer)
}
}

View File

@@ -5,26 +5,24 @@
* See [readme](README.md)
*/
import path from "path"
import { handleErr, resolve } from "./utils"
import {
createDirIfNotExists,
getOptionValueForFile,
handleErr,
log,
pascalCase,
isDir,
removeGlob,
makeRelativePath,
registerHelpers,
getTemplateGlobInfo,
getFileList,
getBasePath,
copyFileTransformed,
getTemplateFileInfo,
logInitStep,
logInputFile,
} from "./utils"
import { LogLevel, ScaffoldConfig } from "./types"
handleTemplateFile,
} from "./file"
import { LogLevel, MinimalConfig, Resolver, ScaffoldCmdConfig, ScaffoldConfig } from "./types"
import { OptionsBase } from "massarg/types"
import { pascalCase, registerHelpers } from "./parser"
import { log, logInitStep, logInputFile } from "./logger"
import { getOptionValueForFile, parseConfig } from "./config"
/**
* Create a scaffold using given `options`.
@@ -50,6 +48,7 @@ import { LogLevel, ScaffoldConfig } from "./types"
* For available default values, see {@link DefaultHelpers}.
*
* @param {ScaffoldConfig} config The main configuration object
* @return {Promise<void>} A promise that resolves when the scaffold is complete
*
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
@@ -101,40 +100,42 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
throw e
}
}
async function handleTemplateFile(
config: ScaffoldConfig,
{ templatePath, basePath }: { templatePath: string; basePath: string },
/**
* Create a scaffold based on a config file or URL.
*
* @param {string} pathOrUrl The path or URL to the config file
* @param {Record<string, string>} config Information needed before loading the config
* @param {Partial<Omit<ScaffoldConfig, 'name'>>} overrides Any overrides to the loaded config
*
* @see {@link Scaffold}
* @category Main
* @return {Promise<void>} A promise that resolves when the scaffold is complete
*/
Scaffold.fromConfig = async function (
/** The path or URL to the config file */
pathOrUrl: string,
/** Information needed before loading the config */
config: MinimalConfig,
/** Any overrides to the loaded config */
overrides?: Resolver<ScaffoldCmdConfig, Partial<Omit<ScaffoldConfig, "name">>>,
): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const { inputPath, outputPathOpt, outputDir, outputPath, exists } = await getTemplateFileInfo(config, {
templatePath,
basePath,
})
const overwrite = getOptionValueForFile(config, inputPath, config.overwrite ?? false)
log(
config,
LogLevel.Debug,
`\nParsing ${templatePath}`,
`\nBase path: ${basePath}`,
`\nFull input path: ${inputPath}`,
`\nOutput Path Opt: ${outputPathOpt}`,
`\nFull output dir: ${outputDir}`,
`\nFull output path: ${outputPath}`,
`\n`,
)
await createDirIfNotExists(path.dirname(outputPath), config)
log(config, LogLevel.Info, `Writing to ${outputPath}`)
await copyFileTransformed(config, { exists, overwrite, outputPath, inputPath })
resolve()
} catch (e: any) {
handleErr(e)
reject(e)
}
})
const _cmdConfig: ScaffoldCmdConfig & OptionsBase = {
dryRun: false,
output: process.cwd(),
verbose: LogLevel.Info,
overwrite: false,
templates: [],
createSubFolder: false,
quiet: false,
help: false,
extras: [],
config: pathOrUrl,
...config,
}
const _overrides = resolve(overrides, _cmdConfig)
const _config = await parseConfig(_cmdConfig)
return Scaffold({ ..._config, ..._overrides })
}
export default Scaffold

View File

@@ -3,7 +3,8 @@ import { HelperDelegate } from "handlebars/runtime"
/**
* The config object for defining a scaffolding group.
*
* @see https://github.com/chenasraf/simple-scaffold#readme
* @see {@link https://chenasraf.github.io/simple-scaffold/pages/node.html | Node.js usage}
* @see {@link https://chenasraf.github.io/simple-scaffold/pages/cli.html | CLI usage}
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
@@ -56,7 +57,7 @@ export interface ScaffoldConfig {
* 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.
* a boolean for each file.
*
* May also be a {@link FileResponseHandler} which returns a boolean value per file.
*
@@ -131,10 +132,8 @@ export interface ScaffoldConfig {
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
* @see https://casraf.blog/simple-scaffold#helpers
* @see https://casraf.blog/simple-scaffold#built-in-helpers
* @see https://handlebarsjs.com/guide/#custom-helpers
*/
* @see {@link https://chenasraf.github.io/simple-scaffold/pages/templates.html | Templates}
* */
helpers?: Record<string, Helper>
/**
@@ -189,7 +188,7 @@ export interface ScaffoldConfig {
* @see {@link DefaultHelpers}
* @see {@link DateHelpers}
* @see {@link ScaffoldConfig}
* @see {@link subFolderNameHelper}
* @see {@link ScaffoldConfig.subFolderNameHelper}
*
* @category Helpers
*/
@@ -326,13 +325,58 @@ export type FileResponse<T> = T | FileResponseHandler<T>
/** @internal */
export interface ScaffoldCmdConfig {
/**
* Name to be passed to the generated files. `{{name}}` and `{{Name}}` inside contents and file names will be replaced
* accordingly.
*/
name: string
templates: string[]
output: string
createSubFolder: boolean
data?: Record<string, string>
appendData?: Record<string, string>
overwrite: boolean
quiet: boolean
verbose: LogLevel
dryRun: boolean
config?: string
/** The key to use for the file which contains the template configurations. */
key?: string
github?: string
}
/**
* A mapping of scaffold template keys to their configurations.
*
* Each configuration is a {@link ScaffoldConfig} object.
*
* The key is the name of the template, and the value is the configuration for that template.
*
* When no template key is provided to the scaffold command, the "default" template is used.
*
* @see {@link ScaffoldConfig}
*/
export type ScaffoldConfigMap = Record<string, ScaffoldConfig>
/** The scaffold config file is either:
* - A {@link ScaffoldConfigMap} object
* - A function that returns a {@link ScaffoldConfigMap} object
* - A promise that resolves to a {@link ScaffoldConfigMap} object
* - A function that returns a promise that resolves to a {@link ScaffoldConfigMap} object
*/
export type ScaffoldConfigFile = AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>
/** @internal */
export type Resolver<T, R = T> = R | ((value: T) => R)
/** @internal */
export type AsyncResolver<T, R = T> = Resolver<T, Promise<R> | R>
/** @internal */
export type LogConfig = Pick<ScaffoldConfig, "quiet" | "verbose">
/** @internal */
export type ConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config">
/** @internal */
export type MinimalConfig = Pick<ScaffoldCmdConfig, "name" | "key">

View File

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

View File

@@ -3,7 +3,7 @@ import FileSystem from "mock-fs/lib/filesystem"
import Scaffold from "../src/scaffold"
import { readdirSync, readFileSync } from "fs"
import { Console } from "console"
import { defaultHelpers } from "../src/utils"
import { defaultHelpers } from "../src/parser"
import { join } from "path"
import * as dateFns from "date-fns"
import crypto from "crypto"

View File

@@ -1,7 +1,9 @@
import { dateHelper, handlebarsParse, nowHelper } from "../src/utils"
import { ScaffoldConfig } from "../src/types"
import { ScaffoldCmdConfig, ScaffoldConfig } from "../src/types"
import path from "path"
import * as dateFns from "date-fns"
import { OptionsBase } from "massarg/types"
import { dateHelper, handlebarsParse, nowHelper } from "../src/parser"
import { parseAppendData } from "../src/config"
const blankConf: ScaffoldConfig = {
verbose: 0,
@@ -11,6 +13,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
@@ -88,4 +104,22 @@ describe("Utils", () => {
})
})
})
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

@@ -1,10 +1,10 @@
{
"compilerOptions": {
"target": "ES2019",
"target": "ES2022",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"lib": ["ES2019"],
"lib": ["ES2022"],
"declaration": true,
"outDir": "dist",
"strict": true,

63
typedoc.config.js Normal file
View File

@@ -0,0 +1,63 @@
const path = require("path")
/** @type {import('typedoc').TypeDocOptions} */
module.exports = {
name: "Simple Scaffold",
entryPoints: ["src/index.ts"],
includeVersion: true,
categorizeByGroup: false,
sort: ["visibility"],
categoryOrder: ["Main", "*"],
media: "media",
githubPages: true,
entryPointStrategy: "expand",
out: "docs",
excludePrivate: true,
excludeProtected: true,
excludeInternal: true,
gaID: "GTM-KHQS9TQ",
validation: {
invalidLink: true,
},
plugin: ["@knodes/typedoc-plugin-pages"],
customCss: "src/docs.css",
options: "typedoc.config.js",
logLevel: "Verbose",
pluginPages: {
logLevel: "Verbose",
pages: [
{
name: "Configuration",
source: "README.md",
childrenDir: path.join(process.cwd(), "pages"),
childrenOutputDir: "./",
children: [
{
name: "CLI usage",
source: "cli.md",
},
{
name: "Node.js usage",
source: "node.md",
},
{
name: "Templates",
source: "templates.md",
},
{
name: "Configuration Files",
source: "configuration_files.md",
},
{
name: "Migrating v0.x to v1.x",
source: "migration.md",
},
],
},
{
name: "Changelog",
source: "./CHANGELOG.md",
},
],
},
}

View File

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

3009
yarn.lock

File diff suppressed because it is too large Load Diff