Compare commits

...

177 Commits

Author SHA1 Message Date
semantic-release-bot
4d1a6e1f0d chore(release): 1.9.0 [skip ci]
# [1.9.0](https://github.com/chenasraf/simple-scaffold/compare/v1.8.0...v1.9.0) (2024-01-02)

### Features

* add --recurse-submodules to git clone ([cbaf130](cbaf130a0c))
2024-01-02 00:20:20 +00:00
daae9c10f9 ci: fix actions 2024-01-02 02:19:46 +02:00
758719de5d chore: update dependencies 2024-01-02 02:19:46 +02:00
1903055d46 ci: update build steps 2024-01-02 02:19:46 +02:00
Claudio Barca
cbaf130a0c feat: add --recurse-submodules to git clone 2024-01-02 02:19:46 +02:00
e26fe2a3d4 ci: update build process 2024-01-02 02:19:46 +02:00
7e1acf0607 ci: update docs build, semantic release
docs: update config files entry
2024-01-02 02:19:46 +02:00
semantic-release-bot
f666c357f4 chore(release): 1.8.0 [skip ci]
## [1.8.0](https://github.com/chenasraf/simple-scaffold/compare/v1.7.2...v1.8.0) (2023-11-29)

### Bug Fixes

* **config:** fn config load ([457c904](457c90470b)), closes [#63](https://github.com/chenasraf/simple-scaffold/issues/63)
2023-11-29 22:16:52 +00:00
f5d55f234a docs: update configuration files docs 2023-11-30 00:12:11 +02:00
semantic-release-bot
746f924a22 chore(release): 1.8.0-pre.1 [skip ci]
## [1.8.0-pre.1](https://github.com/chenasraf/simple-scaffold/compare/v1.7.2...v1.8.0-pre.1) (2023-11-27)

### Bug Fixes

* **config:** fn config load ([457c904](457c90470b)), closes [#63](https://github.com/chenasraf/simple-scaffold/issues/63)
2023-11-30 00:12:11 +02:00
807c3e27e2 ci: update release config 2023-11-28 00:49:28 +02:00
b048841dac chore: update dependencies 2023-11-28 00:49:28 +02:00
457c90470b fix(config): fn config load
closes #63
2023-11-28 00:49:28 +02:00
dependabot[bot]
0fa1ad4db7 build(deps-dev): bump @babel/traverse from 7.21.5 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.21.5 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-28 00:49:28 +02:00
semantic-release-bot
d62eeeb8e4 chore(release): 1.7.2 [skip ci]
## [1.7.2](https://github.com/chenasraf/simple-scaffold/compare/v1.7.1...v1.7.2) (2023-08-20)

### Bug Fixes

* windows path resolution ([98ee000](98ee00031f))
2023-08-20 10:09:48 +00:00
Chen Asraf
565d1f33aa Merge pull request #61 from chenasraf/pre
v1.7.2
2023-08-20 13:08:25 +03:00
semantic-release-bot
9f58fff2cf chore(release): 1.7.2-pre.1 [skip ci]
## [1.7.2-pre.1](https://github.com/chenasraf/simple-scaffold/compare/v1.7.1...v1.7.2-pre.1) (2023-08-15)

### Bug Fixes

* windows path resolution ([98ee000](98ee00031f))
2023-08-15 19:34:02 +00:00
dbba81d053 ci: trigger on pre branch 2023-08-15 22:32:25 +03:00
9f5716e1b9 chore: bump version number 2023-08-15 22:31:38 +03:00
Chen Asraf
4cbb79bb3b Merge pull request #60 from chenasraf/fix/windows
[BUGFIX] Windows path resolution
2023-08-15 22:28:04 +03:00
98ee00031f fix: windows path resolution 2023-08-14 00:19:50 +03:00
8c3369a00d chore: formatting 2023-08-14 00:19:36 +03:00
20ef0cea9b chore: update logs 2023-08-14 00:08:16 +03:00
Chen Asraf
3413151358 test: add tests
chore: use node:* modules for builtins
2023-06-12 23:26:25 +03:00
Chen Asraf
d2a2fda1b1 chore: update dependencies 2023-06-12 23:18:50 +03:00
Chen Asraf
486c07a55b Merge branch 'master' into develop 2023-06-08 08:13:52 +03:00
semantic-release-bot
de05bca546 chore(release): 1.7.1 [skip ci]
## [1.7.1](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0...v1.7.1) (2023-06-07)

### Bug Fixes

* local config file load error ([2b74239](2b7423993b))
2023-06-07 23:56:29 +00:00
Chen Asraf
72d4cf58c5 Merge pull request #58 from chenasraf/develop
v1.7.1
2023-06-08 02:55:06 +03:00
Chen Asraf
2cf31e827e build: fix tsconfig 2023-06-08 02:52:45 +03:00
Chen Asraf
3714e8b3bd build: update release rules, add tests 2023-06-08 02:50:50 +03:00
semantic-release-bot
77be7c09d5 chore(release): 1.7.1-develop.1 [skip ci]
## [1.7.1-develop.1](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0...v1.7.1-develop.1) (2023-06-07)

### Bug Fixes

* local config file load error ([2b74239](2b7423993b))
2023-06-07 21:51:53 +00:00
Chen Asraf
1246b51cda Merge branch 'master' into develop
# Conflicts:
#	CHANGELOG.md
#	package.json
2023-06-08 00:50:24 +03:00
semantic-release-bot
fea6c0fb16 chore(release): 1.7.0-develop.7 [skip ci]
## [1.7.0-develop.7](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0-develop.6...v1.7.0-develop.7) (2023-06-07)

### Bug Fixes

* local config file load error ([2b74239](2b7423993b))
2023-06-07 20:22:57 +00:00
Chen Asraf
2b7423993b fix: local config file load error 2023-06-07 23:21:12 +03:00
semantic-release-bot
a47ba1186c chore(release): 1.7.0-develop.6 [skip ci]
## [1.7.0-develop.6](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0-develop.5...v1.7.0-develop.6) (2023-05-27)
2023-05-27 23:16:21 +00:00
Chen Asraf
06b1552dca docs: update README.md 2023-05-28 02:14:36 +03:00
semantic-release-bot
4868925511 chore(release): 1.7.0 [skip ci]
## [1.7.0](https://github.com/chenasraf/simple-scaffold/compare/v1.6.0...v1.7.0) (2023-05-17)

### Features

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

### Bug Fixes

* use path.normalize ([565090a](565090a951))
2023-05-17 17:30:07 +00:00
Chen Asraf
79cfdbed38 Merge pull request #57 from chenasraf/develop
release
2023-05-17 20:28:02 +03:00
Chen Asraf
4f27b7b934 test: add tests 2023-05-13 02:36:26 +03:00
semantic-release-bot
bee430a40d chore(release): 1.7.0-develop.5 [skip ci]
## [1.7.0-develop.5](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0-develop.4...v1.7.0-develop.5) (2023-05-12)
2023-05-12 22:59:55 +00:00
Chen Asraf
3dfc920455 test: fix + add tests 2023-05-13 01:58:21 +03:00
Chen Asraf
68307d1378 refactor: remove lodash dependency 2023-05-13 01:29:59 +03:00
Chen Asraf
33e1d569a3 build: update workflows 2023-05-13 01:29:40 +03:00
semantic-release-bot
c446439b1a chore(release): 1.7.0-develop.4 [skip ci]
## [1.7.0-develop.4](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0-develop.3...v1.7.0-develop.4) (2023-05-11)
2023-05-11 21:03:55 +00:00
Chen Asraf
773fd00674 build: update package asset name 2023-05-12 00:02:36 +03:00
semantic-release-bot
22763f655e chore(release): 1.7.0-develop.3 [skip ci]
## [1.7.0-develop.3](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0-develop.2...v1.7.0-develop.3) (2023-05-11)
2023-05-11 20:28:22 +00:00
Chen Asraf
a54b1f9fa7 build: update package link 2023-05-11 23:26:59 +03:00
Chen Asraf
dac5527173 build: add packageManager key to package.json 2023-05-11 22:59:36 +03:00
Chen Asraf
339459cad8 build: update workflows 2023-05-11 22:55:52 +03:00
Chen Asraf
62b4a1cd2f build: update workflows 2023-05-11 22:52:36 +03:00
Chen Asraf
5844c080ec build: use pnpm 2023-05-11 22:35:59 +03:00
Chen Asraf
9393c546aa chore: cleanup 2023-05-11 22:35:48 +03:00
semantic-release-bot
263bf0bf36 chore(release): 1.7.0-develop.2 [skip ci]
## [1.7.0-develop.2](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0-develop.1...v1.7.0-develop.2) (2023-05-10)
2023-05-10 21:03:34 +00:00
Chen Asraf
e0ed371adb build: update release.config.js 2023-05-10 22:18:07 +03:00
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
Chen Asraf
d7d2b13888 chore: bump version number [publish] 2023-03-11 23:34:09 +02:00
Chen Asraf
6b6baefbce Merge pull request #48 from locene/develop
fix: base path
2023-03-11 23:29:23 +02:00
locene
943717a769 fix: base path 2023-03-10 06:01:57 +08:00
dependabot[bot]
e28c4db323 Bump json5 from 2.2.0 to 2.2.3
Bumps [json5](https://github.com/json5/json5) from 2.2.0 to 2.2.3.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.0...v2.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-07 04:02:37 +00:00
Chen Asraf
5308ef9618 Update README.md 2022-12-27 12:45:14 +02:00
Chen Asraf
a8e9f71c11 Update FUNDING.yml 2022-08-29 00:02:20 +03:00
Chen Asraf
6fa2a8b03b Update README.md 2022-08-16 16:42:38 +03:00
Chen Asraf
7c69010e95 update docs + spell checker 2022-05-24 10:09:48 +03:00
Chen Asraf
b569f2b0a1 formatting updates 2022-05-24 00:36:17 +03:00
Chen Asraf
adba6490e8 Update README.md 2022-05-18 01:04:10 +03:00
Chen Asraf
1d0c20cba9 docs: fix typo 2022-05-15 14:32:22 +03:00
Chen Asraf
cd68ab41d4 Update README.md 2022-05-15 14:02:42 +03:00
Chen Asraf
1b37a8eb40 update intro gif 2022-05-06 01:25:34 +03:00
Chen Asraf
576798cfeb update intro gif 2022-05-06 01:24:30 +03:00
Chen Asraf
ac8af8e129 revert docs build in ci 2022-05-03 19:52:23 +03:00
Chen Asraf
96b93d8b60 try fix docs build process 2022-05-03 19:51:12 +03:00
Chen Asraf
11edb0d464 remove unnecessary install 2022-05-03 19:46:00 +03:00
Chen Asraf
35262b5a44 fix docs build process 2022-05-03 19:45:34 +03:00
Chen Asraf
3b77e6938c improve docs build process 2022-05-03 19:41:16 +03:00
Chen Asraf
3cf93598c3 improve docs build process 2022-05-03 19:38:12 +03:00
Chen Asraf
8957b595da improve docs build process 2022-05-03 19:35:36 +03:00
Chen Asraf
2841ebd3d2 update docs + formatting update 2022-05-03 19:24:18 +03:00
Chen Asraf
0364247904 fix build 2022-05-01 14:26:14 +03:00
Chen Asraf
ac2c0d7ffb fix build 2022-05-01 14:24:44 +03:00
Chen Asraf
a002402088 update docs 2022-05-01 14:23:03 +03:00
Chen Asraf
bdc23e7074 Update README.md 2022-05-01 13:56:20 +03:00
Chen Asraf
6160a04009 Update README.md 2022-05-01 13:53:57 +03:00
Chen Asraf
7414baf2ab Merge pull request #42 from chenasraf/master
v1.1.2 back merge
2022-04-29 10:19:26 +03:00
53 changed files with 7231 additions and 4477 deletions

5
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: chenasraf
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: casraf
@@ -9,4 +9,5 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
custom:
- "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22&currency_code=ILS&source=url"

View File

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

View File

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

View File

@@ -1,52 +0,0 @@
name: Alpha Releases
on:
push:
branches: [develop]
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: "12.x"
- run: yarn install --frozen-lockfile
- run: yarn test
- run: yarn build
- run: cd ./dist && yarn pack --filename=../package.tgz
if: "contains(github.event.head_commit.message, '[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,21 +1,29 @@
name: Build Documentation
name: Documentation
on:
push:
branches: [master, develop]
branches: [master]
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, 'chore(release)')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- name: Checkout
uses: actions/checkout@v3
with:
node-version: "12.x"
- run: cd doc-theme && yarn install && yarn build && rm -rf node_modules && cd ..
- run: yarn install --frozen-lockfile
- run: yarn typedoc
- uses: peaceiris/actions-gh-pages@v3
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: Install PNPM
run: npm i -g pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Docs
run: pnpm build-docs
- name: Deploy on GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs

View File

@@ -2,16 +2,24 @@ name: Pull Requests
on:
pull_request:
branches: [master, develop]
branches: [master, pre, 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
- name: Checkout
uses: actions/checkout@v3
with:
node-version: "12.x"
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn test
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: Install PNPM
run: npm i -g pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run Tests
run: pnpm test
- name: Build Package
run: pnpm build

View File

@@ -2,49 +2,39 @@ name: Release
on:
push:
branches: [master]
branches: [master, pre, develop]
permissions:
contents: read # for checkout
jobs:
build:
release:
name: Release
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
permissions:
contents: write # to be able to publish a GitHub release
issues: write # to be able to comment on released issues
pull-requests: write # to be able to comment on released pull requests
id-token: write # to enable use of OIDC for npm provenance
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- name: Checkout
uses: actions/checkout@v3
with:
node-version: "12.x"
- run: yarn install --frozen-lockfile
- run: yarn test
- run: yarn build
- run: cd ./dist && yarn pack --filename=../package.tgz
if: "!contains(github.event.head_commit.message, '[skip publish]')"
- uses: Klemensas/action-autotag@stable
if: "!contains(github.event.head_commit.message, '[skip publish]')"
id: update_tag
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: Install PNPM
run: npm i -g pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run Tests
run: pnpm test
- name: Build Package
run: pnpm build
- name: Pack
run: cd ./dist && pnpm pack --pack-destination=../
- name: Semantic Release
run: npx semantic-release
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

4
.gitignore vendored
View File

@@ -42,6 +42,9 @@ typings/
# Optional npm cache directory
.npm
# NPM
.npmrc
# Optional eslint cache
.eslintcache
@@ -62,3 +65,4 @@ dist/
.DS_Store
tmp/
docs/
.nvmrc

View File

@@ -6,4 +6,4 @@
},
"no-inline-html": false,
"first-line-h1": false
}
}

View File

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

50
.vscode/launch.json vendored
View File

@@ -1,29 +1,27 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Scaffold",
"type": "node",
"request": "launch",
"protocol": "inspector",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/test.ts",
"outFiles": [
"${workspaceRoot}/dist/test.js"
],
"env": {
"NODE_ENV": "develop"
},
"sourceMaps": true,
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Scaffold",
"type": "node",
"request": "launch",
"protocol": "inspector",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/test.ts",
"outFiles": ["${workspaceRoot}/dist/test.js"],
"env": {
"NODE_ENV": "develop"
},
{
"type": "node",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickProcess}"
}
]
"sourceMaps": true
},
{
"type": "node",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickProcess}"
}
]
}

13
.vscode/settings.json vendored
View File

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

30
.vscode/tasks.json vendored
View File

@@ -5,55 +5,55 @@
"script": "build",
"label": "build",
"type": "npm",
"problemMatcher": [],
"problemMatcher": []
},
{
"script": "dev",
"label": "dev",
"type": "npm",
"problemMatcher": [],
"problemMatcher": []
},
{
"command": "yarn typedoc --watch",
"command": "pnpm typedoc --watch",
"label": "typedoc --watch",
"type": "shell",
"problemMatcher": [],
"problemMatcher": []
},
{
"script": "start",
"label": "start",
"type": "npm",
"problemMatcher": [],
"problemMatcher": []
},
{
"script": "test",
"label": "test",
"type": "npm",
"problemMatcher": [],
"problemMatcher": []
},
{
"command": "yarn test --watchAll",
"label": "yarn test --watchAll",
"command": "pnpm test --watchAll",
"label": "pnpm test --watchAll",
"type": "shell",
"problemMatcher": [],
"problemMatcher": []
},
{
"script": "cmd",
"label": "cmd",
"type": "npm",
"problemMatcher": [],
"problemMatcher": []
},
{
"script": "build-test",
"label": "build-test",
"type": "npm",
"problemMatcher": [],
"problemMatcher": []
},
{
"script": "build-cmd",
"label": "build-cmd",
"type": "npm",
"problemMatcher": [],
},
],
}
"problemMatcher": []
}
]
}

211
CHANGELOG.md Normal file
View File

@@ -0,0 +1,211 @@
# Change Log
# [1.9.0](https://github.com/chenasraf/simple-scaffold/compare/v1.8.0...v1.9.0) (2024-01-02)
### Features
* add --recurse-submodules to git clone ([cbaf130](https://github.com/chenasraf/simple-scaffold/commit/cbaf130a0cce45a2d20a3c2d132ac98e2f105b4f))
## [1.8.0](https://github.com/chenasraf/simple-scaffold/compare/v1.7.2...v1.8.0) (2023-11-29)
### Bug Fixes
* **config:** fn config load ([457c904](https://github.com/chenasraf/simple-scaffold/commit/457c90470b0f138862469ff878c7e061c7afd18a)), closes [#63](https://github.com/chenasraf/simple-scaffold/issues/63)
## [1.8.0-pre.1](https://github.com/chenasraf/simple-scaffold/compare/v1.7.2...v1.8.0-pre.1) (2023-11-27)
### Bug Fixes
* **config:** fn config load ([457c904](https://github.com/chenasraf/simple-scaffold/commit/457c90470b0f138862469ff878c7e061c7afd18a)), closes [#63](https://github.com/chenasraf/simple-scaffold/issues/63)
## [1.7.2](https://github.com/chenasraf/simple-scaffold/compare/v1.7.1...v1.7.2) (2023-08-20)
### Bug Fixes
* windows path resolution ([98ee000](https://github.com/chenasraf/simple-scaffold/commit/98ee00031fc1ad67a53797a9e28e5c4759bc8bce))
## [1.7.2-pre.1](https://github.com/chenasraf/simple-scaffold/compare/v1.7.1...v1.7.2-pre.1) (2023-08-15)
### Bug Fixes
* windows path resolution ([98ee000](https://github.com/chenasraf/simple-scaffold/commit/98ee00031fc1ad67a53797a9e28e5c4759bc8bce))
## [1.7.1](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0...v1.7.1) (2023-06-07)
### Bug Fixes
- local config file load error
([2b74239](https://github.com/chenasraf/simple-scaffold/commit/2b7423993be06b2375631642455c801ae2acf75f))
## [1.7.0](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0-develop.5...v1.7.0) (2023-05-17)
## [1.7.0-develop.7](https://github.com/chenasraf/simple-scaffold/compare/v1.7.0-develop.6...v1.7.0-develop.7) (2023-06-07)
### Bug Fixes
- local config file load error
([2b74239](https://github.com/chenasraf/simple-scaffold/commit/2b7423993be06b2375631642455c801ae2acf75f))
## [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.6.0-develop.1...v1.6.0) (2023-05-05)
## [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)

472
README.md
View File

@@ -3,393 +3,162 @@
<h2 align="center">
[GitHub](https://github.com/chenasraf/simple-scaffold) |
[Documentation](https://casraf.blog/simple-scaffold) |
[NPM](https://npmjs.com/package/simple-scaffold) |
[casraf.blog](https://casraf.blog)
[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)
</h2>
Simple Scaffold allows you to generate any set of files in the easiest way possible with simple commands.
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.
It is completely framework agnostic so you can use it for anything from a few simple files to an
entire app boilerplate setup.
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.
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.
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!
<br />
<div align="center">
<details>
<summary>Table of contents</summary>
![sample animation](media/intro.gif)
- [Install](#install)
- [Command Line Interface (CLI)](#command-line-interface-cli)
- [Available flags](#available-flags)
- [Node module](#node-module)
- [Node-specific options](#node-specific-options)
- [Preparing files](#preparing-files)
- [Template files](#template-files)
- [Variable/token replacement](#variabletoken-replacement)
- [Helpers](#helpers)
- [Built-in Helpers](#built-in-helpers)
- [Capitalization Helpers](#capitalization-helpers)
- [Date helpers](#date-helpers)
- [Custom Helpers](#custom-helpers)
- [Examples](#examples)
- [Run](#run)
- [Command Example](#command-example)
- [Node Module Example](#node-module-example)
- [Files](#files)
- [Input](#input)
- [Output](#output)
- [Contributing](#contributing)
</details>
</div>
---
## 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"}'
MyComponent
```shell
# generate single component
$ npx simple-scaffold@latest \
-t templates/component -o src/components PageWrapper
```
#### 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",
// ...
},
})
console.log("Done.")
},
}
```
### Files
Then call your scaffold like this:
#### Input
Input file path:
```text
project → scaffold → {{Name}}.js → src → components
```shell
$ npx simple-scaffold@latest -c scaffold.config.js PageWrapper
```
Input file contents:
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.
```typescript
import React from 'react'
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)
export default {{camelCase name}}: React.FC = (props) => {
return (
<div className="{{className}}">{{camelCase name}} Component</div>
)
}
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).
### Remote Configurations
Another quick way to start is to re-use someone else's (or your own) work using a template
repository.
A remote config can be loaded in one of these ways:
- 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`
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.
Here is an example for loading the example component templates in this very repository:
```shell
$ npx simple-scaffold@latest \
-gh chenasraf/simple-scaffold#scaffold.config.js:component \
PageWrapper
# equivalent to:
$ npx simple-scaffold@latest \
-c https://github.com/chenasraf/simple-scaffold.git#scaffold.config.js:component \
PageWrapper
```
#### Output
When template name (`:component`) is omitted, `default` is used.
Output file path:
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).
- With `createSubFolder = false` (default):
## Documentation
```text
project → src → components → MyComponent.js
```
See full documentation [here](https://chenasraf.github.io/simple-scaffold).
- 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
import React from 'react'
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
I am developing this package on my free time, so any support, whether code, issues, or just stars
is very helpful to sustaining its life. If you would like to donate a bit to help keep the project
alive, I would be very thankful!
I am developing this package on my free time, so any support, whether code, issues, or just stars is
very helpful to sustaining its life. If you are feeling incredibly generous and would like to donate
just a small amount to help sustain this project, I would be very very thankful!
<a href='https://ko-fi.com/casraf' target='_blank'>
<img height='36' style='border:0px;height:36px;'
@@ -414,11 +183,20 @@ Some tips on getting around the code:
- Use `yarn dev` for development - it runs TypeScript compile in watch mode, allowing you to make
changes and immediately be able to try them using `yarn cmd`.
- Use `yarn build` to build the output
- Use `yarn build` to build the output once
- Use `yarn test` to run tests
- Use `yarn cmd` to use the CLI feature of Simple Scaffold from within the root directory,
enabling you to test different behaviors. See `yarn cmd -h` for more information.
- Use `yarn cmd` to use the CLI feature of Simple Scaffold from within the root directory, enabling
you to test different behaviors. See `yarn cmd -h` for more information.
> This requires an updated build, and does not trigger one itself. Either use `yarn dev` to watch
> for changes and build, or `yarn build` before running this, or use `yarn build-cmd` instead,
> which triggers a build right before running the command with the rest of the given arguments.
> This requires an updated build, and does not trigger one itself. From here you have several
> options:
>
> - Run `yarn dev` to watch for file changes and build automatically
> - Run `yarn build` before running this to trigger a one-time build
> - Run `yarn build-cmd` which triggers a build right before running `yarn cmd` automatically with
> the rest of the given arguments.
- Use `yarn build-docs` to build the documentation once
- Use `yarn watch-docs` to start docs in watch mode
- To see the documentation, currently you have to serve the directory yourself with a static web
server (like node's built in serve, VS code's "Go Live" mode, etc)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

5
examples/.dotdir/README.md Executable file
View File

@@ -0,0 +1,5 @@
# {{ name }} Readme
TO DO:
- [ ] ...

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,17 +186,20 @@ 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,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
verbose: true,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
// extensionsToTreatAsEsm: [".ts"],
}

BIN
media/intro.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

6
nodemon.json Normal file
View File

@@ -0,0 +1,6 @@
{
"ignore": ["**/*.test.ts", "**/*.spec.ts", ".git", "node_modules"],
"watch": ["src"],
"exec": "node -r tsconfig-paths/register -r ts-node/register ./src/index.ts",
"ext": "ts, js"
}

View File

@@ -1,13 +1,14 @@
{
"name": "simple-scaffold",
"version": "1.1.2",
"description": "A simple command to generate any file structure, from single components to entire app boilerplates.",
"homepage": "https://casraf.blog/simple-scaffold",
"version": "1.9.0",
"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",
"packageManager": "pnpm@8.6.2",
"keywords": [
"javascript",
"cli",
@@ -20,38 +21,46 @@
"scaffolding"
],
"scripts": {
"clean": "rimraf dist/",
"build": "yarn clean && tsc && chmod -R +x ./dist && cp ./package.json ./README.md ./dist/",
"clean": "rm -rf dist/",
"build": "pnpm 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",
"audit-fix": "npm_config_yes=true npx yarn-audit-fix --flow=convert"
"build-test": "pnpm build && pnpm test",
"build-cmd": "pnpm build && pnpm cmd",
"build-docs": "typedoc",
"watch-docs": "pnpm typedoc --watch",
"audit-fix": "pnpm audit --fix",
"changelog": "conventional-changelog -p conventionalcommits -i CHANGELOG.md -s -r 0; echo \"# Change Log\n\n$(cat CHANGELOG.md)\" > CHANGELOG.md"
},
"dependencies": {
"chalk": "^4.1.2",
"date-fns": "^2.28.0",
"glob": "^7.1.3",
"date-fns": "^2.30.0",
"glob": "^10.3.3",
"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",
"@types/lodash": "^4.14.171",
"@knodes/typedoc-plugin-pages": "^0.23.4",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/release-notes-generator": "^10.0.3",
"@types/jest": "^29.5.1",
"@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",
"@types/semantic-release": "^20.0.1",
"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",
"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.7",
"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,172 @@
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 can be 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",
},
}
```
Another feature of using a JS file is you can export a function which will be loaded with the CMD
config provided to Simple Scaffold. The `extras` key contains any values not consumed by built-in
flags, so you can pre-process your args before outputting a config:
```js
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
module.exports = (config) => {
console.log("Config:", config)
return {
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#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#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,16 +1,20 @@
# Migrating from 0.x to 1.0
In Simple Scaffold v1.0, the entire codebase was overhauled, yet usage remains mostly the same
between versions. With these notable exceptions:
- Some of the argument names have changed
- Template syntax has been improved
- The command to run Scaffold has been simplified from `new SimpleScaffold(opts).run()` to `SimpleScaffold(opts)`, which now returns a promise that you can await to know when the process has been completed.
- The command to run Scaffold has been simplified from `new SimpleScaffold(opts).run()` to
`SimpleScaffold(opts)`, which now returns a promise that you can await to know when the process
has been completed.
## Argument changes
- `locals` has been renamed to `data`. The appropriate command line args have been updated as
well to `--data` | `-d`.
- `locals` has been renamed to `data`. The appropriate command line args have been updated as well
to `--data` | `-d`.
- Additional options have been added to both CLI and Node interfaces. See
[Command Line Interface (CLI) usage](https://chenasraf.github.io/simple-scaffold/pages/cli.html)
and [Node.js usage](https://chenasraf.github.io/simple-scaffold/pages/node.html) for more
information.
## 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>
)
}
```

4767
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

43
release.config.js Normal file
View File

@@ -0,0 +1,43 @@
/** @type {import('semantic-release').Options} */
module.exports = {
branches: ["master", { name: "pre", prerelease: true }],
analyzeCommits: {
path: "semantic-release-conventional-commits",
},
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@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: ["*.tgz"],
},
],
],
}

16
scaffold.config.js Normal file
View File

@@ -0,0 +1,16 @@
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
module.exports = (conf) => {
console.log("Config:", conf)
return {
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

@@ -3,15 +3,22 @@ import massarg from "massarg"
import chalk from "chalk"
import { LogLevel, ScaffoldCmdConfig } from "./types"
import { Scaffold } from "./scaffold"
import path from "path"
import fs from "fs/promises"
import path from "node:path"
import fs from "node:fs/promises"
import { parseAppendData, parseConfigFile } 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 pkgFile = await fs.readFile(path.join(__dirname, "package.json"))
const pkg = JSON.parse(pkgFile.toString())
const isConfigProvided =
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 parseConfigFile(config)
return Scaffold(_config)
})
.option({
name: "name",
aliases: ["n"],
@@ -20,11 +27,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: !isConfigProvided,
})
.option({
name: "templates",
@@ -33,7 +58,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: !isConfigProvided,
})
.option({
name: "overwrite",
@@ -48,6 +73,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"],
@@ -71,7 +103,10 @@ export async function parseCliArgs(args = process.argv.slice(2)) {
name: "verbose",
aliases: ["v"],
defaultValue: LogLevel.Info,
description: `Determine amount of logs to display. The values are: ${chalk.bold`0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4 (error)`}. The provided level will display messages of the same level or higher.`,
description:
"Determine amount of logs to display. The values are: " +
`${chalk.bold`0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4 (error)`}. ` +
"The provided level will display messages of the same level or higher.",
parse: Number,
})
.option({
@@ -88,16 +123,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"),

136
src/config.ts Normal file
View File

@@ -0,0 +1,136 @@
import path from "node:path"
import {
ConfigLoadConfig,
FileResponse,
FileResponseHandler,
LogLevel,
ScaffoldCmdConfig,
ScaffoldConfig,
ScaffoldConfigFile,
} from "./types"
import { handlebarsParse } from "./parser"
import { log } from "./logger"
import { resolve, wrapNoopResolver } from "./utils"
import { getGitConfig } from "./git"
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): 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 parseConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfig> {
let c: ScaffoldConfig = config
if (config.github) {
log(config, LogLevel.Info, `Loading config from github ${config.github}`)
config.config = githubPartToUrl(config.github)
}
if (config.config) {
const { configFile, key, isRemote } = parseConfigSelection(config.config, config.key)
log(config, LogLevel.Info, `Loading config from ${configFile} with key ${key}`)
const configPromise = await getConfig({
config: configFile,
isRemote,
quiet: config.quiet,
verbose: config.verbose,
})
let configImport = await resolve(configPromise, config)
if (typeof configImport.default === "function" || configImport.default instanceof Promise) {
configImport = await resolve(configImport.default, config)
}
if (!configImport[key]) {
throw new Error(`Template "${key}" not found in ${configFile}`)
}
const importedKey = configImport[key]
c = {
...config,
...importedKey,
data: {
...(importedKey as any).data,
...config.data,
},
}
}
c.data = { ...c.data, ...config.appendData }
delete config.appendData
return c
}
export function parseConfigSelection(
config: string,
key?: string,
): { configFile: string; key: string; isRemote: boolean } {
const isUrl = config.includes("://")
const hasColonToken = (!isUrl && config.includes(":")) || (isUrl && count(config, ":") > 1)
const colonIndex = config.lastIndexOf(":")
const [configFile, templateKey = "default"] = hasColonToken
? [config.substring(0, colonIndex), config.substring(colonIndex + 1)]
: [config, undefined]
const _key = (key ?? templateKey) || "default"
return { configFile, key: _key, isRemote: isUrl }
}
export function githubPartToUrl(part: string): string {
const gitUrl = new URL(`https://github.com/${part}`)
if (!gitUrl.pathname.endsWith(".git")) {
gitUrl.pathname += ".git"
}
return gitUrl.toString()
}
/** @internal */
export async function getConfig(config: ConfigLoadConfig): Promise<ScaffoldConfigFile> {
const { config: configFile, isRemote, ...logConfig } = config as Required<typeof config>
if (!isRemote) {
log(logConfig, LogLevel.Info, `Loading config from file ${configFile}`)
const absolutePath = path.resolve(process.cwd(), configFile)
return wrapNoopResolver(import(absolutePath))
}
const url = new URL(configFile)
const isHttp = url.protocol === "http:" || url.protocol === "https:"
const isGit = url.protocol === "git:" || (isHttp && url.pathname.endsWith(".git"))
if (isGit) {
return getGitConfig(url, logConfig)
}
if (!isHttp) {
throw new Error(`Unsupported protocol ${url.protocol}`)
}
return wrapNoopResolver(import(path.resolve(process.cwd(), configFile)))
}
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;
}

212
src/file.ts Normal file
View File

@@ -0,0 +1,212 @@
import path from "node:path"
import { F_OK } from "node:constants"
import { LogLevel, ScaffoldConfig } from "./types"
import fs from "node:fs/promises"
import { glob, hasMagic } from "glob"
import { log } from "./logger"
import { getOptionValueForFile } from "./config"
import { handlebarsParse } from "./parser"
import { handleErr } from "./utils"
const { stat, access, mkdir, readFile, writeFile } = fs
export async function createDirIfNotExists(dir: string, config: ScaffoldConfig): Promise<void> {
if (config.dryRun) {
log(config, LogLevel.Info, `Dry Run. Not creating dir ${dir}`)
return
}
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 path.normalize(template.replace(/\*/g, ""))
}
export function makeRelativePath(str: string): string {
return str.startsWith(path.sep) ? str.slice(1) : str
}
export function getBasePath(relPath: string): string {
return path
.resolve(process.cwd(), relPath)
.replace(process.cwd() + path.sep, "")
.replace(process.cwd(), "")
}
export async function getFileList(_config: ScaffoldConfig, template: string): Promise<string[]> {
template = template.replaceAll(/[\\]+/g, "/")
log(_config, LogLevel.Debug, `Getting file list for ${template}`)
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)
const _shouldAddGlob = !isGlob && isDirOrGlob
log(config, LogLevel.Debug, "after", { isDirOrGlob, _shouldAddGlob })
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.toString())
}
} 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)
}
})
}

48
src/git.ts Normal file
View File

@@ -0,0 +1,48 @@
import path from "node:path"
import os from "node:os"
import { log } from "./logger"
import { AsyncResolver, LogConfig, LogLevel, ScaffoldCmdConfig, ScaffoldConfig, ScaffoldConfigMap } from "./types"
import { spawn } from "node:child_process"
import { resolve, wrapNoopResolver } from "./utils"
export 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((res, reject) => {
const clone = spawn("git", ["clone", "--recurse-submodules", "--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 resolve(
async () => (await import(absolutePath)).default as ScaffoldConfigMap,
logConfig,
)
log(logConfig, LogLevel.Info, `Loaded config from git`)
log(logConfig, LogLevel.Debug, `Raw config:`, loadedConfig)
const fixedConfig: ScaffoldConfigMap = {}
for (const [k, v] of Object.entries(loadedConfig)) {
fixedConfig[k] = {
...v,
templates: v.templates.map((t) => path.resolve(tmpPath, t)),
}
}
res(wrapNoopResolver(fixedConfig))
return
}
reject(new Error(`Git clone failed with code ${code}`))
})
})
}

65
src/logger.ts Normal file
View File

@@ -0,0 +1,65 @@
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,
data: {
originalTemplate: string
relativePath: string
parsedTemplate: string
inputFilePath: string
nonGlobTemplate: string
basePath: string
isDirOrGlob: boolean
isGlob: boolean
},
): void {
log(config, LogLevel.Debug, data)
}
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)
}

126
src/parser.ts Normal file
View File

@@ -0,0 +1,126 @@
import path from "node:path"
import { DefaultHelpers, Helper, LogLevel, ScaffoldConfig } from "./types"
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,
}
function _dateHelper(date: Date, formatString: string): string
function _dateHelper(date: Date, formatString: string, durationDifference: number, durationType: keyof Duration): string
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!)
}
// splits by either non-alpha character or capital letter
function toWordParts(string: string): string[] {
return string.split(/(?=[A-Z])|[^a-zA-Z]/).filter((s) => s.length > 0)
}
function camelCase(s: string): string {
return toWordParts(s).reduce((acc, part, i) => {
if (i === 0) {
return part.toLowerCase()
}
return acc + part[0].toUpperCase() + part.slice(1).toLowerCase()
}, "")
}
function snakeCase(s: string): string {
return toWordParts(s).join("_").toLowerCase()
}
function kebabCase(s: string): string {
return toWordParts(s).join("-").toLowerCase()
}
function startCase(s: string): string {
return toWordParts(s)
.map((part) => part[0].toUpperCase() + part.slice(1).toLowerCase())
.join(" ")
}
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

@@ -4,27 +4,21 @@
*
* See [readme](README.md)
*/
import path from "path"
import path from "node: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 { defaultHelpers, registerHelpers } from "./parser"
import { log, logInitStep, logInputFile } from "./logger"
import { parseConfigFile } from "./config"
/**
* Create a scaffold using given `options`.
@@ -47,11 +41,14 @@ import { LogLevel, ScaffoldConfig } from "./types"
* Any functions you provide in `helpers` option will also be available to you to make custom formatting as you see fit
* (for example, formatting a date)
*
* For available default values, see {@link DefaultHelperKeys}.
* 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 DefaultHelperKeys}
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
*
* @category Main
*/
@@ -60,15 +57,16 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
registerHelpers(config)
try {
config.data = { name: config.name, Name: pascalCase(config.name), ...config.data }
config.data = { name: config.name, Name: defaultHelpers.pascalCase(config.name), ...config.data }
logInitStep(config)
for (let _template of config.templates) {
try {
const { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template } = await getTemplateGlobInfo(
config,
_template
_template,
)
const files = await getFileList(config, template)
log(config, LogLevel.Debug, "Iterating files", { files, template })
for (const inputFilePath of files) {
if (await isDir(inputFilePath)) {
continue
@@ -76,9 +74,9 @@ export async function Scaffold(config: ScaffoldConfig): Promise<void> {
const relPath = makeRelativePath(path.dirname(removeGlob(inputFilePath).replace(nonGlobTemplate, "")))
const basePath = getBasePath(relPath)
logInputFile(config, {
origTemplate,
relPath,
template,
originalTemplate: origTemplate,
relativePath: relPath,
parsedTemplate: template,
inputFilePath,
nonGlobTemplate,
basePath,
@@ -99,40 +97,40 @@ 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 = {
dryRun: false,
output: process.cwd(),
verbose: LogLevel.Info,
overwrite: false,
templates: [],
createSubFolder: false,
quiet: false,
config: pathOrUrl,
...config,
}
const _overrides = resolve(overrides, _cmdConfig)
const _config = await parseConfigFile(_cmdConfig)
return Scaffold({ ..._config, ..._overrides })
}
export default Scaffold

View File

@@ -3,8 +3,11 @@ import { HelperDelegate } from "handlebars/runtime"
/**
* The config object for defining a scaffolding group.
*
* @see https://github.com/chenasraf/simple-scaffold#readme
* @see {@link DefaultHelperKeys}
* @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}
*
* @category Config
*/
@@ -54,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.
*
@@ -95,7 +98,7 @@ export interface ScaffoldConfig {
* Additional helpers to add to the template parser. Provide an object whose keys are the name of the function to add,
* and the value is the helper function itself. The signature of helpers is as follows:
* ```typescript
* (text: string) => string
* (text: string, ...args: any[]) => string
* ```
*
* A full example might be:
@@ -104,27 +107,45 @@ export interface ScaffoldConfig {
* Scaffold({
* //...
* helpers: {
* upperCamelCase: (text) => camelCase(text).toUpperCase()
* upperKebabCase: (text) => kebabCase(text).toUpperCase()
* }
* })
* ```
*
* See {@link DefaultHelperKeys} for a list of all the built-in available helpers.
* Which will allow:
*
* @see https://github.com/chenasraf/simple-scaffold#helpers
* @see https://github.com/chenasraf/simple-scaffold#built-in-helpers
*/
* ```
* {{ upperKebabCase "my value" }}
* ```
*
* To transform to:
*
* ```
* MY-VALUE
* ```
*
* See {@link DefaultHelpers} for a list of all the built-in available helpers.
*
* Simple Scaffold uses Handlebars.js, so all the syntax from there is supported. See
* [their docs](https://handlebarsjs.com/guide/#custom-helpers) for more information.
*
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
* @see {@link https://chenasraf.github.io/simple-scaffold/pages/templates.html | Templates}
* */
helpers?: Record<string, Helper>
/**
* Default transformer to apply to subfolder name when using `createSubFolder: true`. Can be one of the default
* capitalization helpers, or a custom one you provide to `helpers`. Defaults to `undefined`, which means no transformation is done.
* capitalization helpers, or a custom one you provide to `helpers`. Defaults to `undefined`, which means no
* transformation is done.
*
* @see {@link createSubFolder}
* @see {@link CapitalizationHelperKeys}
* @see {@link DefaultHelperKeys}
* @see {@link CaseHelpers}
* @see {@link DefaultHelpers}
*/
subFolderNameHelper?: DefaultHelperKeys | string
subFolderNameHelper?: DefaultHelpers | string
/**
* This callback runs right before content is being written to the disk. If you supply this function, you may return
@@ -137,13 +158,13 @@ export interface ScaffoldConfig {
* @param rawContent The original template before token replacement
* @param outputPath The final output path of the processed file
*
* @returns {Promise<String | Buffer | undefined> | String | Buffer | undefined} The final output of the file contents-only, after further modifications -
* or `undefined` to use the original content (i.e. `content.toString()`)
* @returns {Promise<String | Buffer | undefined> | String | Buffer | undefined} The final output of the file
* contents-only, after further modifications - or `undefined` to use the original content (i.e. `content.toString()`)
*/
beforeWrite?(
content: Buffer,
rawContent: Buffer,
outputPath: string
outputPath: string,
): string | Buffer | undefined | Promise<string | Buffer | undefined>
}
@@ -164,13 +185,14 @@ export interface ScaffoldConfig {
* | `upperCase` | `{{ upperCase name }}` | MY NAME |
* | `lowerCase` | `{{ lowerCase name }}` | my name |
*
* @see {@link DefaultHelpers}
* @see {@link DateHelpers}
* @see {@link ScaffoldConfig}
* @see {@link subFolderNameHelper}
* @see {@link DefaultHelperKeys}
* @see {@link ScaffoldConfig.subFolderNameHelper}
*
* @category Helpers
*/
export type CapitalizationHelperKeys =
export type CaseHelpers =
| "camelCase"
| "hyphenCase"
| "kebabCase"
@@ -191,25 +213,48 @@ export type CapitalizationHelperKeys =
* | `date` (with offset) | Custom date with format, and with offset | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" -1 "days" }}` | `2041-31-12 15:00` |
* | `date` (with date from `--data`) | Custom date with format, with data from the `data` config option | `{{ date myCustomDate "yyyy-MM-dd HH:mm" }}` | `2042-01-01 12:00` |
*
* @see {@link DefaultHelperKeys}
* Further details:
*
* - We use [`date-fns`](https://date-fns.org/docs/) for parsing/manipulating the dates. If you want
* more information on the date tokens to use, refer to
* [their format documentation](https://date-fns.org/docs/format).
*
* - The date helper format takes the following arguments:
*
* ```typescript
* (
* date: string,
* format: string,
* offsetAmount?: number,
* offsetType?: "years" | "months" | "weeks" | "days" | "hours" | "minutes" | "seconds"
* )
* ```
*
* - **The now helper** (for current time) takes the same arguments, minus the first one (`date`) as it is implicitly
* the current date.
*
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link ScaffoldConfig}
*
* @category Helpers
*/
export type DateHelperKeys = "date" | "now"
export type DateHelpers = "date" | "now"
/**
* The names of all the available helper functions in templates.
* Simple-Scaffold provides some built-in text transformation filters usable by handleBars.
* Simple-Scaffold provides some built-in text transformation filters usable by Handlebars.js.
*
* For example, you may use `{{ snakeCase name }}` inside a template file or filename, and it will
* replace `My Name` with `my_name` when producing the final value.
*
* @see {@link CapitalizationHelperKeys}
* @see {@link DateHelperKeys}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
* @see {@link ScaffoldConfig}
*
* @category Helpers
*/
export type DefaultHelperKeys = CapitalizationHelperKeys | DateHelperKeys
export type DefaultHelpers = CaseHelpers | DateHelpers
/**
* Helper function, see https://handlebarsjs.com/guide/#custom-helpers
@@ -280,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"> & { isRemote: boolean }
/** @internal */
export type MinimalConfig = Pick<ScaffoldCmdConfig, "name" | "key">

View File

@@ -1,374 +1,17 @@
import path from "path"
import { F_OK } from "constants"
import { DefaultHelperKeys, 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<DefaultHelperKeys, 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 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)
}
export async function createDirIfNotExists(dir: string, config: ScaffoldConfig): Promise<void> {
const parentDir = path.dirname(dir)
if (!(await pathExists(parentDir))) {
await createDirIfNotExists(parentDir, config)
export function wrapNoopResolver<T, R = T>(value: Resolver<T, R>): Resolver<T, R> {
if (typeof value === "function") {
return value
}
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
const nonGlobTemplate = isGlob ? removeGlob(template) : template
const isDirOrGlob = isGlob ? true : await isDir(template)
log(config, LogLevel.Debug, "after isDir", isDirOrGlob)
const _shouldAddGlob = !isGlob && isDirOrGlob
const origTemplate = template
if (_shouldAddGlob) {
_template = path.join(template, "**", "*")
}
return { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template: _template }
}
export interface OutputFileInfo {
inputPath: string
outputPathOpt: string
outputDir: string
outputPath: string
exists: boolean
}
export async function getTemplateFileInfo(
config: ScaffoldConfig,
{ templatePath, basePath }: { templatePath: string; basePath: string }
): Promise<OutputFileInfo> {
const inputPath = path.resolve(process.cwd(), templatePath)
const outputPathOpt = getOptionValueForFile(config, inputPath, config.output)
const outputDir = getOutputDir(config, outputPathOpt, basePath)
const outputPath = handlebarsParse(config, path.join(outputDir, path.basename(inputPath)), {
isPath: true,
}).toString()
const exists = await pathExists(outputPath)
return { inputPath, outputPathOpt, outputDir, outputPath, exists }
}
export async function copyFileTransformed(
config: ScaffoldConfig,
{
exists,
overwrite,
outputPath,
inputPath,
}: {
exists: boolean
overwrite: boolean
outputPath: string
inputPath: string
}
): Promise<void> {
if (!exists || overwrite) {
if (exists && overwrite) {
log(config, LogLevel.Info, `File ${outputPath} exists, overwriting`)
}
const templateBuffer = await readFile(inputPath)
const unprocessedOutputContents = handlebarsParse(config, templateBuffer)
const finalOutputContents =
(await config.beforeWrite?.(unprocessedOutputContents, templateBuffer, outputPath)) ?? unprocessedOutputContents
if (!config.dryRun) {
await writeFile(outputPath, finalOutputContents)
log(config, LogLevel.Info, "Done.")
} else {
log(config, LogLevel.Info, "Content output:")
log(config, LogLevel.Info, finalOutputContents)
}
} else if (exists) {
log(config, LogLevel.Info, `File ${outputPath} already exists, skipping`)
}
}
export function getOutputDir(config: ScaffoldConfig, outputPathOpt: string, basePath: string): string {
return path.resolve(
process.cwd(),
...([
outputPathOpt,
basePath,
config.createSubFolder
? config.subFolderNameHelper
? handlebarsParse(config, `{{ ${config.subFolderNameHelper} name }}`).toString()
: config.name
: undefined,
].filter(Boolean) as string[])
)
}
export function logInputFile(
config: ScaffoldConfig,
{
origTemplate,
relPath,
template,
inputFilePath,
nonGlobTemplate,
basePath,
isDirOrGlob,
isGlob,
}: {
origTemplate: string
relPath: string
template: string
inputFilePath: string
nonGlobTemplate: string
basePath: string
isDirOrGlob: boolean
isGlob: boolean
}
): void {
log(
config,
LogLevel.Debug,
`\nprocess.cwd(): ${process.cwd()}`,
`\norigTemplate: ${origTemplate}`,
`\nrelPath: ${relPath}`,
`\ntemplate: ${template}`,
`\ninputFilePath: ${inputFilePath}`,
`\nnonGlobTemplate: ${nonGlobTemplate}`,
`\nbasePath: ${basePath}`,
`\nisDirOrGlob: ${isDirOrGlob}`,
`\nisGlob: ${isGlob}`,
`\n`
)
}
export function logInitStep(config: ScaffoldConfig): void {
log(config, LogLevel.Debug, "Full config:", {
name: config.name,
templates: config.templates,
output: config.output,
createSubfolder: config.createSubFolder,
data: config.data,
overwrite: config.overwrite,
quiet: config.quiet,
subFolderTransformHelper: config.subFolderNameHelper,
helpers: Object.keys(config.helpers ?? {}),
verbose: `${config.verbose} (${Object.keys(LogLevel).find(
(k) => (LogLevel[k as any] as unknown as number) === config.verbose!
)})`,
})
log(config, LogLevel.Info, "Data:", config.data)
return (_) => value
}

149
tests/config.test.ts Normal file
View File

@@ -0,0 +1,149 @@
import { ScaffoldCmdConfig } from "../src/types"
import * as config from "../src/config"
import { resolve } from "../src/utils"
// @ts-ignore
import * as configFile from "../scaffold.config"
jest.mock("../src/git", () => {
return {
__esModule: true,
...jest.requireActual("../src/git"),
getGitConfig: () => {
return Promise.resolve(blankCliConf)
},
}
})
const { githubPartToUrl, parseAppendData, parseConfigFile, parseConfigSelection } = config
const blankCliConf: ScaffoldCmdConfig = {
verbose: 0,
name: "",
output: "",
templates: [],
data: { name: "test" },
overwrite: false,
createSubFolder: false,
dryRun: false,
quiet: false,
}
describe("config", () => {
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" })
})
})
describe("githubPartToUrl", () => {
test("works", () => {
expect(githubPartToUrl("chenasraf/simple-scaffold")).toEqual("https://github.com/chenasraf/simple-scaffold.git")
expect(githubPartToUrl("chenasraf/simple-scaffold.git")).toEqual(
"https://github.com/chenasraf/simple-scaffold.git",
)
})
})
describe("parseConfigSelection", () => {
test("no key", () => {
expect(parseConfigSelection("scaffold.config.js")).toEqual({
configFile: "scaffold.config.js",
key: "default",
isRemote: false,
})
})
test("separate key", () => {
expect(parseConfigSelection("scaffold.config.js", "component")).toEqual({
configFile: "scaffold.config.js",
key: "component",
isRemote: false,
})
})
test("key override", () => {
expect(parseConfigSelection("scaffold.config.js:component", "main")).toEqual({
configFile: "scaffold.config.js",
key: "main",
isRemote: false,
})
})
test("isRemote: false", () => {
expect(parseConfigSelection("scaffold.config.js", "main")).toEqual({
configFile: "scaffold.config.js",
key: "main",
isRemote: false,
})
})
test("isRemote: true", () => {
expect(
parseConfigSelection("https://github.com/chenasraf/simple-scaffold.git#scaffold.config.js:component", "main"),
).toEqual({
configFile: "https://github.com/chenasraf/simple-scaffold.git#scaffold.config.js",
key: "main",
isRemote: true,
})
})
})
describe("parseConfigFile", () => {
test("normal config does not change", async () => {
expect(
await parseConfigFile({
...blankCliConf,
}),
).toEqual(blankCliConf)
})
describe("appendData", () => {
test("appends", async () => {
const result = await parseConfigFile({
...blankCliConf,
appendData: { key: "value" },
})
expect(result?.data?.key).toEqual("value")
})
test("overwrites existing value", async () => {
const result = await parseConfigFile({
...blankCliConf,
data: { num: "123" },
appendData: { num: "1234" },
})
expect(result?.data?.num).toEqual("1234")
})
})
})
describe("getConfig", () => {
test("gets git config", async () => {
const resultFn = await config.getConfig({
config: "https://github.com/chenasraf/simple-scaffold.git",
isRemote: true,
quiet: true,
verbose: 0,
})
const result = await resolve(resultFn, blankCliConf)
expect(result).toEqual(blankCliConf)
})
test("gets local file config", async () => {
const resultFn = await config.getConfig({
config: "scaffold.config.js",
isRemote: false,
quiet: true,
verbose: 0,
})
const result = await resolve(resultFn, {} as any)
expect(result).toEqual(configFile)
})
})
})

148
tests/parser.test.ts Normal file
View File

@@ -0,0 +1,148 @@
import { ScaffoldCmdConfig, ScaffoldConfig } from "../src/types"
import path from "node:path"
import * as dateFns from "date-fns"
import { OptionsBase } from "massarg/types"
import { dateHelper, defaultHelpers, handlebarsParse, nowHelper } from "../src/parser"
const blankConf: ScaffoldConfig = {
verbose: 0,
name: "",
output: "",
templates: [],
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("parser", () => {
describe("handlebarsParse", () => {
let origSep: any
describe("windows paths", () => {
beforeAll(() => {
origSep = path.sep
Object.defineProperty(path, "sep", { value: "\\" })
})
afterAll(() => {
Object.defineProperty(path, "sep", { value: origSep })
})
test("should work for windows paths", async () => {
expect(handlebarsParse(blankConf, "C:\\exports\\{{name}}.txt", { isPath: true }).toString()).toEqual(
"C:\\exports\\test.txt",
)
})
})
describe("non-windows paths", () => {
beforeAll(() => {
origSep = path.sep
Object.defineProperty(path, "sep", { value: "/" })
})
afterAll(() => {
Object.defineProperty(path, "sep", { value: origSep })
})
test("should work for non-windows paths", async () => {
expect(handlebarsParse(blankConf, "/home/test/{{name}}.txt", { isPath: true })).toEqual(
Buffer.from("/home/test/test.txt"),
)
})
})
test("should not do path escaping on non-path compiles", async () => {
expect(
handlebarsParse(
{ ...blankConf, data: { ...blankConf.data, escaped: "value" } },
"/home/test/{{name}} \\{{escaped}}.txt",
{
isPath: false,
},
),
).toEqual(Buffer.from("/home/test/test {{escaped}}.txt"))
})
})
describe("Helpers", () => {
describe("string helpers", () => {
test("camelCase", () => {
expect(defaultHelpers.camelCase("test string")).toEqual("testString")
expect(defaultHelpers.camelCase("test_string")).toEqual("testString")
expect(defaultHelpers.camelCase("test-string")).toEqual("testString")
expect(defaultHelpers.camelCase("testString")).toEqual("testString")
expect(defaultHelpers.camelCase("TestString")).toEqual("testString")
expect(defaultHelpers.camelCase("Test____String")).toEqual("testString")
})
test("pascalCase", () => {
expect(defaultHelpers.pascalCase("test string")).toEqual("TestString")
expect(defaultHelpers.pascalCase("test_string")).toEqual("TestString")
expect(defaultHelpers.pascalCase("test-string")).toEqual("TestString")
expect(defaultHelpers.pascalCase("testString")).toEqual("TestString")
expect(defaultHelpers.pascalCase("TestString")).toEqual("TestString")
expect(defaultHelpers.pascalCase("Test____String")).toEqual("TestString")
})
test("snakeCase", () => {
expect(defaultHelpers.snakeCase("test string")).toEqual("test_string")
expect(defaultHelpers.snakeCase("test_string")).toEqual("test_string")
expect(defaultHelpers.snakeCase("test-string")).toEqual("test_string")
expect(defaultHelpers.snakeCase("testString")).toEqual("test_string")
expect(defaultHelpers.snakeCase("TestString")).toEqual("test_string")
expect(defaultHelpers.snakeCase("Test____String")).toEqual("test_string")
})
test("kebabCase", () => {
expect(defaultHelpers.kebabCase("test string")).toEqual("test-string")
expect(defaultHelpers.kebabCase("test_string")).toEqual("test-string")
expect(defaultHelpers.kebabCase("test-string")).toEqual("test-string")
expect(defaultHelpers.kebabCase("testString")).toEqual("test-string")
expect(defaultHelpers.kebabCase("TestString")).toEqual("test-string")
expect(defaultHelpers.kebabCase("Test____String")).toEqual("test-string")
})
test("startCase", () => {
expect(defaultHelpers.startCase("test string")).toEqual("Test String")
expect(defaultHelpers.startCase("test_string")).toEqual("Test String")
expect(defaultHelpers.startCase("test-string")).toEqual("Test String")
expect(defaultHelpers.startCase("testString")).toEqual("Test String")
expect(defaultHelpers.startCase("TestString")).toEqual("Test String")
expect(defaultHelpers.startCase("Test____String")).toEqual("Test String")
})
})
describe("date helpers", () => {
describe("now", () => {
test("should work without extra params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(nowHelper(fmt)).toEqual(dateFns.format(now, fmt))
})
})
describe("date", () => {
test("should work with no offset params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(dateHelper(now.toISOString(), fmt)).toEqual(dateFns.format(now, fmt))
})
test("should work with offset params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(dateHelper(now.toISOString(), fmt, -1, "days")).toEqual(
dateFns.format(dateFns.add(now, { days: -1 }), fmt),
)
expect(dateHelper(now.toISOString(), fmt, 1, "months")).toEqual(
dateFns.format(dateFns.add(now, { months: 1 }), fmt),
)
})
})
})
})
})

View File

@@ -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"
@@ -53,7 +53,7 @@ const fileStructHelpers = {
input: {
defaults: defaultHelperNames.reduce<Record<string, string>>(
(all, cur) => ({ ...all, [cur + ".txt"]: `{{ ${cur} name }}` }),
{}
{},
),
custom: {
"add1.txt": "{{ add1 name }}",
@@ -117,7 +117,7 @@ describe("Scaffold", () => {
const data = readFileSync(join(process.cwd(), "output", "app_name", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
})
}),
)
describe(
@@ -135,7 +135,7 @@ describe("Scaffold", () => {
const dataBin = readFileSync(join(process.cwd(), "output", "app_name.bin"))
expect(dataBin).toEqual(fileStructWithBinary.input["{{name}}.bin"])
})
})
}),
)
describe(
@@ -183,7 +183,7 @@ describe("Scaffold", () => {
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my value is 2")
})
})
}),
)
describe(
@@ -206,7 +206,7 @@ describe("Scaffold", () => {
templates: ["non-existing-input"],
data: { value: "1" },
verbose: 0,
})
}),
).rejects.toThrow()
await expect(
@@ -216,12 +216,12 @@ describe("Scaffold", () => {
templates: ["non-existing-input/non-existing-file.txt"],
data: { value: "1" },
verbose: 0,
})
}),
).rejects.toThrow()
expect(() => readFileSync(join(process.cwd(), "output", "app_name.txt"))).toThrow()
})
})
}),
)
describe(
@@ -248,7 +248,7 @@ describe("Scaffold", () => {
expect(() => readFileSync(join(process.cwd(), "output", "app_name.txt"))).toThrow()
})
})
}),
)
describe(
@@ -265,7 +265,7 @@ describe("Scaffold", () => {
const data = readFileSync(join(process.cwd(), "/custom-output/app_name/app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
})
}),
)
describe(
@@ -294,7 +294,7 @@ describe("Scaffold", () => {
expect(oneDeepFile.toString()).toEqual("Hello, my value is 1")
expect(twoDeepFile.toString()).toEqual("Hi! My value is actually NOT 1!")
})
})
}),
)
describe(
@@ -328,7 +328,7 @@ describe("Scaffold", () => {
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
})
}),
)
describe(
"date helpers",
@@ -355,19 +355,19 @@ describe("Scaffold", () => {
// "custom.txt": "Custom date is {{ date customDate 'mmm' }}, time is {{ date customDate 'HH:mm' }}",
expect(nowFile.toString()).toEqual(
`Today is ${dateFns.format(now, "mmm")}, time is ${dateFns.format(now, "HH:mm")}`
`Today is ${dateFns.format(now, "mmm")}, time is ${dateFns.format(now, "HH:mm")}`,
)
expect(offsetFile.toString()).toEqual(
`Yesterday was ${dateFns.format(yesterday, "mmm")}, time is ${dateFns.format(yesterday, "HH:mm")}`
`Yesterday was ${dateFns.format(yesterday, "mmm")}, time is ${dateFns.format(yesterday, "HH:mm")}`,
)
expect(customFile.toString()).toEqual(
`Custom date is ${dateFns.format(dateFns.parseISO(customDate), "mmm")}, time is ${dateFns.format(
dateFns.parseISO(customDate),
"HH:mm"
)}`
"HH:mm",
)}`,
)
})
})
}),
)
describe(
"custom helpers",
@@ -392,7 +392,7 @@ describe("Scaffold", () => {
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
})
}),
)
describe(
"transform subfolder",
@@ -440,7 +440,7 @@ describe("Scaffold", () => {
const data = readFileSync(join(process.cwd(), "output", "REPLACED", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
})
}),
)
describe(
"before write",
@@ -479,7 +479,7 @@ describe("Scaffold", () => {
"Hello, my app is app_name".toUpperCase(),
fileStructNormal.input["{{name}}.txt"],
join(process.cwd(), "output", "app_name.txt"),
].join(", ")
].join(", "),
)
})
test("should work with undefined response custom callback", async () => {
@@ -497,6 +497,6 @@ describe("Scaffold", () => {
const data = readFileSync(join(process.cwd(), "output", "app_name.txt"))
expect(data.toString()).toEqual("Hello, my app is app_name")
})
})
}),
)
})

View File

@@ -1,91 +1,21 @@
import { dateHelper, handlebarsParse, nowHelper } from "../src/utils"
import { ScaffoldConfig } from "../src/types"
import path from "path"
import * as dateFns from "date-fns"
import { handleErr, resolve } from "../src/utils"
const blankConf: ScaffoldConfig = {
verbose: 0,
name: "",
output: "",
templates: [],
data: { name: "test" },
}
describe("Utils", () => {
describe("handlebarsParse", () => {
let origSep: any
describe("windows paths", () => {
beforeAll(() => {
origSep = path.sep
Object.defineProperty(path, "sep", { value: "\\" })
})
afterAll(() => {
Object.defineProperty(path, "sep", { value: origSep })
})
test("should work for windows paths", async () => {
expect(handlebarsParse(blankConf, "C:\\exports\\{{name}}.txt", { isPath: true })).toEqual(
Buffer.from("C:\\exports\\test.txt")
)
})
describe("utils", () => {
describe("resolve", () => {
test("should resolve function", () => {
expect(resolve(() => 1, null)).toBe(1)
expect(resolve((x) => x, 2)).toBe(2)
})
describe("non-windows paths", () => {
beforeAll(() => {
origSep = path.sep
Object.defineProperty(path, "sep", { value: "/" })
})
afterAll(() => {
Object.defineProperty(path, "sep", { value: origSep })
})
test("should work for non-windows paths", async () => {
expect(handlebarsParse(blankConf, "/home/test/{{name}}.txt", { isPath: true })).toEqual(
Buffer.from("/home/test/test.txt")
)
})
})
test("should not do path escaping on non-path compiles", async () => {
expect(
handlebarsParse(
{ ...blankConf, data: { ...blankConf.data, escaped: "value" } },
"/home/test/{{name}} \\{{escaped}}.txt",
{
isPath: false,
}
)
).toEqual(Buffer.from("/home/test/test {{escaped}}.txt"))
test("should resolve value", () => {
expect(resolve(1, null)).toBe(1)
expect(resolve(2, 1)).toBe(2)
})
})
describe("Helpers", () => {
describe("date helpers", () => {
describe("now", () => {
test("should work without extra params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(nowHelper(fmt)).toEqual(dateFns.format(now, fmt))
})
})
describe("date", () => {
test("should work with no offset params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(dateHelper(now.toISOString(), fmt)).toEqual(dateFns.format(now, fmt))
})
test("should work with offset params", () => {
const now = new Date()
const fmt = "yyyy-MM-dd HH:mm"
expect(dateHelper(now.toISOString(), fmt, -1, "days")).toEqual(
dateFns.format(dateFns.add(now, { days: -1 }), fmt)
)
expect(dateHelper(now.toISOString(), fmt, 1, "months")).toEqual(
dateFns.format(dateFns.add(now, { months: 1 }), fmt)
)
})
})
describe("handleErr", () => {
test("should throw error", () => {
expect(() => handleErr({ name: "test", message: "test" })).toThrow()
expect(() => handleErr(null as never)).not.toThrow()
})
})
})

View File

@@ -1,23 +1,20 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2019",
"target": "ES2022",
"module": "commonjs",
"moduleResolution": "node",
"esModuleInterop": true,
"lib": [
"ES2019",
],
"lib": ["ES2022"],
"declaration": true,
"outDir": "dist",
"strict": true,
"sourceMap": true,
"removeComments": false,
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"src/index.ts",
"src/cmd.ts",
],
"exclude": [
"tests/*"
]
"include": ["src/index.ts", "src/cmd.ts"],
"exclude": ["tests/*"]
}

63
typedoc.config.js Normal file
View File

@@ -0,0 +1,63 @@
const path = require("node: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,26 +0,0 @@
{
"name": "Simple Scaffold",
"entryPoints": [
"src/index.ts"
],
"includeVersion": true,
"categorizeByGroup": false,
"sort": [
"visibility"
],
"categoryOrder": [
"Main",
"*"
],
"theme": "doc-theme",
"githubPages": true,
"entryPointStrategy": "expand",
"out": "docs",
"excludePrivate": true,
"excludeProtected": true,
"excludeInternal": true,
"gaID": "GTM-KHQS9TQ",
"validation": {
"invalidLink": true
}
}

3009
yarn.lock

File diff suppressed because it is too large Load Diff