Compare commits

..

461 Commits

Author SHA1 Message Date
dependabot[bot]
bfb6c40ba5 build(deps): bump the npm_and_yarn group across 2 directories with 2 updates
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).
Bumps the npm_and_yarn group with 1 update in the /docs directory: [brace-expansion](https://github.com/juliangruber/brace-expansion).


Updates `vite` from 8.0.3 to 8.0.5
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.5/packages/vite)

Updates `brace-expansion` from 1.1.12 to 1.1.13
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.13)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.5
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 23:50:50 +03:00
8834e49085 chore(master): release 3.1.1 2026-03-27 23:24:03 +03:00
c797893eb5 docs: rewrite docs 2026-03-26 23:56:54 +02:00
5b5a1fa9a4 docs: rewrite readme 2026-03-26 23:42:45 +02:00
f5ed38ad95 fix(deps): update dependencies 2026-03-26 23:30:21 +02:00
6f15f3db14 build: update release workflows 2026-03-23 17:28:49 +02:00
02dcbdaf13 chore(master): release 3.1.0 2026-03-23 17:21:02 +02:00
970e8e0b37 build: fix build warnings & reduce bundle size 2026-03-23 17:17:23 +02:00
c860e4644a fix: interactive inputs with existing config/cli options 2026-03-23 17:14:25 +02:00
bb0248f91a feat: update all ouput logging 2026-03-23 17:02:33 +02:00
972d199fbb feat: config validation 2026-03-23 16:53:55 +02:00
b5fd1df821 feat: scaffoldignore 2026-03-23 16:41:08 +02:00
f6408f221d feat: select, confirm and number input types 2026-03-23 16:32:41 +02:00
0a4ead17c0 feat: after scaffold hook 2026-03-23 16:28:40 +02:00
7926b15053 feat: init command 2026-03-23 16:22:43 +02:00
9cdea1c5ea build: add lint-staged 2026-03-23 16:18:21 +02:00
4a79961ae5 build: update node version 2026-03-23 14:39:31 +02:00
51f422cc90 chore(master): release 3.0.0 2026-03-23 14:25:33 +02:00
93f3a4caaf chore(deps): update docs dependencies 2026-03-23 12:37:27 +02:00
04e7e895d7 feat: add .scaffold.{ext} as auto-detected file 2026-03-23 12:34:48 +02:00
68e6d17fa9 feat: auto-detect config file
Release-As: 3.0.0
2026-03-23 12:29:57 +02:00
d64dd4f0e7 feat: predefined data inputs 2026-03-23 12:27:55 +02:00
519ef273ac feat: interactive inputs 2026-03-23 12:16:24 +02:00
1431fda3db docs: fixes 2026-03-23 12:16:24 +02:00
2229a9cda1 refactor: reorganize and simplify all logic 2026-03-23 11:57:05 +02:00
1f80a50185 docs: update README.md 2026-03-23 11:49:18 +02:00
e82827d909 build: update workflows 2026-03-23 11:45:52 +02:00
2e49448e59 docs: update docs build 2026-03-23 11:39:47 +02:00
0ffd7ef788 build: migrate to vite+vitest 2026-03-23 10:45:57 +02:00
d16fb17c38 test: add comprehensive tests 2026-03-23 10:38:00 +02:00
af33c059b9 fix: string helpers to words parts conversion 2026-03-23 10:37:54 +02:00
d487d36b04 chore(deps): update dependencies 2026-03-23 10:24:27 +02:00
429f12d1b8 chore(master): release 2.3.3 2025-06-19 07:50:55 +03:00
dcba30689b chore: update formatting & lints 2025-06-19 01:28:15 +03:00
7745385573 chore: update dependencies 2025-06-19 01:28:03 +03:00
29f2afe097 test: add cli precedence test 2025-06-19 01:27:50 +03:00
4b0b4e7380 fix: config CLI precedence over file 2025-06-19 00:51:40 +03:00
Chen Asraf
c1536839e3 chore(master): release 2.3.2 2024-10-27 03:32:33 +02:00
7e029fd122 chore: update deps 2024-10-27 01:18:18 +02:00
41f4ca52f1 fix: template config from CLI 2024-10-27 01:12:43 +02:00
Chen Asraf
78d6bf186d chore(master): release 2.3.1 2024-10-03 14:16:18 +03:00
Chen Asraf
80c92bfe84 fix: strip tmpDir from output dir (#108)
* fix: strip tmpDir from output dir

* ci: test PRs

* fix: cmd

* fix: use relative path for replacement

* chore: remove todo
2024-10-03 14:12:30 +03:00
162cc8cec1 ci: remove develop branch 2024-09-18 00:27:51 +03:00
Chen Asraf
db6177c200 chore(develop): release 2.3.0 2024-09-18 00:12:00 +03:00
ae64db846f chore: add coverage script 2024-09-18 00:12:00 +03:00
89dc43c73d fix: exclude globs
refactor: split main file iteration to 2 steps
2024-09-17 23:57:48 +03:00
2c43dc4daf build: update target 2024-09-17 23:57:48 +03:00
f4c907e6c9 ci: fix build 2024-09-17 23:57:48 +03:00
a275e688d4 ci: use release-please 2024-09-17 23:57:48 +03:00
ff4ebf0a5b test: add color tests 2024-09-17 23:57:48 +03:00
ab9322e1ab feat: remove chalk dependency 2024-09-17 23:57:48 +03:00
semantic-release-bot
35f0d014d9 chore(release): 2.2.2 [skip ci]
## [2.2.2](https://github.com/chenasraf/simple-scaffold/compare/v2.2.1...v2.2.2) (2024-08-27)

### Bug Fixes

* homepage url ([daaefaf](daaefaf54e))
2024-08-27 13:07:31 +00:00
8ad8cb4be1 chore: update dependencies 2024-08-27 16:06:56 +03:00
daaefaf54e fix: homepage url 2024-08-27 16:06:56 +03:00
aefba4b773 docs: fix property names 2024-08-27 16:06:56 +03:00
dependabot[bot]
8457f0996a build(deps): bump ws
Bumps the npm_and_yarn group with 1 update in the /docs directory: [ws](https://github.com/websockets/ws).


Updates `ws` from 7.5.9 to 7.5.10
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-27 16:06:56 +03:00
semantic-release-bot
adc95809ba chore(release): 2.2.1 [skip ci]
## [2.2.1](https://github.com/chenasraf/simple-scaffold/compare/v2.2.0...v2.2.1) (2024-04-21)

### Bug Fixes

* beforeWrite from config files ([98b326c](98b326c843))
* use console.info for handlebars parse warning ([19e7b0f](19e7b0f0c3))
2024-04-21 20:38:09 +00:00
98b326c843 fix: beforeWrite from config files 2024-04-21 23:37:35 +03:00
ddc115a037 chore: update dependencies 2024-04-21 23:37:35 +03:00
19e7b0f0c3 fix: use console.info for handlebars parse warning 2024-04-21 23:37:35 +03:00
dependabot[bot]
f883571daa build(deps): bump express from 4.18.2 to 4.19.2 in /docs
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 23:37:35 +03:00
Chen Asraf
be3068a533 ci: update doc workflow permissions 2024-04-21 23:37:35 +03:00
dependabot[bot]
8acc660dea build(deps): bump the npm_and_yarn group across 1 directory with 2 updates
Bumps the npm_and_yarn group with 2 updates in the /docs directory: [follow-redirects](https://github.com/follow-redirects/follow-redirects) and [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware).


Updates `follow-redirects` from 1.15.5 to 1.15.6
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.5...v1.15.6)

Updates `webpack-dev-middleware` from 5.3.3 to 5.3.4
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
  dependency-group: npm_and_yarn-security-group
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
  dependency-group: npm_and_yarn-security-group
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-21 23:37:35 +03:00
semantic-release-bot
df6c351cb0 chore(release): 2.2.0 [skip ci]
# [2.2.0](https://github.com/chenasraf/simple-scaffold/compare/v2.1.0...v2.2.0) (2024-02-23)

### Features

* `list` command ([d579c09](d579c09c11))
* add `--before-write` cli option ([#89](https://github.com/chenasraf/simple-scaffold/issues/89)) ([5f810e2](5f810e2116))
2024-02-23 22:42:32 +00:00
Chen Asraf
5f810e2116 feat: add --before-write cli option (#89)
* feat: add `--before-write` cli option

* refactor: cleanup before write wrapper

* docs: add before-write docs
2024-02-24 00:41:56 +02:00
d579c09c11 feat: list command 2024-02-24 00:41:56 +02:00
3765398ab9 ci: attemp to fix pack cmd 2024-02-24 00:41:56 +02:00
semantic-release-bot
9be8a8b71b chore(release): 2.1.0 [skip ci]
# [2.1.0](https://github.com/chenasraf/simple-scaffold/compare/v2.0.2...v2.1.0) (2024-02-12)

### Features

* support directory in --config flag ([e48b832](e48b832e0b))
* support providing name in config ([4e7ac34](4e7ac34db9))
2024-02-12 23:39:35 +00:00
5cb8c3c081 docs: update navbar logo 2024-02-13 01:39:04 +02:00
3b52c255f0 chore: dry 2024-02-13 01:39:04 +02:00
80cf2076b0 chore: update dependencies 2024-02-13 01:39:04 +02:00
4fd710b763 test: fix tests 2024-02-13 01:39:04 +02:00
4e7ac34db9 feat: support providing name in config 2024-02-13 01:39:04 +02:00
e48b832e0b feat: support directory in --config flag 2024-02-13 01:39:04 +02:00
06ffa656ae docs: update templates page 2024-02-13 01:39:04 +02:00
919fd54ebb docs: usage page order 2024-02-13 01:39:04 +02:00
semantic-release-bot
dbc3283d5a chore(release): 2.0.2 [skip ci]
## [2.0.2](https://github.com/chenasraf/simple-scaffold/compare/v2.0.1...v2.0.2) (2024-02-04)

### Bug Fixes

* try to await scaffold before finally ([1b70897](1b70897f98))
2024-02-04 14:20:58 +00:00
Chen Asraf
0e567ec56f chore: show handlebars fail on default log level
fixes #86
2024-02-04 16:20:25 +02:00
semantic-release-bot
0ec671fe83 chore(release): 2.0.2-pre.1 [skip ci]
## [2.0.2-pre.1](https://github.com/chenasraf/simple-scaffold/compare/v2.0.1...v2.0.2-pre.1) (2024-02-03)

### Bug Fixes

* try to await scaffold before finally ([b0a6bf7](b0a6bf7021))
2024-02-04 16:20:25 +02:00
1b70897f98 fix: try to await scaffold before finally 2024-02-04 16:20:25 +02:00
43c9e8748f docs: update readme 2024-02-04 16:20:25 +02:00
65b14bc707 docs: update logo 2024-02-04 16:20:25 +02:00
f583af662c docs: update readme 2024-02-04 16:20:25 +02:00
600cc78186 docs: add logo, update docs 2024-02-04 16:20:25 +02:00
b4aea804cb docs: update readme 2024-02-04 16:20:25 +02:00
795635dc61 docs: update readme 2024-02-04 16:20:25 +02:00
6a026ce1a1 chore: version output + docs update 2024-02-04 16:20:25 +02:00
268e4d973c docs: fix github link 2024-02-04 16:20:25 +02:00
a403d9df9b ci: clean up release config 2024-02-04 16:20:25 +02:00
298beff355 docs: fix edit link 2024-02-04 16:20:25 +02:00
8f1d58f2c2 ci: fix pack step order 2024-02-04 16:20:25 +02:00
b10d69d2fe chore: update package.json spec 2024-02-04 16:20:25 +02:00
semantic-release-bot
069c890ca3 chore(release): 2.0.1 [skip ci]
## [2.0.1](https://github.com/chenasraf/simple-scaffold/compare/v2.0.0...v2.0.1) (2024-02-02)

### Bug Fixes

* log level flag ([5d7f449](5d7f449050))
* rm tmp dir too early ([4aa52c8](4aa52c84bd))
2024-02-02 00:48:01 +00:00
5d7f449050 fix: log level flag 2024-02-02 02:47:27 +02:00
cd29bd0521 docs: fix migration docs 2024-02-02 02:47:27 +02:00
c9e5dad746 test: fix tests 2024-02-02 02:47:27 +02:00
18f73b3eca build: try to pack in semantic-release 2024-02-02 02:47:27 +02:00
4aa52c84bd fix: rm tmp dir too early 2024-02-02 02:47:27 +02:00
f748125ae6 docs: update changelog 2024-02-02 02:47:27 +02:00
semantic-release-bot
43afe60ce8 chore(release): 2.0.0 [skip ci]
# [2.0.0](https://github.com/chenasraf/simple-scaffold/compare/v1.10.0...v2.0.0) (2024-01-31)

* fix!: version number ([bc0a18d](bc0a18dce0))

### BREAKING CHANGES

* see changelog
2024-01-31 22:27:49 +00:00
bc0a18dce0 fix!: version number
BREAKING CHANGE: see changelog
2024-02-01 00:27:12 +02:00
semantic-release-bot
179fabc579 chore(release): 1.10.0 [skip ci]
# [1.10.0](https://github.com/chenasraf/simple-scaffold/compare/v1.9.0...v1.10.0) (2024-01-31)

### Bug Fixes

* remove gh flag ([939200c](939200c9f2))
* tests ([ff92fd7](ff92fd7607))

### Features

* try multiple default config files ([89aacb5](89aacb58fd))
2024-01-31 22:22:21 +00:00
909fe5bbea fix!: version number 2024-02-01 00:21:49 +02:00
semantic-release-bot
bc7d687977 chore(release): 1.10.0 [skip ci]
# [1.10.0](https://github.com/chenasraf/simple-scaffold/compare/v1.9.0...v1.10.0) (2024-01-31)

### Bug Fixes

* remove gh flag ([939200c](939200c9f2))
* tests ([ff92fd7](ff92fd7607))

### Features

* try multiple default config files ([89aacb5](89aacb58fd))
2024-01-31 22:15:13 +00:00
e6d9816a2f ci: fix release 2024-02-01 00:14:32 +02:00
e2353134d4 chore: update deps 2024-01-31 23:55:39 +02:00
6c7e3e5068 build: fix versioning 2024-01-31 23:55:39 +02:00
81dd6e3b19 docs: fix usage index link 2024-01-31 23:55:39 +02:00
b852a956ba docs: update badges 2024-01-31 23:55:39 +02:00
0ecc2590c7 docs: update badges 2024-01-31 23:55:39 +02:00
af4b753a6d docs: fix intro gif 2024-01-31 23:55:39 +02:00
semantic-release-bot
a8162f2315 chore(release): 2.0.0-pre.3 [skip ci]
# [2.0.0-pre.3](https://github.com/chenasraf/simple-scaffold/compare/v2.0.0-pre.2...v2.0.0-pre.3) (2024-01-30)

### Bug Fixes

* tests ([c219d38](c219d382e6))
2024-01-31 23:55:39 +02:00
ff92fd7607 fix: tests 2024-01-31 23:55:39 +02:00
0dc1116141 feat!: rename createSubFolder and subFolderNameHelper 2024-01-31 23:55:39 +02:00
f36cf4c2f2 docs: regenerate changelog
d
2024-01-31 23:55:39 +02:00
Chen Asraf
dd58c7fdda Update README.md 2024-01-31 23:55:39 +02:00
semantic-release-bot
058f1d4afa chore(release): 2.0.0-pre.2 [skip ci]
# [2.0.0-pre.2](https://github.com/chenasraf/simple-scaffold/compare/v2.0.0-pre.1...v2.0.0-pre.2) (2024-01-29)
2024-01-31 23:55:39 +02:00
583be2d9d2 docs: fix readme doc links
a
2024-01-31 23:55:39 +02:00
semantic-release-bot
18f1fac119 chore(release): 2.0.0-pre.1 [skip ci]
# [2.0.0-pre.1](https://github.com/chenasraf/simple-scaffold/compare/v1.9.0...v2.0.0-pre.1) (2024-01-29)

### Bug Fixes

* remove gh flag ([e66d6ba](e66d6ba86a))

### Features

* try multiple default config files ([f25cda7](f25cda738b))
2024-01-31 23:55:39 +02:00
693b8c8ea4 ci: fix docs 2024-01-31 23:55:39 +02:00
901d5d76b4 ci: fix docs 2024-01-31 23:55:39 +02:00
fac571f588 ci: fix docs 2024-01-31 23:55:39 +02:00
a10f412337 ci: use tag versions 2024-01-31 23:55:39 +02:00
819cd20644 docs: gtag + update deps 2024-01-31 23:55:39 +02:00
dc4c940559 ci: fix docs command 2024-01-31 23:55:39 +02:00
816e2f9aa0 ci: fix 2024-01-31 23:55:39 +02:00
825bd096c2 chore: fix docs & formatting 2024-01-31 23:55:39 +02:00
939200c9f2 fix: remove gh flag 2024-01-31 23:55:39 +02:00
0b7653de72 ci: fix docs build dir 2024-01-31 23:55:39 +02:00
89aacb58fd feat: try multiple default config files 2024-01-31 23:55:39 +02:00
9ce2845ace feat!: separate git/github/config flags
feat: separate git/github/config

t

test

cleanup
2024-01-31 23:55:39 +02:00
5373495f80 docs: update 2024-01-31 23:55:39 +02:00
70a080cc8e docs: update docs 2024-01-31 23:55:39 +02:00
ef7811d7e2 docs: update readme image 2024-01-31 23:55:39 +02:00
8b6b958480 docs: update docs, remove generated files from git 2024-01-31 23:55:39 +02:00
2c73207784 docs: update 2024-01-31 23:55:39 +02:00
5252642f6b docs: docusaurus initial commit 2024-01-31 23:55:39 +02:00
361778a188 feat!: rename verbose to logLevel 2024-01-31 23:55:39 +02:00
a54b1d6297 chore: update deps 2024-01-31 23:55:39 +02:00
ae69eb52db chore: update deps 2024-01-31 23:55:39 +02:00
3b60f1816d docs: update docs 2024-01-31 23:55:39 +02:00
c04d1cc42a chore!: remove Name from default data 2024-01-31 23:55:39 +02:00
c6b185cd8f feat!: remove url colon syntax 2024-01-31 23:55:39 +02:00
5abf528b81 chore!: update massarg 2024-01-31 23:55:39 +02:00
aeb3680ddf ci: update docs build 2024-01-31 23:55:39 +02:00
40e2381d05 ci: update build 2024-01-31 23:55:39 +02:00
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
Chen Asraf
c45d870233 v1.1.2
Fix binary file copy
2022-04-29 10:14:48 +03:00
Chen Asraf
8c8cedee48 update help command 2022-04-29 10:10:53 +03:00
Chen Asraf
40606edaee update test desc 2022-04-29 09:56:59 +03:00
Chen Asraf
e450ad242e fix: binary files + add tests 2022-04-29 09:54:06 +03:00
Chen Asraf
df60922154 Merge pull request #39 from chenasraf/master
Merge pull request #38 from chenasraf/develop
2022-04-24 17:41:55 +03:00
Chen Asraf
256d919253 Merge pull request #38 from chenasraf/develop
v1.1.1 - security fix
2022-04-24 17:30:10 +03:00
Chen Asraf
f8c31a1ecf Merge branch 'master' into develop 2022-04-24 17:28:39 +03:00
Chen Asraf
5571aba8dc npm audit fix 2022-04-24 17:27:03 +03:00
Chen Asraf
12f8bca7b5 fix doc deps 2022-04-23 11:29:09 +03:00
Chen Asraf
643431d333 fix gtag 2022-04-23 11:24:02 +03:00
Chen Asraf
0042c12ee7 fix docs build 2022-04-23 11:19:41 +03:00
Chen Asraf
869911bba0 update docs GA 2022-04-23 11:18:23 +03:00
Chen Asraf
9bd6219aa5 add gaid to docs 2022-04-23 09:45:48 +03:00
Chen Asraf
27a6ba4dda update docs 2022-04-22 23:00:11 +03:00
Chen Asraf
923b5316be update docs 2022-04-21 15:37:44 +03:00
Chen Asraf
bf10fb8ec3 update docs 2022-04-21 15:34:03 +03:00
Chen Asraf
4e2fa01bed update docs [skip publish] 2022-04-21 14:45:58 +03:00
Chen Asraf
af65ecadb9 try to fix docs [skip publish] 2022-04-21 13:07:19 +03:00
Chen Asraf
0d359b1917 fix workflow [skip publish] 2022-04-21 12:37:01 +03:00
Chen Asraf
f2a75c9fb0 Typdoc (#37) [skip publish]
* update package.json

* generate typedocs

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

### New features

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

### Commit log

* add develop branch

* chore: remove unneeded dep

* Update issue templates [skip ci]

* update PR workflow

* implementation of before-write callback + tests

* clean up + update docs

* add explicit function return types

* bump version number

* docs: add table of contents

* correct version number

* update workflows + release alpha

* update README.md [skip ci]

* update README.md [skip ci]

* implement now & custom date helpers

* docs + bump version number

* docs cleanup

* update docs

* accept async beforeWrite callback

* fix docs

* remove redundent check

* fix type import

* update README.md [skip ci]

* bump version number [skip ci]

* update docs [skip ci]
2022-04-21 02:57:52 +03:00
Chen Asraf
b2799d0bad chore: use rimraf + add error debug log [skip ci] 2022-04-10 22:53:44 +03:00
Chen Asraf
f4997c6289 docs: Update README.md [skip ci] 2022-04-10 22:53:31 +03:00
Chen Asraf
fe871eb97e Merge pull request #28 from chenasraf/dependabot/npm_and_yarn/minimist-1.2.6
Security fix: Bump minimist from 1.2.5 to 1.2.6
2022-04-10 14:51:02 +03:00
Chen Asraf
dffa81fde1 bump version number 2022-04-10 14:47:06 +03:00
dependabot[bot]
56be5f32cd Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-10 02:49:52 +00:00
Chen Asraf
4a4e024aec Add keywords to package.json [skip ci] 2022-03-15 11:42:53 +02:00
Chen Asraf
86a7a2c063 Update README.md [skip ci] 2022-03-15 11:40:55 +02:00
Chen Asraf
d3259c44aa Update README.md [skip ci] 2022-03-13 11:22:44 +02:00
Chen Asraf
e26a434dba update README.md 2022-03-03 21:53:50 +02:00
Chen Asraf
1783ddf230 remove unnecessary package [skip ci] 2022-03-03 21:53:08 +02:00
Chen Asraf
4575f73d69 Windows compatibility
Fix transform of windows-style paths
2022-03-03 21:49:44 +02:00
Chen Asraf
f07df79e82 improved test 2022-03-03 21:48:13 +02:00
Chen Asraf
cb6e06f1c9 bump version number 2022-03-03 21:43:55 +02:00
Chen Asraf
a043a05bc9 updated tests 2022-03-03 21:42:31 +02:00
Chen Asraf
52cb3e7353 fixed more windows paths, updated tests 2022-03-03 12:25:39 +02:00
Chen Asraf
89d7897f4e refactor handlebarsParse - remove redundant arg 2022-03-03 10:31:30 +02:00
Chen Asraf
d6e1693074 import/file cleanup 2022-03-03 10:25:44 +02:00
Chen Asraf
56f1340093 fix transform of windows-style paths 2022-03-03 10:21:37 +02:00
Chen Asraf
8782f18a73 v1.0.1
Merge pull request #24 from chenasraf/alpha
2022-02-20 14:10:59 +02:00
Chen Asraf
21c4ab6e1a bump version number [skip ci] 2022-02-20 14:06:56 +02:00
Chen Asraf
d797e5b640 bump alpha version number 2022-02-17 19:19:07 +02:00
Chen Asraf
c3835a7b04 refactoring - code cleanup 2022-02-17 19:18:48 +02:00
Chen Asraf
81ba5f50fd add subFolderNameHelper arg 2022-02-17 19:12:17 +02:00
Chen Asraf
edcf1aceaa Update README.md 2022-02-14 23:34:59 +02:00
Chen Asraf
d0a0db0f82 Update MIGRATION.md [skip ci] 2022-02-14 23:31:52 +02:00
Chen Asraf
819f84e5ca Merge pull request #23 from chenasraf/dependabot/npm_and_yarn/ansi-regex-5.0.1
Bump ansi-regex from 5.0.0 to 5.0.1
2022-02-14 23:29:41 +02:00
dependabot[bot]
36f8b87514 Bump ansi-regex from 5.0.0 to 5.0.1
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 5.0.0 to 5.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v5.0.0...v5.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-14 21:28:17 +00:00
Chen Asraf
d06c0d64b3 bump version: v1.0.0 2022-02-14 23:26:48 +02:00
Chen Asraf
5ab2637485 bump version number 2022-02-14 23:26:48 +02:00
Chen Asraf
0af639254b fail handlebars parse silently 2022-02-14 23:26:48 +02:00
Chen Asraf
391a08ac63 update README.md, default output fix 2022-02-14 23:26:48 +02:00
Chen Asraf
cd34930e04 fix cmd 2022-02-14 23:26:48 +02:00
Chen Asraf
956b00700f update workflows [skip publish] 2022-02-14 23:26:48 +02:00
Chen Asraf
91116bba69 update alpha workflow 2022-02-14 23:26:48 +02:00
Chen Asraf
f4cc44cf17 helpers fix 2022-02-14 23:26:48 +02:00
Chen Asraf
54b90235e5 fix copyright 2022-02-14 23:26:48 +02:00
Chen Asraf
d03d0e0812 added custom helpers 2022-02-14 23:26:48 +02:00
Chen Asraf
5b72b6c166 update deps + add MIGRATION.md 2022-02-14 23:26:48 +02:00
Chen Asraf
d96992cd2d update deps + update cmd requirements 2022-02-14 23:26:48 +02:00
Chen Asraf
925993948a update readme [skip ci] 2022-02-14 23:26:48 +02:00
Chen Asraf
09403e18f1 update readme [skip ci] 2022-02-14 23:26:48 +02:00
Chen Asraf
c2bc8b7606 update docs [skip publish] 2022-02-14 23:26:48 +02:00
Chen Asraf
b1b1aca802 fix basename in some cases 2022-02-14 23:26:48 +02:00
Chen Asraf
2305083c7d run tests on ci [skip publish] 2022-02-14 23:26:48 +02:00
Chen Asraf
8413225202 fix errors, fix nested output 2022-02-14 23:26:48 +02:00
Chen Asraf
559b5ad7f3 remove types from package.json 2022-02-14 23:26:48 +02:00
Chen Asraf
a21a35f773 fix yarn.lock 2022-02-14 23:26:48 +02:00
Chen Asraf
84e6207891 fix log level 0 2022-02-14 23:26:48 +02:00
Chen Asraf
6b57406369 add export for cmd_util 2022-02-14 23:26:48 +02:00
Chen Asraf
53e8bc4cc0 bump version number 2022-02-14 23:26:48 +02:00
Chen Asraf
54848f9c50 update workflows [skip publish] 2022-02-14 23:26:48 +02:00
Chen Asraf
2623b787e6 fixes + add log level [skip publish] 2022-02-14 23:26:48 +02:00
Chen Asraf
ad30ee0c0c fix readme [skip publish] 2022-02-14 23:26:48 +02:00
Chen Asraf
8575b1e0c3 try fix release upload 2022-02-14 23:26:48 +02:00
Chen Asraf
d8aba21d0e try new release version 2022-02-14 23:26:48 +02:00
Chen Asraf
3f2945eaa4 fixed release tarball file location 2022-02-14 23:26:48 +02:00
Chen Asraf
930344656a fixed release tarball file location 2022-02-14 23:26:48 +02:00
Chen Asraf
99c9055208 fix build output files 2022-02-14 23:26:48 +02:00
Chen Asraf
f1698d2a46 add build step 2022-02-14 23:26:48 +02:00
Chen Asraf
c17e6304e6 try to fix workflow 2022-02-14 23:26:48 +02:00
Chen Asraf
535260a0c0 publish: debug mode off, try to fix workflow 2022-02-14 23:26:48 +02:00
Chen Asraf
14988576f3 publish: debug mode on 2022-02-14 23:26:48 +02:00
Chen Asraf
6f03ed9d60 try fix workflow 2022-02-14 23:26:48 +02:00
Chen Asraf
c7749a8d33 update workflows 2021-11-18 13:42:28 +02:00
Chen Asraf
a59f29d71d fix build/publish cmd 2021-11-18 13:38:16 +02:00
Chen Asraf
bc224d93e1 support node 12 for fs package 2021-11-18 13:16:10 +02:00
Chen Asraf
cf923d8889 support node 12 for fs package 2021-11-18 13:14:53 +02:00
Chen Asraf
01e458ee0c update jest config 2021-11-18 13:12:37 +02:00
Chen Asraf
93853712f5 use node 12 2021-11-18 13:08:54 +02:00
Chen Asraf
474a3dcc1f try fix workflow 2021-11-18 13:04:19 +02:00
Chen Asraf
27e84d1093 update workflow 2021-11-18 12:55:07 +02:00
Chen Asraf
a6f25facc0 update workflows 2021-11-18 12:51:16 +02:00
Chen Asraf
3ee66b24f5 build: update workflow 2021-11-18 12:45:37 +02:00
Chen Asraf
0ce19a7e1a build: add workflow 2021-11-18 12:45:37 +02:00
Chen Asraf
7273538d79 fix main field in package.json 2021-11-18 12:45:37 +02:00
Chen Asraf
43b64965e3 v0.7.4 2021-11-18 12:45:37 +02:00
Chen Asraf
4f81654e05 added --quiet flag 2021-11-18 12:45:37 +02:00
Chen Asraf
8fcc7a6dc6 chore: cleanup 2021-11-18 12:45:31 +02:00
Chen Asraf
b4b0de6f2f docs: update README 2021-11-18 12:45:31 +02:00
Chen Asraf
2d5626ca70 improve tests 2021-11-18 12:45:26 +02:00
Chen Asraf
564e821419 maintain directory structure 2021-11-18 12:45:26 +02:00
Chen Asraf
a52f9a0b84 migrate cmd to massarg + update tests 2021-11-18 12:45:26 +02:00
Chen Asraf
aeddd44778 add dry run option 2021-11-18 12:45:26 +02:00
Chen Asraf
7cdf5e461b add cmd args 2021-11-18 12:45:26 +02:00
Chen Asraf
c42a58c2f2 add tests 2021-11-18 12:45:26 +02:00
Chen Asraf
d0c0152717 remove excess files 2021-11-18 12:45:26 +02:00
Chen Asraf
208ee307c8 code splitting 2021-11-18 12:45:16 +02:00
Chen Asraf
54834909b9 major refactor 2021-11-18 12:45:16 +02:00
Chen Asraf
40b592072e Create FUNDING.yml 2021-10-11 18:20:18 +03:00
Chen Asraf
3cb9a6fcd8 fix main field in package.json 2021-09-26 11:42:52 +03:00
Chen Asraf
12974b5561 v0.7.4 2021-09-26 11:37:02 +03:00
Chen Asraf
7f98d469a3 added --quiet flag 2021-09-26 11:28:38 +03:00
Chen Asraf
cd25b04886 Update README.md 2021-05-20 11:03:19 +03:00
Chen Asraf
5cd637f41f Merge pull request #4 from chenasraf/dependabot/npm_and_yarn/handlebars-4.7.7
Bump handlebars from 4.7.6 to 4.7.7
2021-05-20 10:55:41 +03:00
Chen Asraf
0a4467ae5f Merge pull request #5 from chenasraf/dependabot/npm_and_yarn/url-parse-1.5.1
Bump url-parse from 1.4.7 to 1.5.1
2021-05-20 10:55:33 +03:00
Chen Asraf
713a0ed44f Merge pull request #6 from chenasraf/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.20 to 4.17.21
2021-05-20 10:55:18 +03:00
Chen Asraf
edec2d1c26 Merge pull request #7 from chenasraf/dependabot/npm_and_yarn/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.8 to 2.8.9
2021-05-20 10:55:03 +03:00
dependabot[bot]
2e12907412 Bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-09 23:31:02 +00:00
dependabot[bot]
5b7e0e30f1 Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-08 04:50:00 +00:00
dependabot[bot]
09238300cd Bump url-parse from 1.4.7 to 1.5.1
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.1.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 10:20:13 +00:00
dependabot[bot]
552614ca3f Bump handlebars from 4.7.6 to 4.7.7
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.7.6 to 4.7.7.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.7.6...v4.7.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 02:51:26 +00:00
Chen Asraf
813f706cf0 update readme 2021-04-19 22:30:22 +03:00
Chen Asraf
1bc2221472 update readme 2021-04-19 22:28:22 +03:00
Chen Asraf
f07affa124 add basename to output config function (fixes #3) 2021-04-19 22:24:21 +03:00
Chen Asraf
ce22a2c34c disable overwriting files + parse JSON for locals 2021-02-28 01:38:51 +02:00
Chen Asraf
7c0c347002 fix: binary files 2021-02-01 15:24:57 +02:00
Chen Asraf
977288ae5a build: upgrade packages 2021-02-01 15:24:26 +02:00
Chen Asraf
4afafa5a4a fix: support deeper file structure 2021-02-01 15:23:50 +02:00
Chen Asraf
7bee2a51c7 0.5.0 2019-02-27 16:53:42 +02:00
Chen Asraf
d4c049baaf v0.5.0 2019-02-27 16:53:33 +02:00
Chen Asraf
06590c4b6e Fixed output argument + updated README 2019-02-27 16:20:33 +02:00
Chen Asraf
c4f2dfb04f v0.4.5 2019-02-27 16:20:12 +02:00
Chen Asraf
a410b79195 Improved docs 2019-02-27 16:14:56 +02:00
Chen Asraf
71d544aff4 v0.4.4 2019-02-27 16:14:22 +02:00
Chen Asraf
20389d7b33 v0.4.3 2019-02-27 16:12:43 +02:00
Chen Asraf
d7a4362725 mapfile 2019-02-25 19:54:12 +02:00
112 changed files with 24490 additions and 7015 deletions

8
.editorconfig Normal file
View File

@@ -0,0 +1,8 @@
[*]
tab_width = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false

13
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
# These are supported funding model platforms
github: chenasraf
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: casraf
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom:
- "https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TSH3C3ABGQM22&currency_code=ILS&source=url"

47
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,47 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] "
labels: bug, needs-triage
assignees: chenasraf
---
#### Describe the bug
A clear and concise description of what the bug is.
#### How To Reproduce
Steps to reproduce the behavior:
1. Prepare templates:
```text
This is my {{ template }}
```
2. Run with args/config:
```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.
#### 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.1.2]
#### Additional context
Add any other context about the problem here.

View File

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

102
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,102 @@
name: Release
on:
pull_request:
branches:
- master
push:
branches:
- master
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
release:
name: Release Please
if: github.event_name == 'push'
needs:
- build
- test
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
steps:
- uses: googleapis/release-please-action@v4
id: release
with:
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
release-type: node
target-branch: master
publish:
name: NPM Publish
needs: release
runs-on: ubuntu-latest
if: ${{ needs.release.outputs.release_created }}
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
registry-url: "https://registry.npmjs.org"
- run: |
echo "node: $(node --version)"
echo "npm: $(npm --version)"
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: npm publish ./dist --provenance
docs:
name: Deploy Documentation
needs: release
runs-on: ubuntu-latest
if: ${{ needs.release.outputs.release_created }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: cd docs && pnpm install --frozen-lockfile
- run: pnpm docs:build
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs/build

9
.gitignore vendored
View File

@@ -42,6 +42,9 @@ typings/
# Optional npm cache directory
.npm
# NPM
.npmrc
# Optional eslint cache
.eslintcache
@@ -58,4 +61,8 @@ typings/
.env
examples/test-output/**/*
!examples/test-output/.gitkeep
dist/
.DS_Store
tmp/
.nvmrc

1
.husky/pre-commit Normal file
View File

@@ -0,0 +1 @@
pnpm lint-staged

9
.markdownlint.json Normal file
View File

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

5
.prettierignore Normal file
View File

@@ -0,0 +1,5 @@
docs/docs/api/
examples/
.github/
CHANGELOG.md
pnpm-lock.yaml

16
.prettierrc Normal file
View File

@@ -0,0 +1,16 @@
{
"semi": false,
"singleQuote": false,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 100,
"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}"
}
]
}

18
.vscode/settings.json vendored
View File

@@ -1,3 +1,19 @@
{
"typescript.tsdk": "./node_modules/typescript/lib"
"typescript.tsdk": "./node_modules/typescript/lib",
"npm.packageManager": "yarn",
"cSpell.words": [
"massarg",
"MYCOMPONENT",
"myname",
"nobrace",
"nocomment",
"nodir",
"noext",
"nonegate",
"subdir",
"variabletoken"
],
"[markdown]": {
"editor.rulers": [87, 100]
}
}

62
.vscode/tasks.json vendored
View File

@@ -1,7 +1,59 @@
{
"version": "0.1.0",
"command": "webpack",
"isShellCommand": true,
"args": [],
"showOutput": "always"
"version": "2.0.0",
"tasks": [
{
"script": "build",
"label": "build",
"type": "npm",
"problemMatcher": []
},
{
"script": "dev",
"label": "dev",
"type": "npm",
"problemMatcher": []
},
{
"command": "pnpm typedoc --watch",
"label": "typedoc --watch",
"type": "shell",
"problemMatcher": []
},
{
"script": "start",
"label": "start",
"type": "npm",
"problemMatcher": []
},
{
"script": "test",
"label": "test",
"type": "npm",
"problemMatcher": []
},
{
"command": "pnpm test --watchAll",
"label": "pnpm test --watchAll",
"type": "shell",
"problemMatcher": []
},
{
"script": "cmd",
"label": "cmd",
"type": "npm",
"problemMatcher": []
},
{
"script": "build-test",
"label": "build-test",
"type": "npm",
"problemMatcher": []
},
{
"script": "build-cmd",
"label": "build-cmd",
"type": "npm",
"problemMatcher": []
}
]
}

713
CHANGELOG.md Normal file
View File

@@ -0,0 +1,713 @@
# Change Log
## [3.1.1](https://github.com/chenasraf/simple-scaffold/compare/v3.1.0...v3.1.1) (2026-03-26)
### Bug Fixes
* **deps:** update dependencies ([f5ed38a](https://github.com/chenasraf/simple-scaffold/commit/f5ed38ad950e3738b173cd39baf2acc9e35bf8de))
## [3.1.0](https://github.com/chenasraf/simple-scaffold/compare/v3.0.0...v3.1.0) (2026-03-23)
### Features
* after scaffold hook ([0a4ead1](https://github.com/chenasraf/simple-scaffold/commit/0a4ead17c013b5410e8eec8000e83fa7b7186fbb))
* config validation ([972d199](https://github.com/chenasraf/simple-scaffold/commit/972d199fbb7167506f76eba697fbea6acea9de56))
* init command ([7926b15](https://github.com/chenasraf/simple-scaffold/commit/7926b15053726d5eb9e5772b5f04c3989e567b2c))
* scaffoldignore ([b5fd1df](https://github.com/chenasraf/simple-scaffold/commit/b5fd1df821eba7e13e5ceedc53da550c8dd7cd8e))
* select, confirm and number input types ([f6408f2](https://github.com/chenasraf/simple-scaffold/commit/f6408f221d84d84c5671737af63ff8079093fd27))
* update all ouput logging ([bb0248f](https://github.com/chenasraf/simple-scaffold/commit/bb0248f91a012c297e408f15f0846c5272166543))
### Bug Fixes
* interactive inputs with existing config/cli options ([c860e46](https://github.com/chenasraf/simple-scaffold/commit/c860e4644a3f3d4bd6b7dab9974accdf8bae9463))
## [3.0.0](https://github.com/chenasraf/simple-scaffold/compare/v2.3.3...v3.0.0) (2026-03-23)
### Features
* add .scaffold.{ext} as auto-detected file ([04e7e89](https://github.com/chenasraf/simple-scaffold/commit/04e7e895d7d97e29cf1ded2f2d0ac8e6a237b997))
* auto-detect config file ([68e6d17](https://github.com/chenasraf/simple-scaffold/commit/68e6d17fa9898dffbc620eba0140748a90bc007f))
* interactive inputs ([519ef27](https://github.com/chenasraf/simple-scaffold/commit/519ef273ac3db4b7a1e71c8e1c456aa1334d6fbd))
* predefined data inputs ([d64dd4f](https://github.com/chenasraf/simple-scaffold/commit/d64dd4f0e775d3bff3efb074d0af23d54edcbaab))
### Bug Fixes
* string helpers to words parts conversion ([af33c05](https://github.com/chenasraf/simple-scaffold/commit/af33c059b91d3f463a5d174ab3a0119c577880c5))
## [2.3.3](https://github.com/chenasraf/simple-scaffold/compare/v2.3.2...v2.3.3) (2025-06-18)
### Bug Fixes
* config CLI precedence over file ([4b0b4e7](https://github.com/chenasraf/simple-scaffold/commit/4b0b4e73803ff741120b18767ded88db324a8844))
## [2.3.2](https://github.com/chenasraf/simple-scaffold/compare/v2.3.1...v2.3.2) (2024-10-27)
### Bug Fixes
* template config from CLI ([41f4ca5](https://github.com/chenasraf/simple-scaffold/commit/41f4ca52f12d3477e1a9a15757dc816fb99b6743))
## [2.3.1](https://github.com/chenasraf/simple-scaffold/compare/v2.3.0...v2.3.1) (2024-10-03)
### Bug Fixes
* strip tmpDir from output dir ([#108](https://github.com/chenasraf/simple-scaffold/issues/108)) ([80c92bf](https://github.com/chenasraf/simple-scaffold/commit/80c92bfe84dc896412ef98bce222e1d26cdb4e91))
## [2.3.0](https://github.com/chenasraf/simple-scaffold/compare/v2.2.2...v2.3.0) (2024-09-17)
### Features
* remove chalk dependency ([ab9322e](https://github.com/chenasraf/simple-scaffold/commit/ab9322e1ab9c0a07cdab7275f3398286dee67a64))
### Bug Fixes
* exclude globs ([89dc43c](https://github.com/chenasraf/simple-scaffold/commit/89dc43c73d9a8640f45ae77e5c89e4f08f7f99ad))
## [2.2.2](https://github.com/chenasraf/simple-scaffold/compare/v2.2.1...v2.2.2) (2024-08-27)
### Bug Fixes
* homepage url ([daaefaf](https://github.com/chenasraf/simple-scaffold/commit/daaefaf54e8c8887e6f210d02fd5f96c6ff4aa21))
## [2.2.1](https://github.com/chenasraf/simple-scaffold/compare/v2.2.0...v2.2.1) (2024-04-21)
### Bug Fixes
* beforeWrite from config files ([98b326c](https://github.com/chenasraf/simple-scaffold/commit/98b326c84346162f379af46bc5aefb69df8be515))
* use console.info for handlebars parse warning ([19e7b0f](https://github.com/chenasraf/simple-scaffold/commit/19e7b0f0c35c1b79a98781bdec9f54354123d8e0))
# [2.2.0](https://github.com/chenasraf/simple-scaffold/compare/v2.1.0...v2.2.0) (2024-02-23)
### Features
* `list` command ([d579c09](https://github.com/chenasraf/simple-scaffold/commit/d579c09c11f2149fe7bb4515297c1287fa67083e))
* add `--before-write` cli option ([#89](https://github.com/chenasraf/simple-scaffold/issues/89)) ([5f810e2](https://github.com/chenasraf/simple-scaffold/commit/5f810e21160816bc683cc0f375de318ff874871c))
# [2.1.0](https://github.com/chenasraf/simple-scaffold/compare/v2.0.2...v2.1.0) (2024-02-12)
### Features
* support directory in --config flag ([e48b832](https://github.com/chenasraf/simple-scaffold/commit/e48b832e0b72a084d33fa2cbcca332e8209a734f))
* support providing name in config ([4e7ac34](https://github.com/chenasraf/simple-scaffold/commit/4e7ac34db9bf67d012bbd1c06c1a26bc5ac93559))
## [2.0.2](https://github.com/chenasraf/simple-scaffold/compare/v2.0.1...v2.0.2) (2024-02-04)
### Bug Fixes
* try to await scaffold before finally ([1b70897](https://github.com/chenasraf/simple-scaffold/commit/1b70897f9840e6365ff800490fbb813b9840177d))
## [2.0.1](https://github.com/chenasraf/simple-scaffold/compare/v2.0.0...v2.0.1) (2024-02-02)
### Bug Fixes
* log level flag ([5d7f449](https://github.com/chenasraf/simple-scaffold/commit/5d7f449050e50a6e4b2d00b7a2215cdb5fc9b611))
* rm tmp dir too early ([4aa52c8](https://github.com/chenasraf/simple-scaffold/commit/4aa52c84bd8cf302031e9f7f6407466aa736beb7))
# [2.0.0](https://github.com/chenasraf/simple-scaffold/compare/v1.9.0...v2.0.0) (2024-01-31)
* fix!: version number ([bc0a18d](https://github.com/chenasraf/simple-scaffold/commit/bc0a18dce01fefec6187192cb20c9303f7f7dbfa))
* remove gh flag ([939200c](https://github.com/chenasraf/simple-scaffold/commit/939200c9f21be240485ea602a73b983ba2f47aaf))
* tests ([ff92fd7](https://github.com/chenasraf/simple-scaffold/commit/ff92fd7607f1b86f36fc6b62652fdfc81cb391a3))
* try multiple default config files ([89aacb5](https://github.com/chenasraf/simple-scaffold/commit/89aacb58fd90a892f4994c758c61c43b2a6b1fba))
* Update README.md ([e012d51](https://github.com/chenasraf/simple-scaffold/commit/e012d51))
* docs: fix readme doc links ([55e561b](https://github.com/chenasraf/simple-scaffold/commit/55e561b))
* chore: fix docs & formatting ([b4f0731](https://github.com/chenasraf/simple-scaffold/commit/b4f0731))
* chore: update deps ([22ad5d4](https://github.com/chenasraf/simple-scaffold/commit/22ad5d4))
* chore: update deps ([b2373aa](https://github.com/chenasraf/simple-scaffold/commit/b2373aa))
* chore(release): 2.0.0-pre.1 [skip ci] ([add794a](https://github.com/chenasraf/simple-scaffold/commit/add794a))
* ci: fix ([29a7aa3](https://github.com/chenasraf/simple-scaffold/commit/29a7aa3))
* ci: fix docs ([d4cb767](https://github.com/chenasraf/simple-scaffold/commit/d4cb767))
* ci: fix docs ([570b00d](https://github.com/chenasraf/simple-scaffold/commit/570b00d))
* ci: fix docs ([7ef3421](https://github.com/chenasraf/simple-scaffold/commit/7ef3421))
* ci: fix docs build dir ([96c1d5a](https://github.com/chenasraf/simple-scaffold/commit/96c1d5a))
* ci: fix docs command ([5cf5692](https://github.com/chenasraf/simple-scaffold/commit/5cf5692))
* ci: update build ([d168dc1](https://github.com/chenasraf/simple-scaffold/commit/d168dc1))
* ci: update docs build ([9830df8](https://github.com/chenasraf/simple-scaffold/commit/9830df8))
* ci: use tag versions ([b6fed83](https://github.com/chenasraf/simple-scaffold/commit/b6fed83))
* docs: docusaurus initial commit ([a955c4d](https://github.com/chenasraf/simple-scaffold/commit/a955c4d))
* docs: gtag + update deps ([e0c0f5c](https://github.com/chenasraf/simple-scaffold/commit/e0c0f5c))
* docs: update ([4821be6](https://github.com/chenasraf/simple-scaffold/commit/4821be6))
* docs: update ([c95477d](https://github.com/chenasraf/simple-scaffold/commit/c95477d))
* docs: update docs ([f59111f](https://github.com/chenasraf/simple-scaffold/commit/f59111f))
* docs: update docs ([3b0fc7a](https://github.com/chenasraf/simple-scaffold/commit/3b0fc7a))
* docs: update docs, remove generated files from git ([f0a080c](https://github.com/chenasraf/simple-scaffold/commit/f0a080c))
* docs: update readme image ([8478d36](https://github.com/chenasraf/simple-scaffold/commit/8478d36))
* fix: remove gh flag ([e66d6ba](https://github.com/chenasraf/simple-scaffold/commit/e66d6ba))
* feat: try multiple default config files ([f25cda7](https://github.com/chenasraf/simple-scaffold/commit/f25cda7))
* chore!: remove `Name` from default data ([0bb282c](https://github.com/chenasraf/simple-scaffold/commit/0bb282c))
* chore!: update massarg ([55877f0](https://github.com/chenasraf/simple-scaffold/commit/55877f0))
* feat!: remove url colon syntax ([b57be8e](https://github.com/chenasraf/simple-scaffold/commit/b57be8e))
* feat!: rename verbose to logLevel ([17fdf0c](https://github.com/chenasraf/simple-scaffold/commit/17fdf0c))
* feat!: separate git/github/config flags ([995b433](https://github.com/chenasraf/simple-scaffold/commit/995b433))
## 1.9.0 (2024-01-02)
* chore: update dependencies ([758719d](https://github.com/chenasraf/simple-scaffold/commit/758719d))
* chore(release): 1.9.0 [skip ci] ([4d1a6e1](https://github.com/chenasraf/simple-scaffold/commit/4d1a6e1))
* ci: fix actions ([daae9c1](https://github.com/chenasraf/simple-scaffold/commit/daae9c1))
* ci: update build process ([e26fe2a](https://github.com/chenasraf/simple-scaffold/commit/e26fe2a))
* ci: update build steps ([1903055](https://github.com/chenasraf/simple-scaffold/commit/1903055))
* ci: update docs build, semantic release ([7e1acf0](https://github.com/chenasraf/simple-scaffold/commit/7e1acf0))
* feat: add --recurse-submodules to git clone ([cbaf130](https://github.com/chenasraf/simple-scaffold/commit/cbaf130))
## 1.8.0 (2023-11-29)
* chore: update dependencies ([b048841](https://github.com/chenasraf/simple-scaffold/commit/b048841))
* chore(release): 1.8.0 [skip ci] ([f666c35](https://github.com/chenasraf/simple-scaffold/commit/f666c35)), closes [#63](https://github.com/chenasraf/simple-scaffold/issues/63)
* chore(release): 1.8.0-pre.1 [skip ci] ([746f924](https://github.com/chenasraf/simple-scaffold/commit/746f924)), closes [#63](https://github.com/chenasraf/simple-scaffold/issues/63)
* docs: update configuration files docs ([f5d55f2](https://github.com/chenasraf/simple-scaffold/commit/f5d55f2))
* ci: update release config ([807c3e2](https://github.com/chenasraf/simple-scaffold/commit/807c3e2))
* fix(config): fn config load ([457c904](https://github.com/chenasraf/simple-scaffold/commit/457c904)), closes [#63](https://github.com/chenasraf/simple-scaffold/issues/63)
* build(deps-dev): bump @babel/traverse from 7.21.5 to 7.23.2 ([0fa1ad4](https://github.com/chenasraf/simple-scaffold/commit/0fa1ad4))
## <small>1.7.2 (2023-08-20)</small>
* chore(release): 1.7.2 [skip ci] ([d62eeeb](https://github.com/chenasraf/simple-scaffold/commit/d62eeeb))
## <small>1.7.2-pre.1 (2023-08-15)</small>
* chore: bump version number ([9f5716e](https://github.com/chenasraf/simple-scaffold/commit/9f5716e))
* chore: formatting ([8c3369a](https://github.com/chenasraf/simple-scaffold/commit/8c3369a))
* chore: update dependencies ([d2a2fda](https://github.com/chenasraf/simple-scaffold/commit/d2a2fda))
* chore: update logs ([20ef0ce](https://github.com/chenasraf/simple-scaffold/commit/20ef0ce))
* chore(release): 1.7.2-pre.1 [skip ci] ([9f58fff](https://github.com/chenasraf/simple-scaffold/commit/9f58fff))
* ci: trigger on pre branch ([dbba81d](https://github.com/chenasraf/simple-scaffold/commit/dbba81d))
* fix: windows path resolution ([98ee000](https://github.com/chenasraf/simple-scaffold/commit/98ee000))
* test: add tests ([3413151](https://github.com/chenasraf/simple-scaffold/commit/3413151))
## <small>1.7.1 (2023-06-07)</small>
* chore(release): 1.7.1 [skip ci] ([de05bca](https://github.com/chenasraf/simple-scaffold/commit/de05bca))
* build: fix tsconfig ([2cf31e8](https://github.com/chenasraf/simple-scaffold/commit/2cf31e8))
* build: update release rules, add tests ([3714e8b](https://github.com/chenasraf/simple-scaffold/commit/3714e8b))
## <small>1.7.1-develop.1 (2023-06-07)</small>
* chore(release): 1.7.1-develop.1 [skip ci] ([77be7c0](https://github.com/chenasraf/simple-scaffold/commit/77be7c0))
## 1.7.0 (2023-05-17)
* chore(release): 1.7.0 [skip ci] ([4868925](https://github.com/chenasraf/simple-scaffold/commit/4868925))
## 1.7.0-develop.7 (2023-06-07)
* chore(release): 1.7.0-develop.7 [skip ci] ([fea6c0f](https://github.com/chenasraf/simple-scaffold/commit/fea6c0f))
* fix: local config file load error ([2b74239](https://github.com/chenasraf/simple-scaffold/commit/2b74239))
## 1.7.0-develop.6 (2023-05-27)
* chore(release): 1.7.0-develop.6 [skip ci] ([a47ba11](https://github.com/chenasraf/simple-scaffold/commit/a47ba11))
* docs: update README.md ([06b1552](https://github.com/chenasraf/simple-scaffold/commit/06b1552))
* test: add tests ([4f27b7b](https://github.com/chenasraf/simple-scaffold/commit/4f27b7b))
## 1.7.0 (2023-05-17)
* chore(release): 1.7.0 [skip ci] ([4868925](https://github.com/chenasraf/simple-scaffold/commit/4868925))
## 1.7.0-develop.5 (2023-05-12)
* chore(release): 1.7.0-develop.5 [skip ci] ([bee430a](https://github.com/chenasraf/simple-scaffold/commit/bee430a))
* test: fix + add tests ([3dfc920](https://github.com/chenasraf/simple-scaffold/commit/3dfc920))
* refactor: remove lodash dependency ([68307d1](https://github.com/chenasraf/simple-scaffold/commit/68307d1))
* build: update workflows ([33e1d56](https://github.com/chenasraf/simple-scaffold/commit/33e1d56))
## 1.7.0-develop.4 (2023-05-11)
* chore(release): 1.7.0-develop.4 [skip ci] ([c446439](https://github.com/chenasraf/simple-scaffold/commit/c446439))
* build: update package asset name ([773fd00](https://github.com/chenasraf/simple-scaffold/commit/773fd00))
## 1.7.0-develop.3 (2023-05-11)
* chore: cleanup ([9393c54](https://github.com/chenasraf/simple-scaffold/commit/9393c54))
* chore(release): 1.7.0-develop.3 [skip ci] ([22763f6](https://github.com/chenasraf/simple-scaffold/commit/22763f6))
* build: add packageManager key to package.json ([dac5527](https://github.com/chenasraf/simple-scaffold/commit/dac5527))
* build: update package link ([a54b1f9](https://github.com/chenasraf/simple-scaffold/commit/a54b1f9))
* build: update workflows ([339459c](https://github.com/chenasraf/simple-scaffold/commit/339459c))
* build: update workflows ([62b4a1c](https://github.com/chenasraf/simple-scaffold/commit/62b4a1c))
* build: use pnpm ([5844c08](https://github.com/chenasraf/simple-scaffold/commit/5844c08))
## 1.7.0-develop.2 (2023-05-10)
* chore(release): 1.7.0-develop.2 [skip ci] ([263bf0b](https://github.com/chenasraf/simple-scaffold/commit/263bf0b))
* build: update release.config.js ([e0ed371](https://github.com/chenasraf/simple-scaffold/commit/e0ed371))
## 1.7.0-develop.1 (2023-05-09)
* chore: cleanups ([c027e37](https://github.com/chenasraf/simple-scaffold/commit/c027e37))
* chore: cleanups ([2251a9c](https://github.com/chenasraf/simple-scaffold/commit/2251a9c))
* chore(release): 1.7.0-develop.1 [skip ci] ([87934fb](https://github.com/chenasraf/simple-scaffold/commit/87934fb))
* build: fix build ([19b7ed5](https://github.com/chenasraf/simple-scaffold/commit/19b7ed5))
* feat: function config file ([02a8ba1](https://github.com/chenasraf/simple-scaffold/commit/02a8ba1))
* fix: use path.normalize ([565090a](https://github.com/chenasraf/simple-scaffold/commit/565090a))
* docs: update docs ([943aad1](https://github.com/chenasraf/simple-scaffold/commit/943aad1))
* docs: update readme + author ([be92047](https://github.com/chenasraf/simple-scaffold/commit/be92047))
* test: move scaffold.config.js ([9fb4762](https://github.com/chenasraf/simple-scaffold/commit/9fb4762))
## 1.6.0 (2023-05-05)
* chore(release): 1.6.0 [skip ci] ([0940e84](https://github.com/chenasraf/simple-scaffold/commit/0940e84))
* chore(release): bump version number ([77e477e](https://github.com/chenasraf/simple-scaffold/commit/77e477e))
* build: fix package.json version update ([6c8eb02](https://github.com/chenasraf/simple-scaffold/commit/6c8eb02))
* build: use pnpm instead of yarn ([08b0488](https://github.com/chenasraf/simple-scaffold/commit/08b0488))
* docs: fix changelog title ([5ba6034](https://github.com/chenasraf/simple-scaffold/commit/5ba6034))
* docs: update docs ([95dafdf](https://github.com/chenasraf/simple-scaffold/commit/95dafdf))
## 1.6.0-develop.1 (2023-05-04)
* chore(release): 1.5.0-develop.2 [skip ci] ([81743f1](https://github.com/chenasraf/simple-scaffold/commit/81743f1))
* chore(release): 1.5.0-develop.3 [skip ci] ([4bf9674](https://github.com/chenasraf/simple-scaffold/commit/4bf9674))
* chore(release): 1.6.0-develop.1 [skip ci] ([4c3f1c9](https://github.com/chenasraf/simple-scaffold/commit/4c3f1c9))
* docs: fix cli.md page ([2ca91a7](https://github.com/chenasraf/simple-scaffold/commit/2ca91a7))
* docs: fix link ([ba984b6](https://github.com/chenasraf/simple-scaffold/commit/ba984b6))
* docs: update docs ([260ce6a](https://github.com/chenasraf/simple-scaffold/commit/260ce6a))
* docs: update docs ([7b6260c](https://github.com/chenasraf/simple-scaffold/commit/7b6260c))
* docs: update package description ([1799971](https://github.com/chenasraf/simple-scaffold/commit/1799971))
* feat: node.js function for remote configs ([ce5adbe](https://github.com/chenasraf/simple-scaffold/commit/ce5adbe))
* build: remove unnecessary dependency ([99318f7](https://github.com/chenasraf/simple-scaffold/commit/99318f7))
* build: separate test & build ([9489f14](https://github.com/chenasraf/simple-scaffold/commit/9489f14))
* fix: move dependency to dev dependency ([d916d88](https://github.com/chenasraf/simple-scaffold/commit/d916d88))
## 1.5.0 (2023-05-02)
* chore: bump version number ([7f10db0](https://github.com/chenasraf/simple-scaffold/commit/7f10db0))
* chore(release): 1.5.0 [skip ci] ([7be79fd](https://github.com/chenasraf/simple-scaffold/commit/7be79fd))
* docs: update help text ([1a3fd3d](https://github.com/chenasraf/simple-scaffold/commit/1a3fd3d))
## 1.5.0-develop.1 (2023-05-02)
* chore: fix package version ([10ea6b4](https://github.com/chenasraf/simple-scaffold/commit/10ea6b4))
* chore(release): 1.5.0-develop.1 [skip ci] ([93f5b4a](https://github.com/chenasraf/simple-scaffold/commit/93f5b4a))
* docs: add docs for remote templates ([b74411d](https://github.com/chenasraf/simple-scaffold/commit/b74411d))
* feat: add github remote templates ([f961c13](https://github.com/chenasraf/simple-scaffold/commit/f961c13))
* feat: support for remote template configs ([05487f4](https://github.com/chenasraf/simple-scaffold/commit/05487f4))
* build: always build docs ([ce39918](https://github.com/chenasraf/simple-scaffold/commit/ce39918))
* build: fix git/github step order ([c50518a](https://github.com/chenasraf/simple-scaffold/commit/c50518a))
## 1.4.0 (2023-04-28)
* chore(release): 1.4.0 [skip ci] ([83d3807](https://github.com/chenasraf/simple-scaffold/commit/83d3807))
* feat: add `--key` | `-k` to config loader ([6c5ba0b](https://github.com/chenasraf/simple-scaffold/commit/6c5ba0b))
## <small>1.3.2 (2023-04-28)</small>
* chore(release): 1.3.2 [skip ci] ([2c4eccd](https://github.com/chenasraf/simple-scaffold/commit/2c4eccd))
* fix: release build ([2c23fa9](https://github.com/chenasraf/simple-scaffold/commit/2c23fa9))
* fix: release build asset ([0bef2df](https://github.com/chenasraf/simple-scaffold/commit/0bef2df))
## <small>1.3.1 (2023-04-28)</small>
* chore: bump version number + fix changelog ([eba7897](https://github.com/chenasraf/simple-scaffold/commit/eba7897))
* chore(release): 1.3.1 [skip ci] ([398a5d7](https://github.com/chenasraf/simple-scaffold/commit/398a5d7))
* fix: docs ([6e19a86](https://github.com/chenasraf/simple-scaffold/commit/6e19a86))
* fix: remove old peer-dep ([c7e2ef8](https://github.com/chenasraf/simple-scaffold/commit/c7e2ef8))
* docs: fix doc links ([36dd27e](https://github.com/chenasraf/simple-scaffold/commit/36dd27e))
## 1.3.0 (2023-04-25)
* chore(release): 1.3.0 [skip ci] ([dfdbca5](https://github.com/chenasraf/simple-scaffold/commit/dfdbca5))
* docs: add changelog to typedoc ([873fa77](https://github.com/chenasraf/simple-scaffold/commit/873fa77))
* docs: add table of contents ([8a2207b](https://github.com/chenasraf/simple-scaffold/commit/8a2207b))
* docs: clean up css ([1d6643b](https://github.com/chenasraf/simple-scaffold/commit/1d6643b))
* docs: fix CHANGELOG.md ([0c8e6e7](https://github.com/chenasraf/simple-scaffold/commit/0c8e6e7))
* docs: move migration to pages, fix urls ([1460868](https://github.com/chenasraf/simple-scaffold/commit/1460868))
* docs: remove unnecessary nested menus ([42568e0](https://github.com/chenasraf/simple-scaffold/commit/42568e0))
* docs: reorganize file structure ([f41ebfb](https://github.com/chenasraf/simple-scaffold/commit/f41ebfb))
* docs: split into files ([a4498f9](https://github.com/chenasraf/simple-scaffold/commit/a4498f9))
* docs: update config doc ([5a2b187](https://github.com/chenasraf/simple-scaffold/commit/5a2b187))
* docs: update readme ([f28280e](https://github.com/chenasraf/simple-scaffold/commit/f28280e))
* docs: use js for typedoc config ([9b86499](https://github.com/chenasraf/simple-scaffold/commit/9b86499))
* build: only generate docs on master ([1e0b731](https://github.com/chenasraf/simple-scaffold/commit/1e0b731))
* build: remove unnecessary yarn pack ([b3f7912](https://github.com/chenasraf/simple-scaffold/commit/b3f7912))
* build: update changelog sections ([1ce4a41](https://github.com/chenasraf/simple-scaffold/commit/1ce4a41))
* fix: config option should not be mandatory ([3db6a91](https://github.com/chenasraf/simple-scaffold/commit/3db6a91))
* fix: export config file type ([4302eb5](https://github.com/chenasraf/simple-scaffold/commit/4302eb5))
* feat: load scaffold config from files ([c398976](https://github.com/chenasraf/simple-scaffold/commit/c398976))
## 1.2.0 (2023-04-24)
* chore: bump version number ([8e432bf](https://github.com/chenasraf/simple-scaffold/commit/8e432bf))
* chore: bump version number [skip-ci] ([029f260](https://github.com/chenasraf/simple-scaffold/commit/029f260))
* chore: update dependencies ([20400bd](https://github.com/chenasraf/simple-scaffold/commit/20400bd))
* chore: update FUNDING.yml ([1bfcafa](https://github.com/chenasraf/simple-scaffold/commit/1bfcafa))
* chore(release): 1.2.0 [skip ci] ([7da786a](https://github.com/chenasraf/simple-scaffold/commit/7da786a))
* fix: ci node version ([767d34c](https://github.com/chenasraf/simple-scaffold/commit/767d34c))
* fix: github action node version ([7c19c53](https://github.com/chenasraf/simple-scaffold/commit/7c19c53))
* fix: github action node version ([94fec76](https://github.com/chenasraf/simple-scaffold/commit/94fec76))
* fix: semantic-release build dir ([f7956dd](https://github.com/chenasraf/simple-scaffold/commit/f7956dd))
* fix: support quote wrapping in append-data ([4fecca8](https://github.com/chenasraf/simple-scaffold/commit/4fecca8))
* build: add missing dependencies ([75641e5](https://github.com/chenasraf/simple-scaffold/commit/75641e5))
* build: add missing dependencies ([f4c745b](https://github.com/chenasraf/simple-scaffold/commit/f4c745b))
* build: fix build ([47b4c42](https://github.com/chenasraf/simple-scaffold/commit/47b4c42))
* build: fix docs build ([7a4c0ab](https://github.com/chenasraf/simple-scaffold/commit/7a4c0ab))
* build: semantic-release ([2050ea3](https://github.com/chenasraf/simple-scaffold/commit/2050ea3))
* build: update dependencies & fix build ([59a46b0](https://github.com/chenasraf/simple-scaffold/commit/59a46b0))
* build: update github action versions ([222e1a0](https://github.com/chenasraf/simple-scaffold/commit/222e1a0))
* docs: update docs ([7ef6d58](https://github.com/chenasraf/simple-scaffold/commit/7ef6d58))
* docs: update domain ([8f5bee8](https://github.com/chenasraf/simple-scaffold/commit/8f5bee8))
* docs: update spacing ([ed385ec](https://github.com/chenasraf/simple-scaffold/commit/ed385ec))
* docs: update table css ([833ea9d](https://github.com/chenasraf/simple-scaffold/commit/833ea9d))
* docs: update typedoc & remove custom theme ([8fb508f](https://github.com/chenasraf/simple-scaffold/commit/8fb508f))
* docs: update typedoc version ([c334396](https://github.com/chenasraf/simple-scaffold/commit/c334396))
* feat: append-data cli flag ([3c5c2de](https://github.com/chenasraf/simple-scaffold/commit/3c5c2de))
* Bump json5 from 2.2.0 to 2.2.3 ([e28c4db](https://github.com/chenasraf/simple-scaffold/commit/e28c4db))
* Bump minimatch from 3.0.4 to 3.1.2 ([ee4e52c](https://github.com/chenasraf/simple-scaffold/commit/ee4e52c))
## <small>1.1.3 (2023-03-11)</small>
* chore: bump version number [publish] ([d7d2b13](https://github.com/chenasraf/simple-scaffold/commit/d7d2b13))
* fix: base path ([943717a](https://github.com/chenasraf/simple-scaffold/commit/943717a))
* fix: binary files + add tests ([e450ad2](https://github.com/chenasraf/simple-scaffold/commit/e450ad2))
* add gaid to docs ([9bd6219](https://github.com/chenasraf/simple-scaffold/commit/9bd6219))
* fix build ([0364247](https://github.com/chenasraf/simple-scaffold/commit/0364247))
* fix build ([ac2c0d7](https://github.com/chenasraf/simple-scaffold/commit/ac2c0d7))
* fix doc deps ([12f8bca](https://github.com/chenasraf/simple-scaffold/commit/12f8bca))
* fix docs build ([0042c12](https://github.com/chenasraf/simple-scaffold/commit/0042c12))
* fix docs build process ([35262b5](https://github.com/chenasraf/simple-scaffold/commit/35262b5))
* fix gtag ([643431d](https://github.com/chenasraf/simple-scaffold/commit/643431d))
* fix workflow [skip publish] ([0d359b1](https://github.com/chenasraf/simple-scaffold/commit/0d359b1))
* formatting updates ([b569f2b](https://github.com/chenasraf/simple-scaffold/commit/b569f2b))
* improve docs build process ([3b77e69](https://github.com/chenasraf/simple-scaffold/commit/3b77e69))
* improve docs build process ([3cf9359](https://github.com/chenasraf/simple-scaffold/commit/3cf9359))
* improve docs build process ([8957b59](https://github.com/chenasraf/simple-scaffold/commit/8957b59))
* npm audit fix ([5571aba](https://github.com/chenasraf/simple-scaffold/commit/5571aba))
* remove unnecessary install ([11edb0d](https://github.com/chenasraf/simple-scaffold/commit/11edb0d))
* revert docs build in ci ([ac8af8e](https://github.com/chenasraf/simple-scaffold/commit/ac8af8e))
* try fix docs build process ([96b93d8](https://github.com/chenasraf/simple-scaffold/commit/96b93d8))
* try to fix docs [skip publish] ([af65eca](https://github.com/chenasraf/simple-scaffold/commit/af65eca))
* Typdoc (#37) [skip publish] ([f2a75c9](https://github.com/chenasraf/simple-scaffold/commit/f2a75c9)), closes [#37](https://github.com/chenasraf/simple-scaffold/issues/37)
* update docs ([a002402](https://github.com/chenasraf/simple-scaffold/commit/a002402))
* update docs ([27a6ba4](https://github.com/chenasraf/simple-scaffold/commit/27a6ba4))
* update docs ([923b531](https://github.com/chenasraf/simple-scaffold/commit/923b531))
* update docs ([bf10fb8](https://github.com/chenasraf/simple-scaffold/commit/bf10fb8))
* update docs [skip publish] ([4e2fa01](https://github.com/chenasraf/simple-scaffold/commit/4e2fa01))
* update docs + formatting update ([2841ebd](https://github.com/chenasraf/simple-scaffold/commit/2841ebd))
* update docs + spell checker ([7c69010](https://github.com/chenasraf/simple-scaffold/commit/7c69010))
* update docs GA ([869911b](https://github.com/chenasraf/simple-scaffold/commit/869911b))
* Update FUNDING.yml ([a8e9f71](https://github.com/chenasraf/simple-scaffold/commit/a8e9f71))
* update help command ([8c8cede](https://github.com/chenasraf/simple-scaffold/commit/8c8cede))
* update intro gif ([1b37a8e](https://github.com/chenasraf/simple-scaffold/commit/1b37a8e))
* update intro gif ([576798c](https://github.com/chenasraf/simple-scaffold/commit/576798c))
* Update README.md ([5308ef9](https://github.com/chenasraf/simple-scaffold/commit/5308ef9))
* Update README.md ([6fa2a8b](https://github.com/chenasraf/simple-scaffold/commit/6fa2a8b))
* Update README.md ([adba649](https://github.com/chenasraf/simple-scaffold/commit/adba649))
* Update README.md ([cd68ab4](https://github.com/chenasraf/simple-scaffold/commit/cd68ab4))
* Update README.md ([bdc23e7](https://github.com/chenasraf/simple-scaffold/commit/bdc23e7))
* Update README.md ([6160a04](https://github.com/chenasraf/simple-scaffold/commit/6160a04))
* update test desc ([40606ed](https://github.com/chenasraf/simple-scaffold/commit/40606ed))
* docs: fix typo ([1d0c20c](https://github.com/chenasraf/simple-scaffold/commit/1d0c20c))
## 1.1.0 (2022-04-21)
* Add keywords to package.json [skip ci] ([4a4e024](https://github.com/chenasraf/simple-scaffold/commit/4a4e024))
* Bump minimist from 1.2.5 to 1.2.6 ([56be5f3](https://github.com/chenasraf/simple-scaffold/commit/56be5f3))
* bump version number ([dffa81f](https://github.com/chenasraf/simple-scaffold/commit/dffa81f))
* update package.json ([3c57638](https://github.com/chenasraf/simple-scaffold/commit/3c57638))
* Update README.md [skip ci] ([86a7a2c](https://github.com/chenasraf/simple-scaffold/commit/86a7a2c))
* Update README.md [skip ci] ([d3259c4](https://github.com/chenasraf/simple-scaffold/commit/d3259c4))
* v1.1 (#35) ([a3deda2](https://github.com/chenasraf/simple-scaffold/commit/a3deda2)), closes [#35](https://github.com/chenasraf/simple-scaffold/issues/35)
* chore: use rimraf + add error debug log [skip ci] ([b2799d0](https://github.com/chenasraf/simple-scaffold/commit/b2799d0))
* docs: Update README.md [skip ci] ([f4997c6](https://github.com/chenasraf/simple-scaffold/commit/f4997c6))
## <small>1.0.3 (2022-03-03)</small>
* bump version number ([cb6e06f](https://github.com/chenasraf/simple-scaffold/commit/cb6e06f))
* bump version number [skip ci] ([21c4ab6](https://github.com/chenasraf/simple-scaffold/commit/21c4ab6))
* fix transform of windows-style paths ([56f1340](https://github.com/chenasraf/simple-scaffold/commit/56f1340))
* fixed more windows paths, updated tests ([52cb3e7](https://github.com/chenasraf/simple-scaffold/commit/52cb3e7))
* import/file cleanup ([d6e1693](https://github.com/chenasraf/simple-scaffold/commit/d6e1693))
* improved test ([f07df79](https://github.com/chenasraf/simple-scaffold/commit/f07df79))
* refactor handlebarsParse - remove redundant arg ([89d7897](https://github.com/chenasraf/simple-scaffold/commit/89d7897))
* remove unnecessary package [skip ci] ([1783ddf](https://github.com/chenasraf/simple-scaffold/commit/1783ddf))
* update README.md ([e26a434](https://github.com/chenasraf/simple-scaffold/commit/e26a434))
* updated tests ([a043a05](https://github.com/chenasraf/simple-scaffold/commit/a043a05))
## <small>1.0.1-pre.1 (2022-02-17)</small>
* add build step ([f1698d2](https://github.com/chenasraf/simple-scaffold/commit/f1698d2))
* add cmd args ([7cdf5e4](https://github.com/chenasraf/simple-scaffold/commit/7cdf5e4))
* add dry run option ([aeddd44](https://github.com/chenasraf/simple-scaffold/commit/aeddd44))
* add export for cmd_util ([6b57406](https://github.com/chenasraf/simple-scaffold/commit/6b57406))
* add subFolderNameHelper arg ([81ba5f5](https://github.com/chenasraf/simple-scaffold/commit/81ba5f5))
* add tests ([c42a58c](https://github.com/chenasraf/simple-scaffold/commit/c42a58c))
* added --quiet flag ([4f81654](https://github.com/chenasraf/simple-scaffold/commit/4f81654))
* added custom helpers ([d03d0e0](https://github.com/chenasraf/simple-scaffold/commit/d03d0e0))
* bump alpha version number ([d797e5b](https://github.com/chenasraf/simple-scaffold/commit/d797e5b))
* Bump ansi-regex from 5.0.0 to 5.0.1 ([36f8b87](https://github.com/chenasraf/simple-scaffold/commit/36f8b87))
* bump version number ([5ab2637](https://github.com/chenasraf/simple-scaffold/commit/5ab2637))
* bump version number ([53e8bc4](https://github.com/chenasraf/simple-scaffold/commit/53e8bc4))
* bump version: v1.0.0 ([d06c0d6](https://github.com/chenasraf/simple-scaffold/commit/d06c0d6))
* code splitting ([208ee30](https://github.com/chenasraf/simple-scaffold/commit/208ee30))
* Create FUNDING.yml ([40b5920](https://github.com/chenasraf/simple-scaffold/commit/40b5920))
* fail handlebars parse silently ([0af6392](https://github.com/chenasraf/simple-scaffold/commit/0af6392))
* fix basename in some cases ([b1b1aca](https://github.com/chenasraf/simple-scaffold/commit/b1b1aca))
* fix build output files ([99c9055](https://github.com/chenasraf/simple-scaffold/commit/99c9055))
* fix build/publish cmd ([a59f29d](https://github.com/chenasraf/simple-scaffold/commit/a59f29d))
* fix cmd ([cd34930](https://github.com/chenasraf/simple-scaffold/commit/cd34930))
* fix copyright ([54b9023](https://github.com/chenasraf/simple-scaffold/commit/54b9023))
* fix errors, fix nested output ([8413225](https://github.com/chenasraf/simple-scaffold/commit/8413225))
* fix log level 0 ([84e6207](https://github.com/chenasraf/simple-scaffold/commit/84e6207))
* fix main field in package.json ([7273538](https://github.com/chenasraf/simple-scaffold/commit/7273538))
* fix readme [skip publish] ([ad30ee0](https://github.com/chenasraf/simple-scaffold/commit/ad30ee0))
* fix yarn.lock ([a21a35f](https://github.com/chenasraf/simple-scaffold/commit/a21a35f))
* fixed release tarball file location ([3f2945e](https://github.com/chenasraf/simple-scaffold/commit/3f2945e))
* fixed release tarball file location ([9303446](https://github.com/chenasraf/simple-scaffold/commit/9303446))
* fixes + add log level [skip publish] ([2623b78](https://github.com/chenasraf/simple-scaffold/commit/2623b78))
* helpers fix ([f4cc44c](https://github.com/chenasraf/simple-scaffold/commit/f4cc44c))
* improve tests ([2d5626c](https://github.com/chenasraf/simple-scaffold/commit/2d5626c))
* maintain directory structure ([564e821](https://github.com/chenasraf/simple-scaffold/commit/564e821))
* major refactor ([5483490](https://github.com/chenasraf/simple-scaffold/commit/5483490))
* migrate cmd to massarg + update tests ([a52f9a0](https://github.com/chenasraf/simple-scaffold/commit/a52f9a0))
* refactoring - code cleanup ([c3835a7](https://github.com/chenasraf/simple-scaffold/commit/c3835a7))
* remove excess files ([d0c0152](https://github.com/chenasraf/simple-scaffold/commit/d0c0152))
* remove types from package.json ([559b5ad](https://github.com/chenasraf/simple-scaffold/commit/559b5ad))
* run tests on ci [skip publish] ([2305083](https://github.com/chenasraf/simple-scaffold/commit/2305083))
* support node 12 for fs package ([bc224d9](https://github.com/chenasraf/simple-scaffold/commit/bc224d9))
* support node 12 for fs package ([cf923d8](https://github.com/chenasraf/simple-scaffold/commit/cf923d8))
* try fix release upload ([8575b1e](https://github.com/chenasraf/simple-scaffold/commit/8575b1e))
* try fix workflow ([6f03ed9](https://github.com/chenasraf/simple-scaffold/commit/6f03ed9))
* try fix workflow ([474a3dc](https://github.com/chenasraf/simple-scaffold/commit/474a3dc))
* try new release version ([d8aba21](https://github.com/chenasraf/simple-scaffold/commit/d8aba21))
* try to fix workflow ([c17e630](https://github.com/chenasraf/simple-scaffold/commit/c17e630))
* update alpha workflow ([91116bb](https://github.com/chenasraf/simple-scaffold/commit/91116bb))
* update deps + add MIGRATION.md ([5b72b6c](https://github.com/chenasraf/simple-scaffold/commit/5b72b6c))
* update deps + update cmd requirements ([d96992c](https://github.com/chenasraf/simple-scaffold/commit/d96992c))
* update docs [skip publish] ([c2bc8b7](https://github.com/chenasraf/simple-scaffold/commit/c2bc8b7))
* update jest config ([01e458e](https://github.com/chenasraf/simple-scaffold/commit/01e458e))
* Update MIGRATION.md [skip ci] ([d0a0db0](https://github.com/chenasraf/simple-scaffold/commit/d0a0db0))
* update readme [skip ci] ([9259939](https://github.com/chenasraf/simple-scaffold/commit/9259939))
* update readme [skip ci] ([09403e1](https://github.com/chenasraf/simple-scaffold/commit/09403e1))
* Update README.md ([edcf1ac](https://github.com/chenasraf/simple-scaffold/commit/edcf1ac))
* update README.md, default output fix ([391a08a](https://github.com/chenasraf/simple-scaffold/commit/391a08a))
* update workflow ([27e84d1](https://github.com/chenasraf/simple-scaffold/commit/27e84d1))
* update workflows ([c7749a8](https://github.com/chenasraf/simple-scaffold/commit/c7749a8))
* update workflows ([a6f25fa](https://github.com/chenasraf/simple-scaffold/commit/a6f25fa))
* update workflows [skip publish] ([956b007](https://github.com/chenasraf/simple-scaffold/commit/956b007))
* update workflows [skip publish] ([54848f9](https://github.com/chenasraf/simple-scaffold/commit/54848f9))
* use node 12 ([9385371](https://github.com/chenasraf/simple-scaffold/commit/9385371))
* v0.7.4 ([43b6496](https://github.com/chenasraf/simple-scaffold/commit/43b6496))
* publish: debug mode off, try to fix workflow ([535260a](https://github.com/chenasraf/simple-scaffold/commit/535260a))
* publish: debug mode on ([1498857](https://github.com/chenasraf/simple-scaffold/commit/1498857))
* build: add workflow ([0ce19a7](https://github.com/chenasraf/simple-scaffold/commit/0ce19a7))
* build: update workflow ([3ee66b2](https://github.com/chenasraf/simple-scaffold/commit/3ee66b2))
* chore: cleanup ([8fcc7a6](https://github.com/chenasraf/simple-scaffold/commit/8fcc7a6))
* docs: update README ([b4b0de6](https://github.com/chenasraf/simple-scaffold/commit/b4b0de6))
## <small>0.7.5 (2021-09-26)</small>
* fix main field in package.json ([3cb9a6f](https://github.com/chenasraf/simple-scaffold/commit/3cb9a6f))
## <small>0.7.4 (2021-09-26)</small>
* v0.7.4 ([12974b5](https://github.com/chenasraf/simple-scaffold/commit/12974b5))
## <small>0.7.3 (2021-09-26)</small>
* added --quiet flag ([7f98d46](https://github.com/chenasraf/simple-scaffold/commit/7f98d46))
* Bump handlebars from 4.7.6 to 4.7.7 ([552614c](https://github.com/chenasraf/simple-scaffold/commit/552614c))
* Bump hosted-git-info from 2.8.8 to 2.8.9 ([2e12907](https://github.com/chenasraf/simple-scaffold/commit/2e12907))
* Bump lodash from 4.17.20 to 4.17.21 ([5b7e0e3](https://github.com/chenasraf/simple-scaffold/commit/5b7e0e3))
* Bump url-parse from 1.4.7 to 1.5.1 ([0923830](https://github.com/chenasraf/simple-scaffold/commit/0923830))
* update readme ([813f706](https://github.com/chenasraf/simple-scaffold/commit/813f706))
* update readme ([1bc2221](https://github.com/chenasraf/simple-scaffold/commit/1bc2221))
* Update README.md ([cd25b04](https://github.com/chenasraf/simple-scaffold/commit/cd25b04))
## <small>0.7.2 (2021-04-19)</small>
* add basename to output config function (fixes #3) ([f07affa](https://github.com/chenasraf/simple-scaffold/commit/f07affa)), closes [#3](https://github.com/chenasraf/simple-scaffold/issues/3)
* disable overwriting files + parse JSON for locals ([ce22a2c](https://github.com/chenasraf/simple-scaffold/commit/ce22a2c))
## <small>0.6.1 (2021-02-01)</small>
* fix: binary files ([7c0c347](https://github.com/chenasraf/simple-scaffold/commit/7c0c347))
## 0.6.0 (2021-02-01)
* build: upgrade packages ([977288a](https://github.com/chenasraf/simple-scaffold/commit/977288a))
* fix: support deeper file structure ([4afafa5](https://github.com/chenasraf/simple-scaffold/commit/4afafa5))
* 0.5.0 ([7bee2a5](https://github.com/chenasraf/simple-scaffold/commit/7bee2a5))
## 0.5.0 (2019-02-27)
* Fixed output argument + updated README ([06590c4](https://github.com/chenasraf/simple-scaffold/commit/06590c4))
* v0.5.0 ([d4c049b](https://github.com/chenasraf/simple-scaffold/commit/d4c049b))
## <small>0.4.5 (2019-02-27)</small>
* Improved docs ([a410b79](https://github.com/chenasraf/simple-scaffold/commit/a410b79))
* v0.4.5 ([c4f2dfb](https://github.com/chenasraf/simple-scaffold/commit/c4f2dfb))
## <small>0.4.4 (2019-02-27)</small>
* v0.4.4 ([71d544a](https://github.com/chenasraf/simple-scaffold/commit/71d544a))
## <small>0.4.3 (2019-02-27)</small>
* mapfile ([d7a4362](https://github.com/chenasraf/simple-scaffold/commit/d7a4362))
* v0.4.3 ([20389d7](https://github.com/chenasraf/simple-scaffold/commit/20389d7))
## <small>0.4.2 (2019-02-25)</small>
* bugfixes ([a92c471](https://github.com/chenasraf/simple-scaffold/commit/a92c471))
* mapfile ([07b1c4b](https://github.com/chenasraf/simple-scaffold/commit/07b1c4b))
* v0.4.2 ([0a2d7c0](https://github.com/chenasraf/simple-scaffold/commit/0a2d7c0))
## <small>0.4.1 (2019-02-25)</small>
* added 'createSubFolder' option, cleaned up CMD file ([d6195c6](https://github.com/chenasraf/simple-scaffold/commit/d6195c6))
* Update README.md ([b14e3d2](https://github.com/chenasraf/simple-scaffold/commit/b14e3d2))
* v0.4.1 ([ec91fbf](https://github.com/chenasraf/simple-scaffold/commit/ec91fbf))
* Bugfix: dotfiles ([85aa9f9](https://github.com/chenasraf/simple-scaffold/commit/85aa9f9))
## <small>0.3.1 (2018-01-15)</small>
* Update README.md ([686b0bf](https://github.com/chenasraf/simple-scaffold/commit/686b0bf))
* v0.3.1 ([fa2ddca](https://github.com/chenasraf/simple-scaffold/commit/fa2ddca))
## 0.3.0 (2018-01-15)
* cleanups ([4f29a61](https://github.com/chenasraf/simple-scaffold/commit/4f29a61))
* output is optional ([14b60ff](https://github.com/chenasraf/simple-scaffold/commit/14b60ff))
* Rename ([45e8de3](https://github.com/chenasraf/simple-scaffold/commit/45e8de3))
* Uodate README.md ([b09299b](https://github.com/chenasraf/simple-scaffold/commit/b09299b))
* Update README.md ([1275743](https://github.com/chenasraf/simple-scaffold/commit/1275743))
* Update README.md ([a3a77e2](https://github.com/chenasraf/simple-scaffold/commit/a3a77e2))
* v0.3.0 ([0be29dd](https://github.com/chenasraf/simple-scaffold/commit/0be29dd))
## 0.2.0 (2018-01-05)
* Fixed cmd ([4ca7c6a](https://github.com/chenasraf/simple-scaffold/commit/4ca7c6a))
* Improve build ([0fd9964](https://github.com/chenasraf/simple-scaffold/commit/0fd9964))
* Improve cmd script, add readme ([e391f8f](https://github.com/chenasraf/simple-scaffold/commit/e391f8f))
* Move all scripts to webpack, add wip cmd script for bin ([3e42ac5](https://github.com/chenasraf/simple-scaffold/commit/3e42ac5))
* Use handlebars, add cmd script in package.json ([e64c0e4](https://github.com/chenasraf/simple-scaffold/commit/e64c0e4))
* v0.2.0 ([f360159](https://github.com/chenasraf/simple-scaffold/commit/f360159))
## <small>0.1.5 (2018-01-01)</small>
* v0.1.5 ([eecec82](https://github.com/chenasraf/simple-scaffold/commit/eecec82))
## <small>0.1.4 (2018-01-01)</small>
* v0.1.4 ([6ec19fc](https://github.com/chenasraf/simple-scaffold/commit/6ec19fc))
## <small>0.1.3 (2018-01-01)</small>
* v0.1.3 ([a5776d6](https://github.com/chenasraf/simple-scaffold/commit/a5776d6))
## <small>0.1.2 (2018-01-01)</small>
* Get comp name from argv ([c341fe7](https://github.com/chenasraf/simple-scaffold/commit/c341fe7))
* Initial commit ([4896c10](https://github.com/chenasraf/simple-scaffold/commit/4896c10))
* Published + renamed, bugfixes ([1e0abf9](https://github.com/chenasraf/simple-scaffold/commit/1e0abf9))
* Remove dist from gitignore ([7f9a385](https://github.com/chenasraf/simple-scaffold/commit/7f9a385))
* Scaffold basically works, file path resolving sucky atm ([652621f](https://github.com/chenasraf/simple-scaffold/commit/652621f))
* v0.1.2 ([0fed899](https://github.com/chenasraf/simple-scaffold/commit/0fed899))

444
README.md
View File

@@ -1,148 +1,354 @@
# simple-scaffold
Simple Scaffold allows you to create your structured files based on templates.
<p align="center">
<img src="https://chenasraf.github.io//simple-scaffold/img/logo-lg.png" alt="Logo" />
</p>
## Install
You can either use it as a command line tool or import into your own code and run from there.
<h2 align="center">
```bash
# npm
npm install [-g] simple-scaffold
# yarn
yarn [global] add simple-scaffold
[GitHub](https://github.com/chenasraf/simple-scaffold) |
[Documentation](https://chenasraf.github.io/simple-scaffold) |
[NPM](https://npmjs.com/package/simple-scaffold) | [casraf.dev](https://casraf.dev)
![master](https://img.shields.io/github/package-json/v/chenasraf/simple-scaffold/master?label=master)
![build](https://img.shields.io/github/actions/workflow/status/chenasraf/simple-scaffold/release.yml?branch=master)
</h2>
Simple Scaffold is a file scaffolding tool. You define templates once, then generate files from them
whenever you need — whether it's a single component or an entire app boilerplate.
Templates use **Handlebars.js** syntax, so you can inject data, loop over lists, use conditionals,
and write custom helpers. It works as a CLI or as a Node.js library, and it doesn't care what kind
of files you're generating.
<div align="center">
![Intro](https://chenasraf.github.io/simple-scaffold/img/intro.gif)
</div>
---
> **Full documentation is available at
> [chenasraf.github.io/simple-scaffold](https://chenasraf.github.io/simple-scaffold)** — including
> detailed guides on [CLI usage](https://chenasraf.github.io/simple-scaffold/docs/usage/cli),
> [Node.js API](https://chenasraf.github.io/simple-scaffold/docs/usage/node),
> [templates](https://chenasraf.github.io/simple-scaffold/docs/usage/templates),
> [configuration files](https://chenasraf.github.io/simple-scaffold/docs/usage/configuration_files),
> [examples](https://chenasraf.github.io/simple-scaffold/docs/usage/examples), and
> [migration from v1/v2](https://chenasraf.github.io/simple-scaffold/docs/usage/migration).
## Table of Contents
- [Getting Started](#getting-started)
- [Configuration Files](#configuration-files)
- [Templates](#templates)
- [Interactive Mode & Inputs](#interactive-mode--inputs)
- [Remote Templates](#remote-templates)
- [CLI Reference](#cli-reference)
- [Node.js API](#nodejs-api)
- [Built-in Helpers](#built-in-helpers)
- [Contributing](#contributing)
## Getting Started
### Install
```sh
npm install -D simple-scaffold
# or use directly with npx
npx simple-scaffold
```
## Use as a command line tool
The first non-token argument (that has no `--` prefix) will be used as the scaffold name.
The rest is ignored, of course except for the available arguments below.
### Initialize a Project
```bash
simple-scaffold MyComponent --template scaffolds/component/**/* \
--output src/components \
--locals myProp="propname",myVal=123
Run `init` to create a config file and an example template:
```sh
npx simple-scaffold init
```
You can add this as a script in your `package.json`:
This creates `scaffold.config.js` and `templates/default/{{name}}.md`. Now generate files:
```json
```sh
npx simple-scaffold MyProject
```
### One-off Usage (No Config)
Generate files from a template directory without a config file:
```sh
npx simple-scaffold -t templates/component -o src/components MyComponent
```
## Configuration Files
Config files let you define reusable scaffold definitions. Simple Scaffold **auto-detects** config
files in the current directory — no `--config` flag needed.
It searches for these files in order:
`scaffold.config.{mjs,cjs,js,json}`, `scaffold.{mjs,cjs,js,json}`, `.scaffold.{mjs,cjs,js,json}`
### Example
```js
// scaffold.config.js
module.exports = {
component: {
templates: ["templates/component"],
output: "src/components",
},
page: {
templates: ["templates/page"],
output: "src/pages",
subdir: true,
},
}
```
Then run:
```sh
npx simple-scaffold -k component MyComponent
npx simple-scaffold -k page Dashboard
```
Use the key `default` to skip the `-k` flag entirely.
### Listing Available Templates
```sh
npx simple-scaffold list
npx simple-scaffold list -c path/to/config.js
```
## Templates
Templates are regular files in a directory. Both **file names** and **file contents** support
Handlebars syntax. Simple Scaffold preserves the directory structure of your template folder.
### Example Template
`templates/component/{{pascalCase name}}.tsx`
```tsx
// Created: {{ now 'yyyy-MM-dd' }}
import React from "react"
export default {{ pascalCase name }}: React.FC = (props) => {
return (
<div className="{{ camelCase name }}">{{ pascalCase name }} Component</div>
)
}
```
Running `npx simple-scaffold -t templates/component -o src/components PageWrapper` produces
`src/components/PageWrapper.tsx` with all tokens replaced.
### Glob Patterns & Exclusions
Template paths support globs and negation:
```js
{
"scripts": {
"scaffold": "node node_modules/simple-scaffold/dist/cmd.js --template scaffolds/component/**/* --output src/components --locals myProp=\"propname\",myVal=123"
}
templates: ["templates/component/**", "!templates/component/README.md"]
}
```
## Scaffolding
Scaffolding will replace {{vars}} in both the file name and its contents and put the transformed files
in `<output>/<{{Name}}`.
### .scaffoldignore
Your context will be pre-populated with the following:
- `{{Name}}`: CapitalizedName of the component
- `{{name}}`: camelCasedName of the component
Place a `.scaffoldignore` file in your template directory to exclude files. It works like
`.gitignore` — one pattern per line, `#` for comments.
Any `locals` you add in the config will populate with their names wrapped in `{{` and `}}`.
They are all stringified, so be sure to parse them accordingly by creating a script, if necessary.
## Interactive Mode & Inputs
### Command line options
##### `--template glob [--template glob2 [...]]` (required) (alias: -t)
A glob pattern of template files to load.
When running in a terminal, Simple Scaffold prompts for any missing required values (name, output,
template key). Config files can also define **inputs** — custom fields that are prompted
interactively:
A template file may be of any type and extension, and supports [Handlebars](https://handlebarsjs.com) as a parsing engine for the file names and contents, so you may customize both with variables from your configuration.
You can load more than one template list by simple adding more `--template` arguments.
##### `--output path` (optional) (alias -o)
The output directory to put the new files in. They will attempt to maintain their regular structure as they are found, if possible.
Your new scaffold will be placed under a directory with the scaffold name from the argumemts.
You may also pass a function to transform the output path for each file individually.
This function takes 2 arguments: filename, and base glob path
##### `--locals key=value [--locals key=value [,...]]` (optional) (alias: -l)
Pass a KV map to the template for parsing.
##### `--create-sub-folder [true|false]` (optional) (alias -S) (default: true)
Whether to create a subfolder for the output with all the files inside, or simply dump them directly in the output folder.
#####
### Use in Node.js
You can also build the scaffold yourself, if you want to create more complex arguments or scaffold groups.
Simply pass a config object to the constructor, and invoke `run()` when you are ready to start.
The config takes similar arguments to the command line:
```javascript
const SimpleScaffold = require('simple-scaffold').default
const scaffold = new SimpleScaffold({
name: 'component',
templates: [path.join(__dirname, 'scaffolds', 'component')],
output: path.join(__dirname, 'src', 'components'),
createSubFolder: true,
locals: {
property: 'value',
}
}).run()
```
## Example Scaffold Input
### Input Directory structure
```
- project
- scaffold
- {{Name}}.js
- src
- components
- ...
```
#### project/scaffold/{{Name}}.js
```js
const React = require('react')
module.exports = class {{Name}} extends React.Component {
render() {
<div className="{{className}}">{{Name}} Component</div>
}
module.exports = {
component: {
templates: ["templates/component"],
output: "src/components",
inputs: {
author: { type: "text", message: "Author name", required: true },
license: { type: "select", message: "License", options: ["MIT", "Apache-2.0", "GPL-3.0"] },
isPublic: { type: "confirm", message: "Public package?" },
priority: { type: "number", message: "Priority level", default: 1 },
},
},
}
```
### Run Example
```bash
simple-scaffold MyComponent \
--template project/scaffold/**/* \
--output src/components \
--locals 'className=my-component`
**Input types:** `text` (default), `select`, `confirm`, `number`
Pre-fill inputs from the command line to skip prompts:
```sh
npx simple-scaffold -k component -D author=John -D license=MIT MyComponent
```
## Example Scaffold Output
#### Directory structure
```
- project
- src
- components
- MyComponent
- MyComponent.js
- ...
## Remote Templates
Use templates from any Git repository:
```sh
# GitHub shorthand
npx simple-scaffold -g username/repo -k component MyComponent
# Full Git URL (GitLab, Bitbucket, etc.)
npx simple-scaffold -g https://gitlab.com/user/repo.git -k component MyComponent
```
With `createSubfolder = false`:
```
- project
- src
- components
- MyComponent.js
- ...
```
The repository is cloned to a temporary directory, used, and cleaned up automatically.
## CLI Reference
### Commands
| Command | Description |
| ------------- | ---------------------------------------- |
| `[name]` | Generate files from a template (default) |
| `init` | Create config file and example template |
| `list` / `ls` | List available template keys in a config |
### Options
| Flag | Short | Description | Default |
| ------------------------------ | --------- | ---------------------------------------------------- | ----------- |
| `--config` | `-c` | Path to config file or directory | auto-detect |
| `--git` | `-g` | Git URL or GitHub shorthand | |
| `--key` | `-k` | Template key from config | `default` |
| `--output` | `-o` | Output directory | |
| `--templates` | `-t` | Template file paths or globs | |
| `--data` | `-d` | Custom JSON data | |
| `--append-data` | `-D` | Key-value data (`key=string`, `key:=raw`) | |
| `--subdir`/`--no-subdir` | `-s`/`-S` | Create parent directory with input name | `false` |
| `--subdir-helper` | `-H` | Helper to transform subdir name | |
| `--overwrite`/`--no-overwrite` | `-w`/`-W` | Overwrite existing files | `false` |
| `--dry-run` | `-dr` | Preview output without writing files | `false` |
| `--before-write` | `-B` | Script to run before each file is written | |
| `--after-scaffold` | `-A` | Shell command to run after scaffolding | |
| `--quiet` | `-q` | Suppress output | |
| `--log-level` | `-l` | Log level (`none`, `debug`, `info`, `warn`, `error`) | `info` |
| `--version` | `-v` | Show version | |
| `--help` | `-h` | Show help | |
## Node.js API
#### project/scaffold/MyComponent/MyComponent.js
```js
const React = require('react')
import Scaffold from "simple-scaffold"
module.exports = class MyComponent extends React.Component {
render() {
<div className="my-component">MyComponent Component</div>
}
// Basic usage
const scaffold = new Scaffold({
name: "MyComponent",
templates: ["templates/component"],
output: "src/components",
})
await scaffold.run()
// Load from config file
const scaffold = await Scaffold.fromConfig("scaffold.config.js", {
key: "component",
name: "MyComponent",
})
await scaffold.run()
```
### Config Options
| Option | Type | Description |
| --------------- | -------------------------- | ------------------------------------- |
| `name` | `string` | Name for generated files (required) |
| `templates` | `string[]` | Template paths or globs (required) |
| `output` | `string \| Function` | Output directory or per-file function |
| `data` | `Record<string, unknown>` | Custom template data |
| `inputs` | `Record<string, Input>` | Interactive input definitions |
| `helpers` | `Record<string, Function>` | Custom Handlebars helpers |
| `subdir` | `boolean` | Create parent directory with name |
| `subdirHelper` | `string` | Helper for subdir name transformation |
| `overwrite` | `boolean \| Function` | Overwrite existing files |
| `dryRun` | `boolean` | Preview without writing |
| `logLevel` | `string` | Log verbosity |
| `beforeWrite` | `Function` | Async hook before each file write |
| `afterScaffold` | `Function \| string` | Hook after all files are written |
## Built-in Helpers
All helpers work in both file names and file contents.
### Case Helpers
| Helper | Example Input | Output |
| ------------ | ------------- | --------- |
| `camelCase` | `my name` | `myName` |
| `pascalCase` | `my name` | `MyName` |
| `snakeCase` | `my name` | `my_name` |
| `kebabCase` | `my name` | `my-name` |
| `hyphenCase` | `my name` | `my-name` |
| `startCase` | `my name` | `My Name` |
| `upperCase` | `my name` | `MY NAME` |
| `lowerCase` | `My Name` | `my name` |
### Date Helpers
```handlebars
{{now "yyyy-MM-dd"}}
{{now "yyyy-MM-dd HH:mm" -1 "hours"}}
{{date myDateVar "yyyy-MM-dd"}}
{{date "2077-01-01T00:00:00Z" "yyyy-MM-dd" 7 "days"}}
```
### Custom Helpers
Add your own via config:
```js
module.exports = {
component: {
templates: ["templates/component"],
output: "src/components",
helpers: {
shout: (str) => str.toUpperCase() + "!!!",
},
},
}
```
## Contributing
I am developing this package on my free time, so any support, whether code, issues, or just stars is
very helpful to sustaining its life. If you are feeling incredibly generous and would like to donate
just a small amount to help sustain this project, I would be very very thankful!
<a href='https://ko-fi.com/casraf' target='_blank'>
<img
height='36'
src='https://cdn.ko-fi.com/cdn/kofi1.png?v=3'
alt='Buy Me a Coffee at ko-fi.com'
/>
</a>
I welcome any issues or pull requests on GitHub. If you find a bug, or would like a new feature,
don't hesitate to open an appropriate issue and I will do my best to reply promptly.
If you are a developer and want to contribute code, here are some starting tips:
1. Fork this repository
2. Run `pnpm install`
3. Run `pnpm dev` to start file watch mode
4. Make any changes you would like
5. Create tests for your changes
6. Update the relevant documentation (readme, code comments, type comments)
7. Create a PR on upstream
Some tips on getting around the code:
- Use `pnpm cmd` to use the CLI feature of Simple Scaffold from within the root directory, enabling
you to test different behaviors. See `pnpm cmd -h` for more information.
- Use `pnpm test` to run tests
- Use `pnpm docs:build` to build the documentation once
- Use `pnpm docs:watch` to start docs in watch mode
- Use `pnpm build` to build the output

55
cmd.ts
View File

@@ -1,55 +0,0 @@
import SimpleScaffold from './scaffold'
import * as fs from 'fs'
import IScaffold from './index'
import * as cliArgs from 'command-line-args'
import * as cliUsage from 'command-line-usage'
import * as path from 'path'
type Def = cliArgs.OptionDefinition & { description?: string }
function localsParser(content: string) {
const [key, value] = content.split('=')
return { [key]: value }
}
function filePathParser(content: string) {
if (content.startsWith('/')) {
return content
}
return [process.cwd(), content].join(path.sep)
}
const defs: Def[] = [
{ name: 'name', alias: 'n', type: String, description: 'Component output name', defaultOption: true },
{ name: 'templates', alias: 't', type: filePathParser, multiple: true },
{ name: 'output', alias: 'o', type: filePathParser, multiple: true },
{ name: 'locals', alias: 'l', multiple: true, type: localsParser },
{ name: 'create-sub-folder', alias: 'S', type: (text: string) => text && text.trim().length ? ['true', '1', 'on'].includes(text.trim()) : true },
{ name: 'help', alias: 'h', type: Boolean, description: 'Display this help message' },
]
const args = cliArgs(defs, { camelCase: true })
const help = [
{ header: 'Scaffold Generator', content: 'Generate scaffolds for your project based on file templates.' },
{ header: 'Options', optionList: defs }
]
args.locals = (args.locals || []).reduce((all: object, cur: object) => ({ ...all, ...cur }), {} as IScaffold.Config['locals'])
if (args.createSubFolder === null) {
args.createSubFolder = true
}
console.info('Config:', args)
if (args.help || !args.name) {
console.log(cliUsage(help))
process.exit(0)
}
new SimpleScaffold({
name: args.name,
templates: args.templates,
output: args.output,
locals: args.locals,
createSubfolder: args.createSubFolder,
}).run()

292
dist/cmd.js vendored
View File

@@ -1,292 +0,0 @@
#!/usr/bin/env node
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["library"] = factory();
else
root["library"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 6);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = require("path");
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = __webpack_require__(2);
var path = __webpack_require__(0);
var glob = __webpack_require__(3);
var handlebars = __webpack_require__(4);
var SimpleScaffold = /** @class */ (function () {
function SimpleScaffold(config) {
this.locals = {};
var DefaultConfig = {
name: 'scaffold',
templates: [],
output: process.cwd(),
createSubfolder: true,
};
this.config = __assign({}, DefaultConfig, config);
var DefaultLocals = {
Name: this.config.name[0].toUpperCase() + this.config.name.slice(1),
name: this.config.name[0].toLowerCase() + this.config.name.slice(1)
};
this.locals = __assign({}, DefaultLocals, config.locals);
}
SimpleScaffold.prototype.parseLocals = function (text) {
var template = handlebars.compile(text, {
noEscape: true
});
return template(this.locals);
};
SimpleScaffold.prototype.fileList = function (input) {
var output = [];
for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
var checkPath = input_1[_i];
var files = glob.sync(checkPath, { dot: true })
.map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
var idx = checkPath.indexOf('*');
var cleanCheckPath = checkPath;
if (idx >= 0) {
cleanCheckPath = checkPath.slice(0, idx - 1);
}
for (var _a = 0, files_1 = files; _a < files_1.length; _a++) {
var file = files_1[_a];
output.push({ base: cleanCheckPath, file: file });
}
}
return output;
};
SimpleScaffold.prototype.getFileContents = function (filePath) {
console.log(fs.readFileSync(filePath));
return fs.readFileSync(filePath).toString();
};
SimpleScaffold.prototype.getOutputPath = function (file, basePath) {
var out;
if (typeof this.config.output === 'function') {
out = this.config.output(file, basePath);
}
else {
var outputDir = this.config.output + (this.config.createSubfolder ? "/" + this.config.name + "/" : '/');
var idx = file.indexOf(basePath);
var relativeFilePath = file;
if (idx >= 0) {
relativeFilePath = file.slice(idx + basePath.length + 1);
}
out = outputDir + relativeFilePath;
}
return this.parseLocals(out);
};
SimpleScaffold.prototype.writeFile = function (filePath, fileContents) {
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath));
}
console.info('Writing file:', filePath);
fs.writeFile(filePath, fileContents, { encoding: 'utf-8' }, function (err) {
if (err) {
throw err;
}
});
};
SimpleScaffold.prototype.run = function () {
console.log("Generating scaffold: " + this.config.name + "...");
var templates = this.fileList(this.config.templates);
var fileConf, count = 0;
for (var _i = 0, templates_1 = templates; _i < templates_1.length; _i++) {
fileConf = templates_1[_i];
count++;
var file = fileConf.file, base = fileConf.base;
var outputPath = this.getOutputPath(file, base);
var contents = this.getFileContents(file);
var outputContents = this.parseLocals(contents);
this.writeFile(outputPath, outputContents);
console.info('Parsing:', { file: file, base: base, outputPath: outputPath, outputContents: outputContents.replace("\n", "\\n") });
}
if (!count) {
throw new Error('No files to scaffold!');
}
console.log('Done');
};
return SimpleScaffold;
}());
exports.default = SimpleScaffold;
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = require("fs");
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = require("glob");
/***/ }),
/* 4 */
/***/ (function(module, exports) {
module.exports = require("handlebars");
/***/ }),
/* 5 */,
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var scaffold_1 = __webpack_require__(1);
var cliArgs = __webpack_require__(7);
var cliUsage = __webpack_require__(8);
var path = __webpack_require__(0);
function localsParser(content) {
var _a;
var _b = content.split('='), key = _b[0], value = _b[1];
return _a = {}, _a[key] = value, _a;
}
function filePathParser(content) {
if (content.startsWith('/')) {
return content;
}
return [process.cwd(), content].join(path.sep);
}
var defs = [
{ name: 'name', alias: 'n', type: String, description: 'Component output name', defaultOption: true },
{ name: 'templates', alias: 't', type: filePathParser, multiple: true },
{ name: 'output', alias: 'o', type: filePathParser, multiple: true },
{ name: 'locals', alias: 'l', multiple: true, type: localsParser },
{ name: 'create-sub-folder', alias: 'S', type: function (text) { return text && text.trim().length ? ['true', '1', 'on'].includes(text.trim()) : true; } },
{ name: 'help', alias: 'h', type: Boolean, description: 'Display this help message' },
];
var args = cliArgs(defs, { camelCase: true });
var help = [
{ header: 'Scaffold Generator', content: 'Generate scaffolds for your project based on file templates.' },
{ header: 'Options', optionList: defs }
];
args.locals = (args.locals || []).reduce(function (all, cur) { return (__assign({}, all, cur)); }, {});
if (args.createSubFolder === null) {
args.createSubFolder = true;
}
console.info('Config:', args);
if (args.help || !args.name) {
console.log(cliUsage(help));
process.exit(0);
}
new scaffold_1.default({
name: args.name,
templates: args.templates,
output: args.output,
locals: args.locals,
createSubfolder: args.createSubFolder,
}).run();
/***/ }),
/* 7 */
/***/ (function(module, exports) {
module.exports = require("command-line-args");
/***/ }),
/* 8 */
/***/ (function(module, exports) {
module.exports = require("command-line-usage");
/***/ })
/******/ ]);
});
//# sourceMappingURL=cmd.js.map

1
dist/cmd.js.map vendored

File diff suppressed because one or more lines are too long

1
dist/dist/cmd.d.ts vendored
View File

@@ -1 +0,0 @@
export {};

View File

@@ -1,13 +0,0 @@
import IScaffold from './index';
declare class SimpleScaffold {
config: IScaffold.Config;
locals: IScaffold.Config['locals'];
constructor(config: IScaffold.Config);
private parseLocals;
private fileList;
private getFileContents;
private getOutputPath;
private writeFile;
run(): void;
}
export default SimpleScaffold;

1
dist/dist/test.d.ts vendored
View File

@@ -1 +0,0 @@
export {};

96
dist/main.js vendored
View File

@@ -1,96 +0,0 @@
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["library"] = factory();
else
root["library"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 7);
/******/ })
/************************************************************************/
/******/ ({
/***/ 7:
/***/ (function(module, exports, __webpack_require__) {
(function webpackMissingModule() { throw new Error("Cannot find module \"add\""); }());
module.exports = __webpack_require__(8);
/***/ }),
/***/ 8:
/***/ (function(module, exports) {
module.exports = require("jest");
/***/ })
/******/ });
});
//# sourceMappingURL=main.js.map

1
dist/main.js.map vendored
View File

@@ -1 +0,0 @@
{"version":3,"sources":["webpack/universalModuleDefinition","webpack/bootstrap c994c2c400fa1fc61abe","external \"jest\""],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;ACVA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;;;;;;;;;;AC7DA,iC","file":"main.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"library\"] = factory();\n\telse\n\t\troot[\"library\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 7);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap c994c2c400fa1fc61abe","module.exports = require(\"jest\");\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"jest\"\n// module id = 8\n// module chunks = 3"],"sourceRoot":""}

217
dist/scaffold.js vendored
View File

@@ -1,217 +0,0 @@
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["library"] = factory();
else
root["library"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 1);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = require("path");
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = __webpack_require__(2);
var path = __webpack_require__(0);
var glob = __webpack_require__(3);
var handlebars = __webpack_require__(4);
var SimpleScaffold = /** @class */ (function () {
function SimpleScaffold(config) {
this.locals = {};
var DefaultConfig = {
name: 'scaffold',
templates: [],
output: process.cwd(),
createSubfolder: true,
};
this.config = __assign({}, DefaultConfig, config);
var DefaultLocals = {
Name: this.config.name[0].toUpperCase() + this.config.name.slice(1),
name: this.config.name[0].toLowerCase() + this.config.name.slice(1)
};
this.locals = __assign({}, DefaultLocals, config.locals);
}
SimpleScaffold.prototype.parseLocals = function (text) {
var template = handlebars.compile(text, {
noEscape: true
});
return template(this.locals);
};
SimpleScaffold.prototype.fileList = function (input) {
var output = [];
for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
var checkPath = input_1[_i];
var files = glob.sync(checkPath, { dot: true })
.map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
var idx = checkPath.indexOf('*');
var cleanCheckPath = checkPath;
if (idx >= 0) {
cleanCheckPath = checkPath.slice(0, idx - 1);
}
for (var _a = 0, files_1 = files; _a < files_1.length; _a++) {
var file = files_1[_a];
output.push({ base: cleanCheckPath, file: file });
}
}
return output;
};
SimpleScaffold.prototype.getFileContents = function (filePath) {
console.log(fs.readFileSync(filePath));
return fs.readFileSync(filePath).toString();
};
SimpleScaffold.prototype.getOutputPath = function (file, basePath) {
var out;
if (typeof this.config.output === 'function') {
out = this.config.output(file, basePath);
}
else {
var outputDir = this.config.output + (this.config.createSubfolder ? "/" + this.config.name + "/" : '/');
var idx = file.indexOf(basePath);
var relativeFilePath = file;
if (idx >= 0) {
relativeFilePath = file.slice(idx + basePath.length + 1);
}
out = outputDir + relativeFilePath;
}
return this.parseLocals(out);
};
SimpleScaffold.prototype.writeFile = function (filePath, fileContents) {
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath));
}
console.info('Writing file:', filePath);
fs.writeFile(filePath, fileContents, { encoding: 'utf-8' }, function (err) {
if (err) {
throw err;
}
});
};
SimpleScaffold.prototype.run = function () {
console.log("Generating scaffold: " + this.config.name + "...");
var templates = this.fileList(this.config.templates);
var fileConf, count = 0;
for (var _i = 0, templates_1 = templates; _i < templates_1.length; _i++) {
fileConf = templates_1[_i];
count++;
var file = fileConf.file, base = fileConf.base;
var outputPath = this.getOutputPath(file, base);
var contents = this.getFileContents(file);
var outputContents = this.parseLocals(contents);
this.writeFile(outputPath, outputContents);
console.info('Parsing:', { file: file, base: base, outputPath: outputPath, outputContents: outputContents.replace("\n", "\\n") });
}
if (!count) {
throw new Error('No files to scaffold!');
}
console.log('Done');
};
return SimpleScaffold;
}());
exports.default = SimpleScaffold;
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = require("fs");
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = require("glob");
/***/ }),
/* 4 */
/***/ (function(module, exports) {
module.exports = require("handlebars");
/***/ })
/******/ ]);
});
//# sourceMappingURL=scaffold.js.map

File diff suppressed because one or more lines are too long

246
dist/test.js vendored
View File

@@ -1,246 +0,0 @@
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["library"] = factory();
else
root["library"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 5);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = require("path");
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = __webpack_require__(2);
var path = __webpack_require__(0);
var glob = __webpack_require__(3);
var handlebars = __webpack_require__(4);
var SimpleScaffold = /** @class */ (function () {
function SimpleScaffold(config) {
this.locals = {};
var DefaultConfig = {
name: 'scaffold',
templates: [],
output: process.cwd(),
createSubfolder: true,
};
this.config = __assign({}, DefaultConfig, config);
var DefaultLocals = {
Name: this.config.name[0].toUpperCase() + this.config.name.slice(1),
name: this.config.name[0].toLowerCase() + this.config.name.slice(1)
};
this.locals = __assign({}, DefaultLocals, config.locals);
}
SimpleScaffold.prototype.parseLocals = function (text) {
var template = handlebars.compile(text, {
noEscape: true
});
return template(this.locals);
};
SimpleScaffold.prototype.fileList = function (input) {
var output = [];
for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
var checkPath = input_1[_i];
var files = glob.sync(checkPath, { dot: true })
.map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
var idx = checkPath.indexOf('*');
var cleanCheckPath = checkPath;
if (idx >= 0) {
cleanCheckPath = checkPath.slice(0, idx - 1);
}
for (var _a = 0, files_1 = files; _a < files_1.length; _a++) {
var file = files_1[_a];
output.push({ base: cleanCheckPath, file: file });
}
}
return output;
};
SimpleScaffold.prototype.getFileContents = function (filePath) {
console.log(fs.readFileSync(filePath));
return fs.readFileSync(filePath).toString();
};
SimpleScaffold.prototype.getOutputPath = function (file, basePath) {
var out;
if (typeof this.config.output === 'function') {
out = this.config.output(file, basePath);
}
else {
var outputDir = this.config.output + (this.config.createSubfolder ? "/" + this.config.name + "/" : '/');
var idx = file.indexOf(basePath);
var relativeFilePath = file;
if (idx >= 0) {
relativeFilePath = file.slice(idx + basePath.length + 1);
}
out = outputDir + relativeFilePath;
}
return this.parseLocals(out);
};
SimpleScaffold.prototype.writeFile = function (filePath, fileContents) {
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath));
}
console.info('Writing file:', filePath);
fs.writeFile(filePath, fileContents, { encoding: 'utf-8' }, function (err) {
if (err) {
throw err;
}
});
};
SimpleScaffold.prototype.run = function () {
console.log("Generating scaffold: " + this.config.name + "...");
var templates = this.fileList(this.config.templates);
var fileConf, count = 0;
for (var _i = 0, templates_1 = templates; _i < templates_1.length; _i++) {
fileConf = templates_1[_i];
count++;
var file = fileConf.file, base = fileConf.base;
var outputPath = this.getOutputPath(file, base);
var contents = this.getFileContents(file);
var outputContents = this.parseLocals(contents);
this.writeFile(outputPath, outputContents);
console.info('Parsing:', { file: file, base: base, outputPath: outputPath, outputContents: outputContents.replace("\n", "\\n") });
}
if (!count) {
throw new Error('No files to scaffold!');
}
console.log('Done');
};
return SimpleScaffold;
}());
exports.default = SimpleScaffold;
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = require("fs");
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = require("glob");
/***/ }),
/* 4 */
/***/ (function(module, exports) {
module.exports = require("handlebars");
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var scaffold_1 = __webpack_require__(1);
var path = __webpack_require__(0);
var templateDir = path.join(process.cwd(), 'examples');
new scaffold_1.default({
templates: [templateDir + '/test-input/Component/**/*'],
output: templateDir + '/test-output/no-create-subpath',
createSubfolder: false,
locals: {
property: 'myProp',
value: '"value"'
}
}).run();
new scaffold_1.default({
templates: [templateDir + '/test-input/Component/**/*'],
output: templateDir + '/test-output',
locals: {
property: 'myProp',
value: '"value"'
}
}).run();
/***/ })
/******/ ]);
});
//# sourceMappingURL=test.js.map

1
dist/test.js.map vendored

File diff suppressed because one or more lines are too long

22
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
docs/api

44
docs/README.md Normal file
View File

@@ -0,0 +1,44 @@
# Website
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
### Installation
```
$ yarn
```
### Local Development
```
$ yarn start
```
This command starts a local development server and opens up a browser window. Most changes are
reflected live without having to restart the server.
### Build
```
$ yarn build
```
This command generates static content into the `build` directory and can be served using any static
contents hosting service.
### Deployment
Using SSH:
```
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and
push to the `gh-pages` branch.

3
docs/babel.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
}

View File

@@ -0,0 +1,134 @@
---
title: Templates
---
# Templates
Templates are regular files in a directory. Both **file names** and **file contents** support
[Handlebars.js](https://handlebarsjs.com/) syntax for token replacement.
## How Templates Are Resolved
Each path in the `templates` array is resolved individually. If a path points to a directory or glob
pattern containing multiple files, the first directory up the tree becomes the base path — files are
then copied recursively into `output`, preserving their relative structure.
> In the examples below, `name` is `AppName` and `output` is `src`.
| Template path | Files found | Output |
| ----------------------------- | ------------------------------------------------------ | ------------------------------------------------------------ |
| `./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` |
### Glob Patterns & Exclusions
Template paths support glob patterns and negation with `!`:
```js
{
templates: ["templates/component/**", "!templates/component/README.md"]
}
```
## Ignoring Files
Place a `.scaffoldignore` file in your template directory to exclude files. It works like
`.gitignore` — one pattern per line, `#` for comments.
```text
# .scaffoldignore
*.log
node_modules
README.md
dist/**
```
The `.scaffoldignore` file itself is never copied to the output.
Patterns are matched against both the file's basename and its path relative to the template
directory, so `README.md` matches at any depth while `dist/**` only matches a `dist` directory at
the template root.
## Token Replacement
Handlebars expressions like `{{ name }}` are replaced in both file names and file contents. The
`name` variable is always available — it's the name you pass when running the scaffold.
Any additional data from `--data`, `-D`, `data` config, or [inputs](configuration_files#inputs) is
also available.
```bash
npx simple-scaffold \
-t templates/component/{{name}}.jsx \
-o src/components \
MyComponent
```
This produces `src/components/MyComponent.jsx`, with all tokens inside the file replaced as well.
All standard Handlebars features work — `{{#if}}`, `{{#each}}`, `{{#with}}`, and more. See
[Handlebars.js Language Features](https://handlebarsjs.com/guide/#language-features) for details.
## Built-in Helpers
Simple Scaffold includes helpers you can use in templates and file names. Helpers can also be
nested: `{{ pascalCase (snakeCase name) }}`.
### Case Helpers
| Helper | Usage | `my name` becomes |
| ------------ | ----------------------- | ----------------- |
| _(none)_ | `{{ name }}` | `my name` |
| `camelCase` | `{{ camelCase name }}` | `myName` |
| `pascalCase` | `{{ pascalCase name }}` | `MyName` |
| `snakeCase` | `{{ snakeCase name }}` | `my_name` |
| `kebabCase` | `{{ kebabCase name }}` | `my-name` |
| `hyphenCase` | `{{ hyphenCase name }}` | `my-name` |
| `startCase` | `{{ startCase name }}` | `My Name` |
| `upperCase` | `{{ upperCase name }}` | `MY NAME` |
| `lowerCase` | `{{ lowerCase name }}` | `my name` |
### Date Helpers
Both `now` and `date` use [`date-fns`](https://date-fns.org/docs/format) format tokens.
| Helper | Example | Output |
| -------------------- | ---------------------------------------------------------------- | ------------------- |
| `now` | `{{ now "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
| `now` (with offset) | `{{ now "yyyy-MM-dd HH:mm" -1 "hours" }}` | `2042-01-01 14:00` |
| `date` | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
| `date` (with offset) | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" -1 "days" }}` | `2041-12-31 15:00` |
| `date` (from data) | `{{ date myCustomDate "yyyy-MM-dd HH:mm" }}` | _(depends on data)_ |
**Signatures:**
```typescript
now(format: string, offsetAmount?: number, offsetType?: "years" | "months" | "weeks" | "days" | "hours" | "minutes" | "seconds")
date(date: string, format: string, offsetAmount?: number, offsetType?: "years" | "months" | "weeks" | "days" | "hours" | "minutes" | "seconds")
```
## Custom Helpers
You can register custom Handlebars helpers via the `helpers` config option:
```js
module.exports = {
component: {
templates: ["templates/component"],
output: "src/components",
helpers: {
shout: (text) => text.toUpperCase() + "!!!",
},
},
}
```
Then use in templates: `{{ shout name }}`.
All helpers (built-in and custom) are also available as values for `subdirHelper` (`--subdir-helper`
/ `-H`).
For more on Handlebars helpers, see the
[Handlebars.js docs](https://handlebarsjs.com/guide/#custom-helpers).

View File

@@ -0,0 +1,205 @@
---
title: Configuration Files
---
# Configuration Files
Config files let you define reusable scaffold definitions — template paths, output directories,
custom data, inputs, and hooks — all in one place.
## Creating a Config File
The fastest way is to run `init`:
```sh
npx simple-scaffold init
```
This creates a `scaffold.config.js` and an example template in `templates/default/`. See
[`init` command](cli#init) for options.
### Config Structure
A config file exports an object mapping **template keys** to scaffold configurations:
```js
// scaffold.config.js
module.exports = {
component: {
templates: ["templates/component"],
output: "src/components",
},
page: {
templates: ["templates/page"],
output: "src/pages",
subdir: true,
},
}
```
For the full list of options, see [ScaffoldConfig](../api/interfaces/ScaffoldConfig) or the
[Node.js API](node) page.
### Dynamic Configs
JS config files can export a **function** that receives the CLI config and returns the scaffold map.
This lets you pre-process arguments or add logic:
```js
/** @type {import('simple-scaffold').ScaffoldConfigFile} */
module.exports = (config) => ({
component: {
templates: ["templates/component"],
output: "src/components",
data: { timestamp: Date.now() },
},
})
```
The function can also be `async`.
### Supported Formats
- `.js` (CommonJS)
- `.cjs` (CommonJS, explicit)
- `.mjs` (ESM)
- `.json`
:::note The correct extension may depend on your `package.json` `"type"` field. Packages with
`"type": "module"` may require `.mjs` or `.cjs` instead of `.js`. :::
## Using a Config File
### Auto-detection
Simple Scaffold automatically searches the current directory for a config file — no `--config` flag
needed. The following names are tried in order:
1. `scaffold.config.{mjs,cjs,js,json}`
2. `scaffold.{mjs,cjs,js,json}`
3. `.scaffold.{mjs,cjs,js,json}`
```sh
# Just run from the project root — config is found automatically
npx simple-scaffold -k component MyComponent
```
### Explicit Path
Use `--config` (`-c`) to point to a specific file or directory:
```sh
npx simple-scaffold -c path/to/scaffold.config.js -k component MyComponent
```
When a directory is given, the auto-detection order above is used within that directory.
### Default Template Key
If you don't provide `--key`, the `default` key is used:
```js
module.exports = {
default: {
templates: ["templates/default"],
output: "src",
},
}
```
```sh
# Uses the "default" key — no -k needed
npx simple-scaffold MyProject
```
If multiple keys exist and no `--key` is provided, you'll be prompted to select one interactively.
### Providing a Default Name
If your template doesn't need a dynamic name (e.g. common config files), set `name` in the config:
```js
module.exports = {
eslint: {
name: ".eslintrc",
templates: ["templates/eslint"],
output: ".",
},
}
```
The name can still be overridden with `--name` on the command line.
## Inputs
Inputs define custom fields that are prompted interactively and become template data variables:
```js
module.exports = {
component: {
templates: ["templates/component"],
output: "src/components",
inputs: {
author: { message: "Author name", required: true },
license: {
type: "select",
message: "License",
options: ["MIT", "Apache-2.0", "GPL-3.0"],
},
private: { type: "confirm", message: "Private?", default: false },
port: { type: "number", message: "Port", default: 3000 },
},
},
}
```
Use them in templates as `{{ author }}`, `{{ license }}`, `{{ private }}`, `{{ port }}`.
**Input types:**
| Type | Description | Value type |
| --------- | ------------------------------ | ---------- |
| `text` | Free-form text input (default) | `string` |
| `select` | Choose from a list of options | `string` |
| `confirm` | Yes/no prompt | `boolean` |
| `number` | Numeric input | `number` |
**Behavior:**
- **Required** inputs are prompted if not provided via `--data` or `-D`
- **Select and confirm** inputs are always prompted unless pre-provided
- **Optional** inputs with a `default` use that value silently
- In non-interactive environments (CI, piped input), only defaults are applied
Pre-fill inputs from the CLI:
```sh
npx simple-scaffold -k component -D author=John -D license=MIT MyComponent
```
## Remote Templates (Git)
Load config files and templates from any Git repository:
```sh
# GitHub shorthand
npx simple-scaffold -g username/repo -k component MyComponent
# Full Git URL (GitLab, Bitbucket, etc.)
npx simple-scaffold -g https://gitlab.com/user/repo.git -k component MyComponent
```
When `--config` is omitted, the standard auto-detection order is used within the cloned repo. The
repository is cloned to a temporary directory and cleaned up automatically.
### From Node.js
```ts
import Scaffold from "simple-scaffold"
const scaffold = await Scaffold.fromConfig(
"https://github.com/user/repo.git", // or a local file path
{ name: "MyComponent", key: "component" },
)
await scaffold.run()
```

162
docs/docs/usage/03-cli.md Normal file
View File

@@ -0,0 +1,162 @@
---
title: CLI
---
# CLI
```sh
npx simple-scaffold [options] [name]
```
Use `--help` (`-h`) to see all available options at any time.
## Commands
| Command | Description |
| ------------- | ----------------------------------------- |
| _(default)_ | Generate files from a template |
| `init` | Create a config file and example template |
| `list` / `ls` | List available template keys in a config |
### `init`
Scaffolds a config file and example template directory to get you started:
```sh
npx simple-scaffold init
npx simple-scaffold init --format mjs
npx simple-scaffold init --dir packages/my-lib
```
| Option | Description |
| ----------------- | ----------------------------------------------------------------- |
| `--dir` / `-d` | Directory to create the config in (defaults to current directory) |
| `--format` / `-f` | Config format: `js`, `mjs`, or `json` (prompts if omitted) |
Creates:
- A config file (`scaffold.config.js` by default) with a `default` template key
- A `templates/default/` directory with an example `{{name}}.md` template
Existing files are never overwritten.
### `list`
Lists all template keys defined in a config file:
```sh
npx simple-scaffold list
npx simple-scaffold list -c path/to/config.js
npx simple-scaffold list -g username/repo
```
## Options
| Flag | Short | Description | Default |
| -------------------------------- | --------- | ----------------------------------------------------- | --------------- |
| `--name` | `-n` | Name for generated files (can also be positional arg) | |
| `--config` | `-c` | Path to config file or directory | _(auto-detect)_ |
| `--git` | `-g` | Git URL or GitHub shorthand (`user/repo`) | |
| `--key` | `-k` | Template key from config | `default` |
| `--output` | `-o` | Output directory | |
| `--templates` | `-t` | Template paths or glob patterns (repeatable) | |
| `--data` | `-d` | Custom data as JSON string | |
| `--append-data` | `-D` | Key-value data (`key=string`, `key:=raw`), repeatable | |
| `--subdir` / `--no-subdir` | `-s`/`-S` | Create parent directory with the input name | `false` |
| `--subdir-helper` | `-H` | Helper to transform subdir name | |
| `--overwrite` / `--no-overwrite` | `-w`/`-W` | Overwrite existing files | `false` |
| `--dry-run` | `-dr` | Preview output without writing files | `false` |
| `--before-write` | `-B` | Command to run before each file is written | |
| `--after-scaffold` | `-A` | Shell command to run after all files are written | |
| `--quiet` | `-q` | Suppress output (same as `--log-level none`) | |
| `--log-level` | `-l` | Log level: `none`, `debug`, `info`, `warn`, `error` | `info` |
| `--version` | `-v` | Show version | |
| `--help` | `-h` | Show help | |
## Interactive Mode
When running in a terminal (TTY), Simple Scaffold prompts for any missing required values:
- **Name** — if `--name` is not provided
- **Template key** — if `--key` is not provided and the config has multiple templates
- **Output directory** — if `--output` is not provided
- **Template paths** — if `--templates` is not provided (comma-separated)
[Inputs](configuration_files#inputs) defined in config files are also prompted interactively.
In non-interactive environments (CI, piped input), missing required values cause an error.
## Hooks
### Before Write
Runs a command before each file is written. The command receives a temporary file path and should
output the final content to stdout.
```sh
# Appends file path automatically
npx simple-scaffold -c . --before-write prettier
# Use tokens for explicit control
npx simple-scaffold -c . --before-write 'cat {{path}} | my-linter'
```
**Tokens:**
- `{{path}}` — temporary file path with Handlebars-processed contents
- `{{rawpath}}` — temporary file path with raw (unprocessed) contents
If no tokens are found, `{{path}}` is appended automatically. Returning an empty string (after
trimming) discards the result and writes the original contents.
### After Scaffold
Runs a shell command after all files are written. The command executes in the output directory:
```sh
npx simple-scaffold -c . --after-scaffold 'npm install'
npx simple-scaffold -c . --after-scaffold 'git init && git add .'
```
See the [Node.js API](node#after-scaffold-hook) for the function-based equivalent.
## CLI Examples
```sh
# Use auto-detected config, default key
npx simple-scaffold MyProject
# Specify config and key
npx simple-scaffold -c scaffold.config.js -k component MyComponent
# GitHub remote template
npx simple-scaffold -g username/repo -k component MyComponent
# Full Git URL
npx simple-scaffold -g https://gitlab.com/user/repo.git -k component MyComponent
# One-off (no config file)
npx simple-scaffold -t templates/component -o src/components MyComponent
# With custom data
npx simple-scaffold -k component -D author=John -D license:='"MIT"' MyComponent
# Dry run
npx simple-scaffold -k component --dry-run MyComponent
```
### package.json Scripts
```json
{
"scripts": {
"scaffold": "simple-scaffold -k component",
"scaffold:page": "simple-scaffold -k page"
}
}
```
```sh
npm run scaffold -- MyComponent
npm run scaffold:page -- Dashboard
```

208
docs/docs/usage/04-node.md Normal file
View File

@@ -0,0 +1,208 @@
---
title: Node.js API
---
# Node.js API
Use Simple Scaffold programmatically for more complex workflows, custom logic, or integration into
build tools.
## Basic Usage
```typescript
import Scaffold from "simple-scaffold"
const scaffold = new Scaffold({
name: "MyComponent",
templates: ["templates/component"],
output: "src/components",
})
await scaffold.run()
```
## Loading from a Config File
```typescript
import Scaffold from "simple-scaffold"
const scaffold = await Scaffold.fromConfig(
"scaffold.config.js", // local path or HTTPS Git URL
{ name: "MyComponent", key: "component" },
{
/* optional config overrides */
},
)
await scaffold.run()
```
## Config Interface
```typescript
interface ScaffoldConfig {
name: string
templates: string[]
output: FileResponse<string>
subdir?: boolean
subdirHelper?: string
data?: Record<string, unknown>
overwrite?: FileResponse<boolean>
logLevel?: "none" | "debug" | "info" | "warn" | "error"
dryRun?: boolean
helpers?: Record<string, Helper>
inputs?: Record<string, ScaffoldInput>
beforeWrite?(
content: Buffer,
rawContent: Buffer,
outputPath: string,
): string | Buffer | undefined | Promise<string | Buffer | undefined>
afterScaffold?: AfterScaffoldHook
}
```
For the full API reference, see [ScaffoldConfig](../api/interfaces/ScaffoldConfig).
### Options
| Option | Type | Description |
| --------------- | ------------------------------- | ------------------------------------------------------------------------ |
| `name` | `string` | Name for generated files _(required)_ |
| `templates` | `string[]` | Template paths or globs; prefix with `!` to exclude _(required)_ |
| `output` | `string \| Function` | Output directory, or a function for per-file control |
| `data` | `Record<string, unknown>` | Custom data available in templates |
| `inputs` | `Record<string, ScaffoldInput>` | Interactive input definitions (see [Inputs](configuration_files#inputs)) |
| `helpers` | `Record<string, Function>` | Custom Handlebars helpers |
| `subdir` | `boolean` | Create a parent directory with the input name (default: `false`) |
| `subdirHelper` | `string` | Helper to transform the subdir name (e.g. `"pascalCase"`) |
| `overwrite` | `boolean \| Function` | Overwrite existing files (default: `false`); function for per-file logic |
| `dryRun` | `boolean` | Preview without writing files (default: `false`) |
| `logLevel` | `string` | Log verbosity (default: `"info"`) |
| `beforeWrite` | `Function` | Async hook before each file is written |
| `afterScaffold` | `Function \| string` | Hook after all files are written |
## Hooks
### Before Write
Runs before each file is written. Return a `string` or `Buffer` to replace the file contents, or
`undefined` to keep the default (Handlebars-processed) output.
```typescript
await new Scaffold({
name: "MyComponent",
templates: ["templates/component"],
output: "src/components",
beforeWrite: async (content, rawContent, outputPath) => {
// Format the output, transform it, or return undefined to keep as-is
return content.toString().trim()
},
}).run()
```
### After Scaffold Hook
Runs after all files have been written. Pass a **function** for full control, or a **string** to run
a shell command in the output directory.
**Function:**
```typescript
await new Scaffold({
name: "my-app",
templates: ["templates/app"],
output: ".",
afterScaffold: async ({ config, files }) => {
console.log(`Created ${files.length} files`)
// e.g. run npm install, git init, open editor, etc.
},
}).run()
```
**Shell command:**
```typescript
await new Scaffold({
name: "my-app",
templates: ["templates/app"],
output: "my-app",
afterScaffold: "npm install && git init",
}).run()
```
The context object:
```typescript
interface AfterScaffoldContext {
config: ScaffoldConfig
files: string[] // absolute paths of written files
}
```
In dry-run mode, the hook is still called but the `files` array will be empty.
## Inputs
Define interactive prompts that merge into template data:
```typescript
await new Scaffold({
name: "component",
templates: ["templates/component"],
output: "src/components",
inputs: {
author: { message: "Author name", required: true },
license: {
type: "select",
message: "License",
options: ["MIT", "Apache-2.0", "GPL-3.0"],
},
private: { type: "confirm", message: "Private package?", default: false },
port: { type: "number", message: "Dev server port", default: 3000 },
},
}).run()
// In templates: {{ author }}, {{ license }}, {{ private }}, {{ port }}
```
```typescript
interface ScaffoldInput {
type?: "text" | "select" | "confirm" | "number"
message?: string
required?: boolean
default?: string | boolean | number
options?: (string | { name: string; value: string })[] // for type: "select"
}
```
- **Required** inputs are prompted if not already in `data`
- **Optional** inputs with a `default` are applied silently
- Pre-providing values in `data` skips the prompt for that input
## Full Example
```typescript
import path from "path"
import Scaffold from "simple-scaffold"
await new Scaffold({
name: "MyComponent",
templates: [path.join(__dirname, "scaffolds", "component")],
output: path.join(__dirname, "src", "components"),
subdir: true,
subdirHelper: "pascalCase",
data: {
property: "value",
},
helpers: {
twice: (text) => [text, text].join(" "),
},
inputs: {
author: { message: "Author name", required: true },
license: { message: "License", default: "MIT" },
},
beforeWrite: (content, rawContent, outputPath) => {
return content.toString().toUpperCase()
},
afterScaffold: async ({ config, files }) => {
console.log(`Created ${files.length} files in ${config.output}`)
},
}).run()
```

View File

@@ -0,0 +1,186 @@
---
title: Examples
---
# Examples
## React Component
### Template
**File:** `templates/component/{{pascalCase name}}.tsx`
```tsx
/**
* Author: {{ author }}
* Date: {{ now "yyyy-MM-dd" }}
*/
import React from "react"
export default {{ pascalCase name }}: React.FC = (props) => {
return (
<div className="{{ camelCase name }}">{{ pascalCase name }} Component</div>
)
}
```
### Config
```js
// scaffold.config.js
module.exports = {
component: {
templates: ["templates/component"],
output: "src/components",
data: {
author: "My Name",
},
},
}
```
### Running
```sh
npx simple-scaffold -k component MyComponent
```
### Output
**File:** `src/components/MyComponent.tsx`
```tsx
/**
* Author: My Name
* Date: 2077-01-01
*/
import React from "react"
export default MyComponent: React.FC = (props) => {
return (
<div className="myComponent">MyComponent Component</div>
)
}
```
## Subdir Variations
Given the template and config above, the output path changes based on `subdir` settings:
| Setting | Output path |
| ------------------------------------------- | -------------------------------------------- |
| `subdir: false` (default) | `src/components/MyComponent.tsx` |
| `subdir: true` | `src/components/MyComponent/MyComponent.tsx` |
| `subdir: true`, `subdirHelper: "upperCase"` | `src/components/MYCOMPONENT/MyComponent.tsx` |
## CLI One-liner (No Config)
```sh
npx simple-scaffold \
-t templates/component/**/* \
-o src/components \
-d '{"author": "My Name"}' \
MyComponent
```
## Node.js Equivalent
```typescript
import Scaffold from "simple-scaffold"
await new Scaffold({
name: "MyComponent",
templates: ["templates/component"],
output: "src/components",
data: {
author: "My Name",
},
}).run()
```
## Reusable Config Files
### CommonJS (`scaffold.config.js`)
```js
module.exports = {
default: {
templates: ["templates/component"],
output: "src/components",
},
}
```
### ESM (`scaffold.config.mjs`)
```js
export default {
default: {
templates: ["templates/component"],
output: "src/components",
},
}
```
### Dynamic Config (with function)
```js
module.exports = (config) => ({
default: {
templates: ["templates/component"],
output: "src/components",
data: {
generatedAt: new Date().toISOString(),
},
},
})
```
## With Inputs
```js
// scaffold.config.js
module.exports = {
package: {
templates: ["templates/package"],
output: "packages",
subdir: true,
inputs: {
description: { message: "Package description", required: true },
author: { message: "Author", default: "Team" },
license: {
type: "select",
message: "License",
options: ["MIT", "Apache-2.0", "ISC"],
},
private: { type: "confirm", message: "Private package?", default: true },
},
},
}
```
```sh
# Interactive — prompts for each input
npx simple-scaffold -k package my-lib
# Non-interactive — provide all values upfront
npx simple-scaffold -k package -D description="A utility library" -D author=John my-lib
```
## With Hooks
```js
module.exports = {
app: {
templates: ["templates/app"],
output: ".",
subdir: true,
afterScaffold: "cd {{name}} && npm install && git init",
},
}
```
```sh
npx simple-scaffold -k app my-app
# Files are generated, then npm install and git init run automatically
```

View File

@@ -0,0 +1,64 @@
---
title: Migration
---
# Migration
## v1.x to v2.x
### CLI Changes
**Remote config syntax:**
- The `:template_key` suffix syntax has been removed. Use `-k template_key` instead.
- `--github` (`-gh`) is now `--git` (`-g`) and supports any Git URL. GitHub shorthand still works:
`-g username/project`.
- The `#template_file` suffix syntax has been removed. Use `--config` (`-c`) to specify which file
to look for inside the Git project.
**Renamed flags:**
| v1.x | v2.x |
| ---------------------------------- | -------------------------------------------------------- |
| `--create-sub-folder` / `-s` | `--subdir` / `-s` |
| `--sub-folder-name-helper` / `-sh` | `--subdir-helper` / `-H` |
| `--verbose` (true/false) | `--log-level` (`debug`, `info`, `warn`, `error`, `none`) |
**Boolean flags** no longer take a value. Use `-q` instead of `-q true`, `-s` instead of `-s 1`,
etc.
### Behavior Changes
**`{{ Name }}` removed.** The auto-populated `Name` (PascalCase) variable is gone. Use
`{{ pascalCase name }}` in your templates instead. If you need the old behavior, inject it manually
via `data`:
```js
module.exports = {
default: {
templates: ["templates/default"],
output: "src",
data: { Name: "{{ pascalCase name }}" }, // or set it programmatically
},
}
```
## v0.x to v1.x
In v1.0, the codebase was overhauled but usage remained mostly the same.
### API Changes
| v0.x | v1.x |
| -------------------------------- | ------------------------------------------ |
| `new SimpleScaffold(opts).run()` | `SimpleScaffold(opts)` (returns a Promise) |
| `locals` option | `data` option |
| `--locals` / `-l` flag | `--data` / `-d` flag |
### Template Syntax
Templates still use Handlebars.js. v1.x added **built-in helpers** (case transformations, date
formatting), removing the need to pre-process template data for common operations like `camelCase`,
`snakeCase`, etc.
See [Templates](templates#built-in-helpers) for the full list of available helpers.

View File

@@ -0,0 +1 @@
label: "Usage"

18
docs/docs/usage/index.md Normal file
View File

@@ -0,0 +1,18 @@
---
title: Usage
sidebar_position: 0
---
# Usage
Simple Scaffold can be used as a **CLI tool** or as a **Node.js library**. Both approaches share the
same core concepts: templates, configuration files, and Handlebars-based token replacement.
- [Templates](./usage/templates) — how template files and directories work, built-in helpers, and
custom helpers
- [Configuration Files](./usage/configuration_files) — reusable scaffold definitions,
auto-detection, inputs, and remote Git templates
- [CLI](./usage/cli) — all commands, flags, interactive mode, and hooks
- [Node.js API](./usage/node) — programmatic usage, full config interface, and hooks
- [Examples](./usage/examples) — end-to-end examples for CLI and Node.js
- [Migration](./usage/migration) — upgrading from v1.x or v0.x to the latest version

191
docs/docusaurus.config.ts Normal file
View File

@@ -0,0 +1,191 @@
import { themes as prismThemes } from "prism-react-renderer"
import type { Config } from "@docusaurus/types"
import type * as Preset from "@docusaurus/preset-classic"
const config: Config = {
title: "Simple Scaffold",
tagline: "Generate any file structure - from single components to entire app boilerplates, with a single command.",
favicon: "img/favicon.svg",
// Set the production url of your site here
url: "https://chenasraf.github.io",
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: "/simple-scaffold",
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: "chenasraf", // Usually your GitHub org/user name.
projectName: "simple-scaffold", // Usually your repo name.
onBrokenLinks: "warn",
markdown: {
hooks: {
onBrokenMarkdownLinks: "warn",
},
},
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: "en",
locales: ["en"],
},
plugins: [
[
"docusaurus-plugin-typedoc",
// Plugin / TypeDoc options
{
entryPoints: ["../src/index.ts"],
tsconfig: "../tsconfig.json",
// typedoc options
watch: process.env.NODE_ENV === "development",
excludePrivate: true,
excludeProtected: true,
excludeInternal: true,
// includeVersion: true,
categorizeByGroup: false,
sort: ["visibility"],
categoryOrder: ["Main", "*"],
entryPointStrategy: "expand",
pageTitleTemplates: {
index: "{projectName}",
member: "`{rawName}`",
module: "{name}",
},
validation: {
invalidLink: true,
},
},
],
],
presets: [
[
"classic",
{
docs: {
sidebarPath: "./sidebars.ts",
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl: "https://github.com/chenasraf/simple-scaffold/blob/master/docs",
},
theme: {
customCss: "./src/css/custom.css",
},
googleTagManager: {
containerId: "GTM-KHQS9TQ",
},
} satisfies Preset.Options,
],
],
themeConfig: {
// Replace with your project's social card
image: "img/docusaurus-social-card.jpg",
navbar: {
title: "Simple Scaffold",
logo: {
alt: "Simple Scaffold",
src: "img/favicon.svg",
},
items: [
{
position: "left",
type: "docSidebar",
sidebarId: "api",
label: "API",
to: "docs/api",
},
{
position: "left",
type: "docSidebar",
sidebarId: "usage",
label: "Usage",
to: "docs/usage",
},
// {
// position: "left",
// type: "docSidebar",
// sidebarId: "docs",
// },
// {
// label: "API",
// href: "/docs/api",
// position: "left",
// },
// {
// label: "Usage",
// href: "/docs/usage",
// position: "left",
// },
{
href: "https://npmjs.com/package/simple-scaffold",
label: "NPM",
position: "right",
},
{
href: "https://github.com/chenasraf/simple-scaffold",
label: "GitHub",
position: "right",
},
],
},
footer: {
style: "dark",
links: [
{
title: "Docs",
items: [
{
label: "Usage",
to: "/docs/usage",
},
{
label: "API",
to: "/docs/api",
},
],
},
{
title: "More from @casraf",
items: [
{
label: "Massarg - CLI Argument Parser",
href: "https://chenasraf.github.io/massarg",
},
{
label: "Website",
href: "https://casraf.dev",
},
],
},
{
title: "More",
items: [
{
label: "npm",
href: "https://npmjs.com/package/simple-scaffold",
},
{
label: "GitHub",
href: "https://github.com/chenasraf/simple-scaffold",
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} Chen Asraf. Built with Docusaurus.`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
} satisfies Preset.ThemeConfig,
}
export default config

51
docs/package.json Normal file
View File

@@ -0,0 +1,51 @@
{
"name": "simple-scaffold-docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start --port 3001",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "^3.9.2",
"@docusaurus/plugin-google-tag-manager": "^3.9.2",
"@docusaurus/preset-classic": "^3.9.2",
"@mdx-js/react": "^3.1.1",
"clsx": "^2.1.1",
"prism-react-renderer": "^2.4.1",
"react": "^19.2.4",
"react-dom": "^19.2.4"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.9.2",
"@docusaurus/tsconfig": "^3.9.2",
"@docusaurus/types": "^3.9.2",
"docusaurus-plugin-typedoc": "^1.4.2",
"typedoc": "^0.28.18",
"typedoc-plugin-markdown": "^4.11.0",
"typescript": "~6.0.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=18.0"
}
}

11776
docs/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

28
docs/sidebars.ts Normal file
View File

@@ -0,0 +1,28 @@
import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"
const sidebars: SidebarsConfig = {
usage: [{ type: "autogenerated", dirName: "usage" }],
api: [
{ type: "doc", id: "api/index", label: "Overview" },
{
type: "category",
label: "Functions",
items: [{ type: "autogenerated", dirName: "api/functions" }],
},
{
type: "category",
label: "Types",
items: [
{ type: "autogenerated", dirName: "api/interfaces" },
{ type: "autogenerated", dirName: "api/type-aliases" },
],
},
{
type: "category",
label: "Variables",
items: [{ type: "autogenerated", dirName: "api/variables" }],
},
],
}
export default sidebars

View File

@@ -0,0 +1,72 @@
import clsx from "clsx"
import Heading from "@theme/Heading"
import styles from "./styles.module.css"
type FeatureItem = {
title: string
Svg: React.ComponentType<React.ComponentProps<"svg">>
description: JSX.Element
}
const FeatureList: FeatureItem[] = [
{
title: "Easy to Use",
Svg: require("@site/static/img/undraw_docusaurus_mountain.svg").default,
description: (
<>
Generate anything from a simple component to an entire app boilerplate - you decide! Put dynamic data in your
templates to quickly generate skeletons, formatted data dumps, or repetitive code - and immediately get to
coding!
</>
),
},
{
title: "Use It Anywhere, For Anything",
Svg: require("@site/static/img/undraw_docusaurus_tree.svg").default,
description: (
<>
Whether you need files specific to your project or commonly used templates - you can use them both locally or
use Git to share them with your team. Spackle on some one-time-use data, and run one command.
</>
),
},
{
title: "Handlebars Support",
Svg: require("@site/static/img/undraw_docusaurus_react.svg").default,
description: (
<>
Did you think you stop at some static data? Generate entire mapped lists of items, pre-parse information, fake
data, and more - you can attach any function or any data to your templates. Handlebars will parse it all and
generate the files you need.
</>
),
},
]
function Feature({ title, Svg, description }: FeatureItem) {
return (
<div className={clsx("col col--4")}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<Heading as="h3">{title}</Heading>
<p>{description}</p>
</div>
</div>
)
}
export default function HomepageFeatures(): JSX.Element {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

30
docs/src/css/custom.css Normal file
View File

@@ -0,0 +1,30 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme="dark"] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

View File

@@ -0,0 +1,34 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
}
.heroImage {
margin-bottom: 1.5rem;
}
.logo {
width: 100%;
max-width: 300px;
margin: 0 auto;
}

44
docs/src/pages/index.tsx Normal file
View File

@@ -0,0 +1,44 @@
import clsx from "clsx"
import Link from "@docusaurus/Link"
import useDocusaurusContext from "@docusaurus/useDocusaurusContext"
import Layout from "@theme/Layout"
import HomepageFeatures from "@site/src/components/HomepageFeatures"
import Heading from "@theme/Heading"
import styles from "./index.module.css"
function HomepageHeader() {
const { siteConfig } = useDocusaurusContext()
return (
<header className={clsx("hero hero--primary", styles.heroBanner)}>
<div className="container">
<img className={styles.logo} src="img/logo-lg.svg" alt="Simple Scaffold" />
<Heading as="h1" className="hero__title">
{siteConfig.title}
</Heading>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<img className={styles.heroImage} src="img/intro.gif" alt="Simple-Scaffold doing its thing" />
<div className={styles.buttons}>
<Link className="button button--secondary button--lg" to="/docs/api">
API
</Link>
<Link className="button button--secondary button--lg" to="/docs/usage">
Usage
</Link>
</div>
</div>
</header>
)
}
export default function Home(): JSX.Element {
const { siteConfig } = useDocusaurusContext()
return (
<Layout title={siteConfig.title} description="Description will go into a meta tag in <head />">
<HomepageHeader />
<main>
<HomepageFeatures />
</main>
</Layout>
)
}

View File

@@ -0,0 +1,7 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/static/img/docusaurus.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
docs/static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
docs/static/img/favicon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

20
docs/static/img/favicon.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 265 KiB

BIN
docs/static/img/intro.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

BIN
docs/static/img/logo-lg.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

20
docs/static/img/logo-lg.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 281 KiB

BIN
docs/static/img/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

20
docs/static/img/logo.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 432 KiB

View File

@@ -0,0 +1,171 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
<title>Easy to Use</title>
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -0,0 +1,170 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
<title>Powered by React</title>
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
</g>
<g id="React-icon" transform="translate(906.3 541.56)">
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663">
<title>Focus on What Matters</title>
<circle cx="321" cy="321" r="321" fill="#f2f2f2" />
<ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56" />
<ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2" />
<rect x="131" y="152.5" width="840" height="50" fill="#3f3d56" />
<path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2" />
<circle cx="181" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="217" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="253" cy="147.5" r="13" fill="#3f3d56" />
<rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060" />
<rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555" />
<rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f" />
<path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1" />
<path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd" />
<path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd" />
<path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

7
docs/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": ".",
},
}

18
eslint.config.mjs Normal file
View File

@@ -0,0 +1,18 @@
import eslint from '@eslint/js'
import tseslint from 'typescript-eslint'
export default [
...tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended),
{
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
},
},
{
ignores: ['node_modules/', 'build/', 'dist/', 'gen/'],
},
]

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1 @@
{{name}}

View File

@@ -1,19 +0,0 @@
import * as React from 'react'
import * as css from './{{Name}}.css'
class {{Name}} extends React.Component<any> {
private {{property}}
constructor(props: any) {
super(props)
this.{{property}} = {{value}}
}
public render() {
return (
<div className={ css.{{Name}} } />
)
}
}
export default {{Name}}

View File

@@ -0,0 +1,17 @@
import * as React from "react"
import * as css from "./{{pascalCase name}}.css"
class {{pascalCase name}} extends React.Component<any> {
private {{ property }}
constructor(props: any) {
super(props)
this.{{ property }} = {{ value }}
}
public render() {
return <div className={ css.{{pascalCase name}} } />
}
}
export default {{pascalCase name}}

21
index.d.ts vendored
View File

@@ -1,21 +0,0 @@
declare namespace IScaffold {
export interface Config {
name?: string
templates: string[]
output: string | ((path: string, base: string) => string)
locals?: Locals
createSubfolder?: boolean
}
export interface Locals {
[k: string]: string
}
export interface FileRepr {
base: string
file: string
}
}
export default IScaffold

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,45 +1,82 @@
{
"name": "simple-scaffold",
"version": "0.4.2",
"description": "Create files based on templates",
"repository": "https://github.com/chenasraf/simple-scaffold.git",
"author": "Chen Asraf <inbox@casraf.com>",
"version": "3.1.1",
"description": "Generate any file structure - from single components to entire app boilerplates, with a single command.",
"homepage": "https://chenasraf.github.io/simple-scaffold",
"repository": {
"type": "git",
"url": "https://github.com/chenasraf/simple-scaffold.git"
},
"author": "Chen Asraf <contact@casraf.dev> (https://casraf.dev)",
"license": "MIT",
"main": "dist/scaffold.js",
"bin": "dist/cmd.js",
"types": "index.d.ts",
"main": "index.js",
"bin": {
"simple-scaffold": "cmd.js"
},
"packageManager": "pnpm@9.9.0",
"keywords": [
"javascript",
"cli",
"template",
"files",
"typescript",
"generator",
"scaffold",
"file",
"scaffolding"
],
"scripts": {
"build": "NODE_ENV=${NODE_ENV:-production} webpack -p && chmod -R +x ./dist",
"dev": "webpack --watch",
"start": "node dist/scaffold.js",
"test": "jest",
"cmd": "dist/cmd.js",
"build-test": "yarn build && yarn test",
"build-cmd": "yarn build && yarn cmd"
"clean": "rimraf dist",
"build": "pnpm clean && vite build && tsc --emitDeclarationOnly && chmod -R +x ./dist && cp ./package.json ./README.md ./dist/",
"dev": "vite build --watch",
"start": "vite-node src/scaffold.ts",
"test": "vitest run",
"test:watch": "vitest",
"coverage": "vitest run --coverage && open coverage/lcov-report/index.html",
"cmd": "vite-node src/cmd.ts",
"docs:build": "cd docs && pnpm build",
"docs:watch": "cd docs && pnpm start",
"audit-fix": "pnpm audit --fix",
"ci": "pnpm install --frozen-lockfile",
"lint": "eslint src/ tests/",
"format": "prettier --write .",
"lint-staged": "lint-staged",
"prepare": "husky"
},
"lint-staged": {
"*.{ts,mts,js,mjs}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,yml,yaml,css}": [
"prettier --write"
]
},
"dependencies": {
"command-line-args": "^5.0.2",
"command-line-usage": "^5.0.5",
"glob": "^7.1.2",
"handlebars": "^4.0.11",
"ts-loader": "^3.1.1",
"typescript": "^2.6.1",
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.4",
"webpack-node-externals": "^1.6.0"
"@inquirer/confirm": "^6.0.10",
"@inquirer/input": "^5.0.10",
"@inquirer/number": "^4.0.10",
"@inquirer/select": "^5.1.2",
"date-fns": "^4.1.0",
"glob": "^13.0.6",
"handlebars": "^4.7.9",
"massarg": "2.1.1",
"minimatch": "^10.2.4",
"zod": "^4.3.6"
},
"devDependencies": {
"@types/command-line-args": "^5.0.0",
"@types/command-line-usage": "^5.0.1",
"@types/glob": "^5.0.34",
"@types/handlebars": "^4.0.36",
"@types/node": "^8.0.50",
"jest": "^22.0.4"
},
"jest": {
"testPathIgnorePatterns": [
"node_modules",
"dist"
]
"@eslint/js": "^10.0.1",
"@types/node": "^25.5.0",
"@vitest/coverage-v8": "^4.1.2",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"mock-fs": "^5.5.0",
"prettier": "^3.8.1",
"rimraf": "^6.1.3",
"typescript": "^6.0.2",
"typescript-eslint": "^8.57.2",
"vite": "^8.0.3",
"vite-node": "^6.0.0",
"vitest": "^4.1.2"
}
}

2238
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

22
scaffold.config.cjs Normal file
View File

@@ -0,0 +1,22 @@
// @ts-check
/** @type {import('./dist').ScaffoldConfigFile} */
module.exports = () => {
// 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" },
},
configs: {
templates: ["examples/test-input/**/.*"],
output: "examples/test-output/configs",
name: "---",
},
}
}

View File

@@ -1,112 +0,0 @@
import * as fs from 'fs'
import * as path from 'path'
import IScaffold from './index'
import * as glob from 'glob'
import * as handlebars from 'handlebars'
class SimpleScaffold {
public config: IScaffold.Config
public locals: IScaffold.Config['locals'] = {} as any
constructor(config: IScaffold.Config) {
const DefaultConfig: IScaffold.Config = {
name: 'scaffold',
templates: [],
output: process.cwd(),
createSubfolder: true,
}
this.config = { ...DefaultConfig, ...config }
const DefaultLocals = {
Name: this.config.name![0].toUpperCase() + this.config.name!.slice(1),
name: this.config.name![0].toLowerCase() + this.config.name!.slice(1)
}
this.locals = { ...DefaultLocals, ...config.locals }
}
private parseLocals(text: string): string {
const template = handlebars.compile(text, {
noEscape: true
})
return template(this.locals)
}
private fileList(input: string[]): IScaffold.FileRepr[] {
const output: IScaffold.FileRepr[] = []
for (const checkPath of input) {
const files = glob.sync(checkPath, { dot: true })
.map(g => g[0] == '/' ? g : path.join(process.cwd(), g))
const idx = checkPath.indexOf('*')
let cleanCheckPath = checkPath
if (idx >= 0) {
cleanCheckPath = checkPath.slice(0, idx - 1)
}
for (const file of files) {
output.push({ base: cleanCheckPath, file })
}
}
return output
}
private getFileContents(filePath: string): string {
console.log(fs.readFileSync(filePath))
return fs.readFileSync(filePath).toString()
}
private getOutputPath(file: string, basePath: string): string {
let out: string
if (typeof this.config.output === 'function') {
out = this.config.output(file, basePath)
} else {
const outputDir = this.config.output + (this.config.createSubfolder ? `/${this.config.name}/` : '/')
const idx = file.indexOf(basePath)
let relativeFilePath = file
if (idx >= 0) {
relativeFilePath = file.slice(idx + basePath.length + 1)
}
out = outputDir + relativeFilePath
}
return this.parseLocals(out)
}
private writeFile(filePath: string, fileContents: string): void {
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath))
}
console.info('Writing file:', filePath)
fs.writeFile(filePath, fileContents, { encoding: 'utf-8' }, (err) => {
if (err) {
throw err
}
})
}
public run(): void {
console.log(`Generating scaffold: ${this.config.name}...`)
const templates = this.fileList(this.config.templates)
let fileConf, count = 0
for (fileConf of templates) {
count++
const { file, base } = fileConf
const outputPath = this.getOutputPath(file, base)
const contents = this.getFileContents(file)
const outputContents = this.parseLocals(contents)
this.writeFile(outputPath, outputContents)
console.info('Parsing:', { file, base, outputPath, outputContents: outputContents.replace("\n", "\\n") })
}
if (!count) {
throw new Error('No files to scaffold!')
}
console.log('Done')
}
}
export default SimpleScaffold

86
src/before-write.ts Normal file
View File

@@ -0,0 +1,86 @@
import path from "node:path"
import fs from "node:fs/promises"
import { exec } from "node:child_process"
import { LogConfig, LogLevel, ScaffoldConfig } from "./types"
import { log } from "./logger"
import { createDirIfNotExists, getUniqueTmpPath } from "./fs-utils"
/**
* Wraps a CLI beforeWrite command string into a beforeWrite callback function.
* The command receives the processed content via a temp file and can return modified content via stdout.
* @internal
*/
export function wrapBeforeWrite(
config: LogConfig & Pick<ScaffoldConfig, "dryRun">,
beforeWrite: string,
): ScaffoldConfig["beforeWrite"] {
return async (content, rawContent, outputFile) => {
const tmpDir = path.join(getUniqueTmpPath(), path.basename(outputFile))
await createDirIfNotExists(path.dirname(tmpDir), config)
const ext = path.extname(outputFile)
const rawTmpPath = tmpDir.replace(ext, ".raw" + ext)
try {
log(config, LogLevel.debug, "Parsing beforeWrite command", beforeWrite)
const cmd = await prepareBeforeWriteCmd({
beforeWrite,
tmpDir,
content,
rawTmpPath,
rawContent,
})
const result = await new Promise<string | undefined>((resolve, reject) => {
log(config, LogLevel.debug, "Running parsed beforeWrite command:", cmd)
const proc = exec(cmd)
proc.stdout!.on("data", (data) => {
if (data.trim()) {
resolve(data.toString())
} else {
resolve(undefined)
}
})
proc.stderr!.on("data", (data) => {
reject(data.toString())
})
})
return result
} catch (e) {
log(config, LogLevel.debug, e)
log(config, LogLevel.warning, "Error running beforeWrite command, returning original content")
return undefined
} finally {
await fs.rm(tmpDir, { force: true })
await fs.rm(rawTmpPath, { force: true })
}
}
}
async function prepareBeforeWriteCmd({
beforeWrite,
tmpDir,
content,
rawTmpPath,
rawContent,
}: {
beforeWrite: string
tmpDir: string
content: Buffer
rawTmpPath: string
rawContent: Buffer
}): Promise<string> {
let cmd: string = ""
const pathReg = /\{\{\s*path\s*\}\}/gi
const rawPathReg = /\{\{\s*rawpath\s*\}\}/gi
if (pathReg.test(beforeWrite)) {
await fs.writeFile(tmpDir, content)
cmd = beforeWrite.replaceAll(pathReg, tmpDir)
}
if (rawPathReg.test(beforeWrite)) {
await fs.writeFile(rawTmpPath, rawContent)
cmd = beforeWrite.replaceAll(rawPathReg, rawTmpPath)
}
if (!cmd) {
await fs.writeFile(tmpDir, content)
cmd = [beforeWrite, tmpDir].join(" ")
}
return cmd
}

349
src/cmd.ts Normal file
View File

@@ -0,0 +1,349 @@
#!/usr/bin/env node
import path from "node:path"
import fs from "node:fs/promises"
import { massarg } from "massarg"
import { ListCommandCliOptions, LogLevel, ScaffoldCmdConfig, ScaffoldConfigMap } from "./types"
import { Scaffold } from "./scaffold"
import { findConfigFile, getConfigFile, parseAppendData, parseConfigFile } from "./config"
import { log } from "./logger"
import { MassargCommand } from "massarg/command"
import { getUniqueTmpPath as generateUniqueTmpPath } from "./file"
import { colorize } from "./colors"
import { promptBeforeConfig, promptAfterConfig, resolveInputs } from "./prompts"
import { initScaffold } from "./init"
export async function parseCliArgs(args = process.argv.slice(2)) {
const isProjectRoot = Boolean(
await fs.stat(path.join(__dirname, "package.json")).catch(() => false),
)
const pkgFile = await fs.readFile(
path.resolve(__dirname, isProjectRoot ? "." : "..", "package.json"),
)
const pkg = JSON.parse(pkgFile.toString())
return massarg<ScaffoldCmdConfig>({
name: pkg.name,
description: pkg.description,
})
.main(async (config) => {
if (config.version) {
console.log(pkg.version)
return
}
log(config, LogLevel.debug, `Simple Scaffold v${pkg.version}`)
config.tmpDir = generateUniqueTmpPath()
try {
// Auto-detect config file in cwd — but only if the user didn't
// explicitly provide templates/output (which signals a one-time run)
const isOneTimeRun = config.templates?.length > 0 || config.output
if (!config.config && !config.git && !isOneTimeRun) {
try {
config.config = await findConfigFile(process.cwd())
log(config, LogLevel.debug, `Auto-detected config file: ${config.config}`)
} catch {
// No config file found — that's fine, continue without one
}
}
// Load config map early so we can prompt for name and template key
const hasConfigSource = Boolean(config.config || config.git)
let configMap: ScaffoldConfigMap | undefined
if (hasConfigSource) {
configMap = await getConfigFile(config)
}
// Phase 1: prompt for name + key (needed before parseConfigFile)
config = await promptBeforeConfig(config, configMap)
// Parse and merge the config file
log(config, LogLevel.debug, "Parsing config file...", config)
const parsed = await parseConfigFile(config)
// Phase 2: prompt for anything still missing after config merge
const prompted = await promptAfterConfig(parsed)
const resolved = await resolveInputs(prompted)
await Scaffold(resolved)
} catch (e) {
const message = "message" in (e as object) ? (e as Error).message : e?.toString()
log(config, LogLevel.error, message)
} finally {
log(config, LogLevel.debug, "Cleaning up temporary files...", config.tmpDir)
if (config.tmpDir) await fs.rm(config.tmpDir, { recursive: true, force: true })
}
})
.option({
name: "name",
aliases: ["n"],
description:
"Name to be passed to the generated files. `{{name}}` and other data parameters inside " +
"contents and file names will be replaced accordingly. You may omit the `--name` or `-n` " +
"for this specific option. If omitted in an interactive terminal, you will be prompted.",
isDefault: true,
})
.option({
name: "config",
aliases: ["c"],
description: "Filename or directory to load config from",
})
.option({
name: "git",
aliases: ["g"],
description: "Git URL or GitHub path to load a template from.",
})
.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)`. If omitted and multiple templates are available, " +
"you will be prompted to select one.",
})
.option({
name: "output",
aliases: ["o"],
description:
"Path to output to. If `--subdir` is enabled, the subdir will be created inside " +
"this path. If omitted in an interactive terminal, you will be prompted.",
})
.option({
name: "templates",
aliases: ["t"],
array: true,
description:
"Template files to use as input. You may provide multiple files, each of which can be a relative or " +
"absolute path, " +
"or a glob pattern for multiple file matching easily. If omitted in an interactive terminal, " +
"you will be prompted for a comma-separated list.",
})
.flag({
name: "overwrite",
aliases: ["w"],
defaultValue: false,
description: "Enable to override output files, even if they already exist.",
negatable: true,
})
.option({
name: "data",
aliases: ["d"],
description: "Add custom data to the templates. By default, only your app name is included.",
parse: (v) => JSON.parse(v),
})
.option({
name: "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,
})
.flag({
name: "subdir",
aliases: ["s"],
defaultValue: false,
description: "Create a parent directory with the input name (and possibly `--subdir-helper`",
negatable: true,
negationName: "no-subdir",
})
.option({
name: "subdir-helper",
aliases: ["H"],
description: "Default helper to apply to subdir name when using `--subdir`.",
})
.flag({
name: "quiet",
aliases: ["q"],
defaultValue: false,
description: "Suppress output logs (Same as `--log-level none`)",
})
.option({
name: "log-level",
aliases: ["l"],
defaultValue: LogLevel.info,
description:
"Determine amount of logs to display. The values are: " +
`${colorize.bold`\`none | debug | info | warn | error\``}. ` +
"The provided level will display messages of the same level or higher.",
parse: (v) => {
const val = v.toLowerCase()
if (!(val in LogLevel)) {
throw new Error(
`Invalid log level: ${val}, must be one of: ${Object.keys(LogLevel).join(", ")}`,
)
}
return val
},
})
.option({
name: "before-write",
aliases: ["B"],
description:
"Run a script before writing the files. This can be a command or a path to a" +
" file. A temporary file path will be passed to the given command and the command should " +
"return a string for the final output.",
})
.option({
name: "after-scaffold",
aliases: ["A"],
description:
"Run a shell command after all files have been written. " +
"The command is executed in the output directory. " +
"For example: `--after-scaffold 'npm install'`",
})
.flag({
name: "dry-run",
aliases: ["dr"],
defaultValue: false,
description:
"Don't emit files. This is good for testing your scaffolds and making sure they " +
"don't fail, without having to write actual file contents or create directories.",
})
.flag({
name: "version",
aliases: ["v"],
description: "Display version.",
})
.command(
new MassargCommand<ListCommandCliOptions>({
name: "list",
aliases: ["ls"],
description:
"List all available templates for a given config. See `list -h` for more information.",
run: async (_config) => {
const config = {
templates: [],
name: "",
version: false,
output: "",
subdir: false,
overwrite: false,
dryRun: false,
tmpDir: generateUniqueTmpPath(),
..._config,
config: _config.config ?? (!_config.git ? process.cwd() : undefined),
}
try {
const file = await getConfigFile(config)
console.log(colorize.underline`Available templates:\n`)
console.log(Object.keys(file).join("\n"))
} catch (e) {
const message = "message" in (e as object) ? (e as Error).message : e?.toString()
log(config, LogLevel.error, message)
} finally {
log(config, LogLevel.debug, "Cleaning up temporary files...", config.tmpDir)
if (config.tmpDir) await fs.rm(config.tmpDir, { recursive: true, force: true })
}
},
})
.option({
name: "config",
aliases: ["c"],
description:
"Filename or directory to load config from. Defaults to current working directory.",
})
.option({
name: "git",
aliases: ["g"],
description: "Git URL or GitHub path to load a template from.",
})
.option({
name: "log-level",
aliases: ["l"],
defaultValue: LogLevel.none,
description:
"Determine amount of logs to display. The values are: " +
`${colorize.bold`\`none | debug | info | warn | error\``}. ` +
"The provided level will display messages of the same level or higher.",
parse: (v) => {
const val = v.toLowerCase()
if (!(val in LogLevel)) {
throw new Error(
`Invalid log level: ${val}, must be one of: ${Object.keys(LogLevel).join(", ")}`,
)
}
return val
},
})
.help({
bindOption: true,
}),
)
.command(
new MassargCommand<{ dir?: string; format?: string }>({
name: "init",
aliases: [],
description:
"Initialize a new scaffold config file and example template in the current directory.",
run: async (config) => {
try {
await initScaffold({
dir: config.dir,
format: config.format as "js" | "mjs" | "json" | undefined,
})
} catch (e) {
const message = "message" in (e as object) ? (e as Error).message : e?.toString()
console.error(colorize.red(message ?? "Unknown error"))
}
},
})
.option({
name: "dir",
aliases: ["d"],
description: "Directory to create the config in. Defaults to current working directory.",
})
.option({
name: "format",
aliases: ["f"],
description: "Config file format: js, mjs, or json. If omitted, you will be prompted.",
})
.help({
bindOption: true,
}),
)
.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 -g chenasraf/simple-scaffold --key component",
})
.example({
description: "Usage with https git URL (for non-GitHub)",
input:
"simple-scaffold -g https://example.com/user/template.git -c scaffold.cmd.js --key component",
})
.example({
description: "Excluded template key, assumes 'default' key",
input: "simple-scaffold -c scaffold.cmd.js MyComponent",
})
.example({
description:
"Shortest syntax for GitHub, searches for config file automaticlly, assumes and template key 'default'",
input: "simple-scaffold -g chenasraf/simple-scaffold MyComponent",
})
.help({
bindOption: true,
lineLength: 100,
useGlobalTableColumns: true,
usageText: [
colorize.yellow`simple-scaffold`,
colorize.gray`[options]`,
colorize.cyan`<name>`,
].join(" "),
optionOptions: {
displayNegations: true,
},
footerText: [
`Version: ${pkg.version}`,
`Copyright © Chen Asraf 2017-${new Date().getFullYear()}`,
``,
`Documentation: ${colorize.underline`https://chenasraf.github.io/simple-scaffold`}`,
`NPM: ${colorize.underline`https://npmjs.com/package/simple-scaffold`}`,
`GitHub: ${colorize.underline`https://github.com/chenasraf/simple-scaffold`}`,
].join("\n"),
})
.parse(args)
}
parseCliArgs()

74
src/colors.ts Normal file
View File

@@ -0,0 +1,74 @@
/** ANSI color code mapping for terminal output. */
const colorMap = {
reset: 0,
dim: 2,
bold: 1,
italic: 3,
underline: 4,
red: 31,
green: 32,
yellow: 33,
blue: 34,
magenta: 35,
cyan: 36,
white: 37,
gray: 90,
} as const
/** Available terminal color names. */
export type TermColor = keyof typeof colorMap
function _colorize(text: string, color: TermColor): string {
const c = colorMap[color]!
let r: number
if (c > 1 && c < 30) {
r = c + 20
} else if (c === 1) {
r = 23
} else {
r = 0
}
return `\x1b[${c}m${text}\x1b[${r}m`
}
function isTemplateStringArray(
template: TemplateStringsArray | unknown,
): template is TemplateStringsArray {
return Array.isArray(template) && typeof template[0] === "string"
}
const createColorize =
(color: TermColor) =>
(template: TemplateStringsArray | unknown, ...params: unknown[]): string => {
return isTemplateStringArray(template)
? _colorize(
(template as TemplateStringsArray).reduce(
(acc, str, i) => acc + str + (params[i] ?? ""),
"",
),
color,
)
: _colorize(String(template), color)
}
type TemplateStringsFn = ReturnType<typeof createColorize> & ((text: string) => string)
type TemplateStringsFns = { [key in TermColor]: TemplateStringsFn }
/**
* Colorize text for terminal output.
*
* Can be used as a function: `colorize("text", "red")`
* Or via named helpers: `colorize.red("text")` / `colorize.red\`template\``
*/
export const colorize: typeof _colorize & TemplateStringsFns = Object.assign(
_colorize,
Object.entries(colorMap).reduce(
(acc, [key]) => {
acc[key as TermColor] = createColorize(key as TermColor)
return acc
},
{} as Record<TermColor, TemplateStringsFn>,
),
)

206
src/config.ts Normal file
View File

@@ -0,0 +1,206 @@
import path from "node:path"
import {
ConfigLoadConfig,
LogConfig,
LogLevel,
RemoteConfigLoadConfig,
ScaffoldCmdConfig,
ScaffoldConfig,
ScaffoldConfigFile,
ScaffoldConfigMap,
} from "./types"
import { log } from "./logger"
import { resolve, wrapNoopResolver } from "./utils"
import { getGitConfig } from "./git"
import { isDir, pathExists } from "./fs-utils"
import { wrapBeforeWrite } from "./before-write"
// Re-export for backward compatibility (tests import from here)
export { getOptionValueForFile } from "./file"
/** Parses CLI append-data syntax (`key=value` or `key:=jsonValue`) into a data object. @internal */
export function parseAppendData(value: string, options: ScaffoldCmdConfig): unknown {
const data = options.data ?? {}
const [key, val] = value.split(/:?=/)
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("'"))
)
}
/** Loads and resolves a config file (local or remote). @internal */
export async function getConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfigMap> {
if (config.git && !config.git.includes("://")) {
log(config, LogLevel.debug, `Loading config from GitHub ${config.git}`)
config.git = githubPartToUrl(config.git)
}
const isGit = Boolean(config.git)
const configFilename = config.config
const configPath = isGit ? config.git : configFilename
log(config, LogLevel.debug, `Loading config from file ${configFilename}`)
const configPromise = await (isGit
? getRemoteConfig({
git: configPath,
config: configFilename,
logLevel: config.logLevel,
tmpDir: config.tmpDir!,
})
: getLocalConfig({ config: configFilename, logLevel: config.logLevel }))
let configImport = await resolve(configPromise, config)
if (typeof configImport.default === "function" || configImport.default instanceof Promise) {
log(config, LogLevel.debug, "Config is a function or promise, resolving...")
configImport = await resolve(configImport.default, config)
}
return configImport
}
/**
* Parses a CLI config into a full ScaffoldConfig by merging CLI args, config file values,
* and append-data overrides. @internal
*/
export async function parseConfigFile(config: ScaffoldCmdConfig): Promise<ScaffoldConfig> {
let output: ScaffoldConfig = {
name: config.name,
templates: config.templates ?? [],
output: config.output,
logLevel: config.logLevel,
dryRun: config.dryRun,
data: config.data,
subdir: config.subdir,
overwrite: config.overwrite,
subdirHelper: config.subdirHelper,
beforeWrite: undefined,
tmpDir: config.tmpDir!,
}
if (config.quiet) {
config.logLevel = LogLevel.none
}
const shouldLoadConfig = Boolean(config.config || config.git)
if (shouldLoadConfig) {
const key = config.key ?? "default"
const configImport = await getConfigFile(config)
if (!configImport[key]) {
throw new Error(`Template "${key}" not found in ${config.config}`)
}
const imported = configImport[key]
log(config, LogLevel.debug, "Imported result", imported)
output = {
...output,
...imported,
beforeWrite: undefined,
templates: config.templates || imported.templates,
output: config.output || imported.output,
data: {
...imported.data,
...config.data,
},
}
}
output.data = { ...output.data, ...config.appendData }
const cmdBeforeWrite = config.beforeWrite
? wrapBeforeWrite(config, config.beforeWrite)
: undefined
output.beforeWrite = cmdBeforeWrite ?? output.beforeWrite
if (config.afterScaffold) {
output.afterScaffold = config.afterScaffold
}
if (!output.name) {
throw new Error("simple-scaffold: Missing required option: name")
}
log(output, LogLevel.debug, "Parsed config", output)
return output
}
/** Converts a GitHub shorthand (user/repo) to a full HTTPS git URL. @internal */
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()
}
/** Loads a scaffold config from a local file or directory. @internal */
export async function getLocalConfig(
config: ConfigLoadConfig & Partial<LogConfig>,
): Promise<ScaffoldConfigFile> {
const { config: configFile, ...logConfig } = config as Required<typeof config>
const absolutePath = path.resolve(process.cwd(), configFile)
const _isDir = await isDir(absolutePath)
if (_isDir) {
log(logConfig, LogLevel.debug, `Resolving config file from directory ${absolutePath}`)
const file = await findConfigFile(absolutePath)
const exists = await pathExists(file)
if (!exists) {
throw new Error(`Could not find config file in directory ${absolutePath}`)
}
log(logConfig, LogLevel.debug, `Loading config from: ${path.resolve(absolutePath, file)}`)
return wrapNoopResolver(import(path.resolve(absolutePath, file)))
}
log(logConfig, LogLevel.debug, `Loading config from: ${absolutePath}`)
return wrapNoopResolver(import(absolutePath))
}
/** Loads a scaffold config from a remote git repository. @internal */
export async function getRemoteConfig(
config: RemoteConfigLoadConfig & Partial<LogConfig>,
): Promise<ScaffoldConfigFile> {
const { config: configFile, git, tmpDir, ...logConfig } = config as Required<typeof config>
log(
logConfig,
LogLevel.debug,
`Loading config from remote ${git}, config file ${configFile || "<auto-detect>"}`,
)
const url = new URL(git!)
const isHttp = url.protocol === "http:" || url.protocol === "https:"
const isGit = url.protocol === "git:" || (isHttp && url.pathname.endsWith(".git"))
if (!isGit) {
throw new Error(`Unsupported protocol ${url.protocol}`)
}
return getGitConfig(url, configFile, tmpDir, logConfig)
}
/** Searches for a scaffold config file in the given directory, trying known filenames in order. @internal */
export async function findConfigFile(root: string): Promise<string> {
const allowed = ["mjs", "cjs", "js", "json"].reduce((acc, ext) => {
acc.push(`scaffold.config.${ext}`)
acc.push(`scaffold.${ext}`)
acc.push(`.scaffold.${ext}`)
return acc
}, [] as string[])
for (const file of allowed) {
const exists = await pathExists(path.resolve(root, file))
if (exists) {
return file
}
}
throw new Error(`Could not find config file in git repo`)
}

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;
}

214
src/file.ts Normal file
View File

@@ -0,0 +1,214 @@
import path from "node:path"
import fs from "node:fs/promises"
import { FileResponse, FileResponseHandler, LogLevel, ScaffoldConfig } from "./types"
import { glob, hasMagic } from "glob"
import { log } from "./logger"
import { handlebarsParse } from "./parser"
import { handleErr } from "./utils"
import { createDirIfNotExists, pathExists, isDir } from "./fs-utils"
import { removeGlob } from "./path-utils"
const { readFile, writeFile } = fs
// Re-export extracted utilities for backward compatibility (tests import from here)
export { createDirIfNotExists, pathExists, isDir, getUniqueTmpPath } from "./fs-utils"
export { removeGlob, makeRelativePath, getBasePath } from "./path-utils"
/**
* Resolves a config option that may be either a static value or a per-file function.
* For function values, the file path is parsed through Handlebars before being passed.
* @internal
*/
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, { asPath: true }).toString()),
path.basename(handlebarsParse(config, filePath, { asPath: true }).toString()),
)
}
/** Information about a template glob pattern and how it was resolved. */
export interface GlobInfo {
/** The template path with glob wildcards stripped. */
baseTemplatePath: string
/** The original template string as provided by the user. */
origTemplate: string
/** Whether the template is a directory or contains glob patterns. */
isDirOrGlob: boolean
/** Whether the template contains glob wildcard characters. */
isGlob: boolean
/** The final resolved template path (with `**\/*` appended for directories). */
template: string
}
/** Expands a list of glob patterns into a flat list of matching file paths. */
export async function getFileList(config: ScaffoldConfig, templates: string[]): Promise<string[]> {
log(config, LogLevel.debug, `Getting file list for glob list: ${templates}`)
return (
await glob(templates, {
dot: true,
nodir: true,
})
).map(path.normalize)
}
/** Analyzes a template path to determine if it's a glob, directory, or single file. */
export async function getTemplateGlobInfo(
config: ScaffoldConfig,
template: string,
): Promise<GlobInfo> {
const _isGlob = hasMagic(template)
log(config, LogLevel.debug, "before isDir", "isGlob:", _isGlob, template)
let resolvedTemplate = template
let baseTemplatePath = _isGlob ? removeGlob(template) : template
baseTemplatePath = path.normalize(baseTemplatePath)
const isDirOrGlob = _isGlob ? true : await isDir(template)
const shouldAddGlob = !_isGlob && isDirOrGlob
log(config, LogLevel.debug, "after", { isDirOrGlob, shouldAddGlob })
if (shouldAddGlob) {
resolvedTemplate = path.join(template, "**", "*")
}
return {
baseTemplatePath,
origTemplate: template,
isDirOrGlob,
isGlob: _isGlob,
template: resolvedTemplate,
}
}
/** Complete information about a template file's output destination. */
export interface OutputFileInfo {
inputPath: string
outputPathOpt: string
outputDir: string
outputPath: string
exists: boolean
}
/** Computes the full output path and metadata for a single template file. */
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.replace(config.tmpDir!, "./"))
const rawOutputPath = path.join(outputDir, path.basename(inputPath))
const outputPath = handlebarsParse(config, rawOutputPath, { asPath: true }).toString()
const exists = await pathExists(outputPath)
return { inputPath, outputPathOpt, outputDir, outputPath, exists }
}
/**
* Reads a template file, applies Handlebars parsing, runs the beforeWrite hook,
* and writes the result to the output path.
*/
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.debug, `Overwriting ${outputPath}`)
}
log(config, LogLevel.debug, `Processing file ${inputPath}`)
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)
} else {
log(config, LogLevel.debug, "Dry run — output would be:")
log(config, LogLevel.debug, finalOutputContents.toString())
}
} else if (exists) {
log(config, LogLevel.debug, `Skipped ${outputPath} (already exists)`)
}
}
/** Computes the output directory for a file, combining the output path, base path, and optional subdir. */
export function getOutputDir(
config: ScaffoldConfig,
outputPathOpt: string,
basePath: string,
): string {
return path.resolve(
process.cwd(),
...([
outputPathOpt,
basePath,
config.subdir
? config.subdirHelper
? handlebarsParse(config, `{{ ${config.subdirHelper} name }}`).toString()
: config.name
: undefined,
].filter(Boolean) as string[]),
)
}
/**
* Processes a single template file: resolves output paths, creates directories,
* and writes the transformed output.
* Returns the output path if the file was written, or null if skipped.
*/
export async function handleTemplateFile(
config: ScaffoldConfig,
{ templatePath, basePath }: { templatePath: string; basePath: string },
): Promise<string | null> {
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)
const shouldWrite = (!exists || overwrite) && !config.dryRun
log(config, LogLevel.debug, `Writing to ${outputPath}`)
await copyFileTransformed(config, { exists, overwrite, outputPath, inputPath })
return shouldWrite ? outputPath : null
} catch (e: unknown) {
handleErr(e as NodeJS.ErrnoException)
throw e
}
}

64
src/fs-utils.ts Normal file
View File

@@ -0,0 +1,64 @@
import os from "node:os"
import path from "node:path"
import fs from "node:fs/promises"
import { F_OK } from "node:constants"
import { LogConfig, LogLevel, ScaffoldConfig } from "./types"
import { log } from "./logger"
const { stat, access, mkdir } = fs
/** Recursively creates a directory and its parents if they don't exist. */
export async function createDirIfNotExists(
dir: string,
config: LogConfig & Pick<ScaffoldConfig, "dryRun">,
): 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: unknown) {
if (e && (e as NodeJS.ErrnoException).code !== "EEXIST") {
throw e
}
return
}
}
}
/** Checks whether a file or directory exists at the given path. */
export async function pathExists(filePath: string): Promise<boolean> {
try {
await access(filePath, F_OK)
return true
} catch (e: unknown) {
if (e && (e as NodeJS.ErrnoException).code === "ENOENT") {
return false
}
throw e
}
}
/** Returns true if the given path is a directory. */
export async function isDir(dirPath: string): Promise<boolean> {
const tplStat = await stat(dirPath)
return tplStat.isDirectory()
}
/** Generates a unique temporary directory path for scaffold operations. @internal */
export function getUniqueTmpPath(): string {
return path.resolve(
os.tmpdir(),
`scaffold-config-${Date.now()}-${Math.random().toString(36).slice(2)}`,
)
}

65
src/git.ts Normal file
View File

@@ -0,0 +1,65 @@
import path from "node:path"
import { log } from "./logger"
import { AsyncResolver, LogConfig, LogLevel, ScaffoldCmdConfig, ScaffoldConfigMap } from "./types"
import { spawn } from "node:child_process"
import { resolve, wrapNoopResolver } from "./utils"
import { findConfigFile } from "./config"
export async function getGitConfig(
url: URL,
file: string,
tmpPath: string,
logConfig: LogConfig,
): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
const repoUrl = `${url.protocol}//${url.host}${url.pathname}`
log(logConfig, LogLevel.debug, `Cloning git repo ${repoUrl}`)
return new Promise((res, reject) => {
log(logConfig, LogLevel.debug, `Cloning git repo to ${tmpPath}`)
const clone = spawn("git", ["clone", "--recurse-submodules", "--depth", "1", repoUrl, tmpPath])
clone.on("error", reject)
clone.on("close", async (code) => {
if (code === 0) {
res(await loadGitConfig({ logConfig, url: repoUrl, file, tmpPath }))
return
}
reject(new Error(`Git clone failed with code ${code}`))
})
})
}
/** @internal */
export async function loadGitConfig({
logConfig,
url: repoUrl,
file,
tmpPath,
}: {
logConfig: LogConfig
url: string
file: string
tmpPath: string
}): Promise<AsyncResolver<ScaffoldCmdConfig, ScaffoldConfigMap>> {
log(logConfig, LogLevel.debug, `Loading config from git repo: ${repoUrl}`)
const filename = file || (await findConfigFile(tmpPath))
const absolutePath = path.resolve(tmpPath, filename)
log(logConfig, LogLevel.debug, `Resolving config file: ${absolutePath}`)
const loadedConfig = await resolve(
async () => (await import(absolutePath)).default as ScaffoldConfigMap,
logConfig,
)
log(logConfig, LogLevel.debug, `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)),
}
}
return wrapNoopResolver(fixedConfig)
}

67
src/ignore.ts Normal file
View File

@@ -0,0 +1,67 @@
import path from "node:path"
import fs from "node:fs/promises"
import { minimatch } from "minimatch"
import { pathExists } from "./fs-utils"
const IGNORE_FILENAME = ".scaffoldignore"
/**
* Reads a `.scaffoldignore` file from the given directory and returns
* the parsed patterns for filtering.
*
* Lines starting with `#` are comments. Empty lines are skipped.
*
* @param dir The directory to search for `.scaffoldignore`
* @returns Array of glob patterns to ignore
*/
export async function loadIgnorePatterns(dir: string): Promise<string[]> {
const ignorePath = path.resolve(dir, IGNORE_FILENAME)
if (!(await pathExists(ignorePath))) {
return []
}
const content = await fs.readFile(ignorePath, "utf-8")
return parseIgnoreFile(content)
}
/**
* Parses the contents of a `.scaffoldignore` file into glob patterns.
* @internal
*/
export function parseIgnoreFile(content: string): string[] {
return content
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0 && !line.startsWith("#"))
}
/**
* Filters a list of file paths, removing any that match the ignore patterns.
* Patterns are matched against the relative path from baseDir.
* Also always excludes `.scaffoldignore` itself.
*/
export function filterIgnoredFiles(
files: string[],
ignorePatterns: string[],
baseDir: string,
): string[] {
return files.filter((file) => {
const basename = path.basename(file)
if (basename === IGNORE_FILENAME) {
return false
}
const relPath = path.relative(baseDir, file)
for (const pattern of ignorePatterns) {
if (
minimatch(relPath, pattern, { dot: true }) ||
minimatch(basename, pattern, { dot: true })
) {
return false
}
}
return true
})
}

6
src/index.ts Normal file
View File

@@ -0,0 +1,6 @@
export * from "./scaffold"
export * from "./types"
export { validateConfig, assertConfigValid, scaffoldConfigSchema } from "./validate"
import Scaffold from "./scaffold"
export default Scaffold

111
src/init.ts Normal file
View File

@@ -0,0 +1,111 @@
import path from "node:path"
import fs from "node:fs/promises"
import select from "@inquirer/select"
import { colorize } from "./colors"
import { pathExists } from "./fs-utils"
const CONFIG_EXTENSIONS = {
js: "scaffold.config.js",
mjs: "scaffold.config.mjs",
json: "scaffold.config.json",
} as const
type ConfigFormat = keyof typeof CONFIG_EXTENSIONS
const CONFIG_TEMPLATES: Record<ConfigFormat, string> = {
js: `/** @type {import('simple-scaffold').ScaffoldConfigMap} */
module.exports = {
default: {
templates: ["templates/default"],
output: ".",
// inputs: {
// author: { message: "Author name", required: true },
// license: { message: "License", default: "MIT" },
// },
},
}
`,
mjs: `/** @type {import('simple-scaffold').ScaffoldConfigMap} */
export default {
default: {
templates: ["templates/default"],
output: ".",
// inputs: {
// author: { message: "Author name", required: true },
// license: { message: "License", default: "MIT" },
// },
},
}
`,
json: `{
"default": {
"templates": ["templates/default"],
"output": "."
}
}
`,
}
const EXAMPLE_TEMPLATE_CONTENT = `# {{ name }}
Created by Simple Scaffold.
{{#if description}}{{ description }}{{/if}}
`
export interface InitOptions {
/** Working directory to create the config in. Defaults to cwd. */
dir?: string
/** Config format to use. If not provided, the user is prompted. */
format?: ConfigFormat
/** Whether to create an example template directory. Defaults to true. */
createExample?: boolean
}
/**
* Initializes a new Simple Scaffold project by creating a config file
* and an optional example template directory.
*/
export async function initScaffold(options: InitOptions = {}): Promise<void> {
const dir = options.dir ?? process.cwd()
const format =
options.format ??
(await select<ConfigFormat>({
message: colorize.cyan("Config file format:"),
choices: [
{ name: "JavaScript (CommonJS)", value: "js" },
{ name: "JavaScript (ESM)", value: "mjs" },
{ name: "JSON", value: "json" },
],
}))
const filename = CONFIG_EXTENSIONS[format]
const configPath = path.resolve(dir, filename)
if (await pathExists(configPath)) {
console.log(colorize.yellow(`${filename} already exists, skipping config creation.`))
} else {
await fs.writeFile(configPath, CONFIG_TEMPLATES[format])
console.log(colorize.green(`Created ${filename}`))
}
const createExample = options.createExample ?? true
if (createExample) {
const templateDir = path.resolve(dir, "templates", "default")
const templateFile = path.join(templateDir, "{{name}}.md")
if (await pathExists(templateDir)) {
console.log(colorize.yellow("templates/default/ already exists, skipping example template."))
} else {
await fs.mkdir(templateDir, { recursive: true })
await fs.writeFile(templateFile, EXAMPLE_TEMPLATE_CONTENT)
console.log(colorize.green("Created templates/default/{{name}}.md"))
}
}
console.log()
console.log(colorize.dim("Get started:"))
console.log(colorize.dim(` npx simple-scaffold MyProject`))
console.log()
}

135
src/logger.ts Normal file
View File

@@ -0,0 +1,135 @@
import util from "util"
import path from "node:path"
import { LogConfig, LogLevel, ScaffoldConfig } from "./types"
import { colorize, TermColor } from "./colors"
/** Priority ordering for log levels (higher = more severe). */
const LOG_PRIORITY: Record<LogLevel, number> = {
[LogLevel.none]: 0,
[LogLevel.debug]: 1,
[LogLevel.info]: 2,
[LogLevel.warning]: 3,
[LogLevel.error]: 4,
}
/** Maps each log level to a terminal color. */
const LOG_LEVEL_COLOR: Record<LogLevel, TermColor> = {
[LogLevel.none]: "reset",
[LogLevel.debug]: "dim",
[LogLevel.info]: "reset",
[LogLevel.warning]: "yellow",
[LogLevel.error]: "red",
}
/** Logs a message at the given level, respecting the configured log level filter. */
export function log(config: LogConfig, level: LogLevel, ...obj: unknown[]): void {
if (
config.logLevel === LogLevel.none ||
LOG_PRIORITY[level] < LOG_PRIORITY[config.logLevel ?? LogLevel.info]
) {
return
}
const colorFn = colorize[LOG_LEVEL_COLOR[level]]
const key: "log" | "warn" | "error" =
level === LogLevel.error ? "error" : level === LogLevel.warning ? "warn" : "log"
const logFn: (..._args: unknown[]) => void = console[key]
logFn(
...obj.map((i) =>
i instanceof Error
? colorFn(i, JSON.stringify(i, undefined, 1), i.stack)
: typeof i === "object"
? util.inspect(i, { depth: null, colors: true })
: colorFn(i),
),
)
}
/**
* Logs detailed file processing information at debug level.
* @deprecated Use `log(config, LogLevel.debug, data)` directly instead.
*/
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)
}
/** Logs the full scaffold configuration at debug level. */
export function logInitStep(config: ScaffoldConfig): void {
log(config, LogLevel.debug, "Full config:", {
name: config.name,
templates: config.templates,
output: config.output,
subdir: config.subdir,
data: config.data,
overwrite: config.overwrite,
subdirHelper: config.subdirHelper,
helpers: Object.keys(config.helpers ?? {}),
logLevel: config.logLevel,
dryRun: config.dryRun,
beforeWrite: config.beforeWrite,
} as Record<keyof ScaffoldConfig, unknown>)
}
/**
* Logs a tree of created files, grouped by directory.
*/
export function logFileTree(config: LogConfig, files: string[]): void {
if (files.length === 0) return
// Find common prefix to make paths relative
const commonDir = files.reduce((prefix, file) => {
while (!file.startsWith(prefix)) {
prefix = path.dirname(prefix)
}
return prefix
}, path.dirname(files[0]))
log(config, LogLevel.info, "")
log(config, LogLevel.info, colorize.bold(`📁 ${commonDir}`))
const relPaths = files.map((f) => path.relative(commonDir, f)).sort()
for (let i = 0; i < relPaths.length; i++) {
const isLast = i === relPaths.length - 1
const prefix = isLast ? "└── " : "├── "
log(config, LogLevel.info, colorize.dim(prefix) + relPaths[i])
}
}
/**
* Logs a final summary line with file count and elapsed time.
*/
export function logSummary(
config: LogConfig,
fileCount: number,
elapsedMs: number,
dryRun?: boolean,
): void {
const timeStr =
elapsedMs < 1000 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1000).toFixed(2)}s`
log(config, LogLevel.info, "")
if (dryRun) {
log(
config,
LogLevel.info,
colorize.yellow(`🏜️ Dry run complete — ${fileCount} file(s) would be created (${timeStr})`),
)
} else if (fileCount === 0) {
log(config, LogLevel.info, colorize.yellow(`⚠️ No files created (${timeStr})`))
} else {
log(config, LogLevel.info, colorize.green(`✅ Created ${fileCount} file(s) in ${timeStr}`))
}
}

140
src/parser.ts Normal file
View File

@@ -0,0 +1,140 @@
import path from "node:path"
import { DefaultHelpers, Helper, LogLevel, ScaffoldConfig } from "./types"
import Handlebars from "handlebars"
import { add, format, parseISO, type Duration } from "date-fns"
import { log } from "./logger"
const dateFns = { add, format, parseISO }
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-alphanumeric character or capital letter boundaries
function toWordParts(string: string): string[] {
// First split on non-alphanumeric characters
return string
.split(/[^a-zA-Z0-9]/)
.flatMap((segment) =>
// Then split camelCase/PascalCase boundaries, handling consecutive uppercase (e.g. "HTMLParser" -> "HTML", "Parser")
segment.split(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-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,
{ asPath = false }: { asPath?: boolean } = {},
): Buffer {
const { data } = config
try {
let str = templateBuffer.toString()
if (asPath) {
str = str.replace(/\\/g, "/")
}
const parser = Handlebars.compile(str, { noEscape: true })
let outputContents = parser(data)
if (asPath && path.sep !== "/") {
outputContents = outputContents.replace(/\//g, "\\")
}
return Buffer.from(outputContents)
} catch (e) {
log(config, LogLevel.debug, e)
log(config, LogLevel.debug, "Couldn't parse file with handlebars, returning original content")
return Buffer.from(templateBuffer)
}
}

19
src/path-utils.ts Normal file
View File

@@ -0,0 +1,19 @@
import path from "node:path"
/** Strips glob wildcard characters from a template path. */
export function removeGlob(template: string): string {
return path.normalize(template.replace(/\*/g, ""))
}
/** Removes a leading path separator, making the path relative. */
export function makeRelativePath(str: string): string {
return str.startsWith(path.sep) ? str.slice(1) : str
}
/** Computes a base path relative to the current working directory. */
export function getBasePath(relPath: string): string {
return path
.resolve(process.cwd(), relPath)
.replace(process.cwd() + path.sep, "")
.replace(process.cwd(), "")
}

253
src/prompts.ts Normal file
View File

@@ -0,0 +1,253 @@
import input from "@inquirer/input"
import select from "@inquirer/select"
import confirm from "@inquirer/confirm"
import number from "@inquirer/number"
import { colorize } from "./colors"
import {
ScaffoldCmdConfig,
ScaffoldConfig,
ScaffoldConfigMap,
ScaffoldInput,
ScaffoldInputType,
} from "./types"
/** Prompts the user for a scaffold name. */
export async function promptForName(): Promise<string> {
return input({
message: colorize.cyan("Scaffold name:"),
required: true,
validate: (value) => {
if (!value.trim()) return "Name cannot be empty"
return true
},
})
}
/** Prompts the user to select a template key from the available config keys. */
export async function promptForTemplateKey(configMap: ScaffoldConfigMap): Promise<string> {
const keys = Object.keys(configMap)
if (keys.length === 0) {
throw new Error("No templates found in config file")
}
if (keys.length === 1) {
return keys[0]
}
return select({
message: colorize.cyan("Select a template:"),
choices: keys.map((key) => ({
name: key,
value: key,
})),
})
}
/** Prompts the user for an output directory path. */
export async function promptForOutput(): Promise<string> {
return input({
message: colorize.cyan("Output directory:"),
required: true,
default: ".",
validate: (value) => {
if (!value.trim()) return "Output directory cannot be empty"
return true
},
})
}
/** Prompts the user for template paths (comma-separated). */
export async function promptForTemplates(): Promise<string[]> {
const value = await input({
message: colorize.cyan("Template paths (comma-separated):"),
required: true,
validate: (value) => {
if (!value.trim()) return "At least one template path is required"
return true
},
})
return value
.split(",")
.map((t) => t.trim())
.filter(Boolean)
}
/** Prompts for a single input based on its type. */
async function promptSingleInput(
key: string,
def: ScaffoldInput,
): Promise<string | boolean | number | undefined> {
const type: ScaffoldInputType = def.type ?? "text"
const message = colorize.cyan(def.message ?? `${key}:`)
switch (type) {
case "text":
return input({
message,
required: def.required,
default: def.default as string | undefined,
validate: def.required
? (value) => {
if (!value.trim()) return `${key} is required`
return true
}
: undefined,
})
case "select": {
const choices = (def.options ?? []).map((opt) =>
typeof opt === "string" ? { name: opt, value: opt } : opt,
)
if (choices.length === 0) {
throw new Error(`Input "${key}" has type "select" but no options defined`)
}
return select({
message,
choices,
default: def.default as string | undefined,
})
}
case "confirm":
return confirm({
message,
default: (def.default as boolean | undefined) ?? false,
})
case "number":
return (
(await number({
message,
required: def.required,
default: def.default as number | undefined,
})) ?? def.default
)
}
}
/**
* Prompts the user for any required scaffold inputs that are not already provided in data.
* Also applies default values for optional inputs that have one.
* Returns the merged data object.
*/
export async function promptForInputs(
inputs: Record<string, ScaffoldInput>,
existingData: Record<string, unknown> = {},
): Promise<Record<string, unknown>> {
const data = { ...existingData }
for (const [key, def] of Object.entries(inputs)) {
// Skip if already provided via data/CLI
if (key in data && data[key] !== undefined && data[key] !== "") {
continue
}
if (def.required || def.type === "select" || def.type === "confirm") {
data[key] = await promptSingleInput(key, def)
} else if (def.default !== undefined && !(key in data)) {
data[key] = def.default
}
}
return data
}
/** Returns true if the process is running in an interactive terminal. */
export function isInteractive(): boolean {
return Boolean(process.stdin.isTTY)
}
/**
* Prompts for name and template key before the config file is parsed.
* These are needed by parseConfigFile to know which template to load.
*/
export async function promptBeforeConfig(
config: ScaffoldCmdConfig,
configMap?: ScaffoldConfigMap,
): Promise<ScaffoldCmdConfig> {
if (!isInteractive()) {
return config
}
if (!config.name) {
config.name = await promptForName()
}
if (configMap && !config.key) {
const keys = Object.keys(configMap)
if (keys.length > 1) {
config.key = await promptForTemplateKey(configMap)
}
}
return config
}
/**
* Prompts for any values still missing after the config file has been parsed.
* Only prompts in interactive mode.
*/
export async function promptAfterConfig(config: ScaffoldConfig): Promise<ScaffoldConfig> {
if (!isInteractive()) {
return config
}
if (!config.output || (typeof config.output === "string" && !config.output)) {
config.output = await promptForOutput()
}
if (!config.templates || config.templates.length === 0) {
config.templates = await promptForTemplates()
}
return config
}
/**
* @deprecated Use {@link promptBeforeConfig} and {@link promptAfterConfig} instead.
*/
export async function promptForMissingConfig(
config: ScaffoldCmdConfig,
configMap?: ScaffoldConfigMap,
): Promise<ScaffoldCmdConfig> {
const afterPre = await promptBeforeConfig(config, configMap)
if (!isInteractive()) {
return afterPre
}
if (!afterPre.output) {
afterPre.output = await promptForOutput()
}
if (!afterPre.templates || afterPre.templates.length === 0) {
afterPre.templates = await promptForTemplates()
}
return afterPre
}
/**
* Prompts for any required inputs defined in the scaffold config and merges them into data.
* Only prompts in interactive mode; in non-interactive mode, only applies defaults.
*/
export async function resolveInputs(config: ScaffoldConfig): Promise<ScaffoldConfig> {
if (!config.inputs) {
return config
}
const interactive = isInteractive()
if (interactive) {
config.data = await promptForInputs(config.inputs, config.data)
} else {
// Non-interactive: only apply defaults
const data = { ...config.data }
for (const [key, def] of Object.entries(config.inputs)) {
if (def.default !== undefined && !(key in data)) {
data[key] = def.default
}
}
config.data = data
}
return config
}

223
src/scaffold.ts Normal file
View File

@@ -0,0 +1,223 @@
/**
* @module
* Simple Scaffold
*
* See [readme](README.md)
*/
import path from "node:path"
import os from "node:os"
import { exec } from "node:child_process"
import { handleErr, resolve } from "./utils"
import { isDir, getTemplateGlobInfo, getFileList, handleTemplateFile, GlobInfo } from "./file"
import { removeGlob, makeRelativePath, getBasePath } from "./path-utils"
import { LogLevel, MinimalConfig, Resolver, ScaffoldCmdConfig, ScaffoldConfig } from "./types"
import { registerHelpers } from "./parser"
import { log, logInitStep, logFileTree, logSummary } from "./logger"
import { parseConfigFile } from "./config"
import { resolveInputs } from "./prompts"
import { loadIgnorePatterns, filterIgnoredFiles } from "./ignore"
import { assertConfigValid } from "./validate"
/**
* Create a scaffold using given `options`.
*
* #### Create files
* To create a file structure to output, use any directory and file structure you would like.
* Inside folder names, file names or file contents, you may place `{{ var }}` where `var` is either
* `name` which is the scaffold name you provided or one of the keys you provided in the `data` option.
*
* The contents and names will be replaced with the transformed values so you can use your original structure as a
* boilerplate for other projects, components, modules, or even single files.
*
* The files will maintain their structure, starting from the directory containing the template (or the template itself
* if it is already a directory), and will output from that directory into the directory defined by `config.output`.
*
* #### Helpers
* Helpers are functions you can use to transform your `{{ var }}` contents into other values without having to
* pre-define the data and use a duplicated key.
*
* Any functions you provide in `helpers` option will also be available to you to make custom formatting as you see fit
* (for example, formatting a date)
*
* For available default values, see {@link DefaultHelpers}.
*
* @param {ScaffoldConfig} config The main configuration object
* @return {Promise<void>} A promise that resolves when the scaffold is complete
*
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
*
* @category Main
*/
export async function Scaffold(config: ScaffoldConfig): Promise<void> {
config.output ??= process.cwd()
await assertConfigValid(config)
config = await resolveInputs(config)
registerHelpers(config)
const startTime = performance.now()
const writtenFiles: string[] = []
try {
config.data = { name: config.name, ...config.data }
logInitStep(config)
log(config, LogLevel.info, `Scaffolding "${config.name}"...`)
const excludes = config.templates.filter((t) => t.startsWith("!"))
const includes = config.templates.filter((t) => !t.startsWith("!"))
const templates = await resolveTemplateGlobs(config, includes)
for (const tpl of templates) {
const files = await processTemplateGlob(config, tpl, excludes)
writtenFiles.push(...files)
}
} catch (e: unknown) {
log(config, LogLevel.error, e)
throw e
}
const elapsed = performance.now() - startTime
logFileTree(config, writtenFiles)
logSummary(config, writtenFiles.length, elapsed, config.dryRun)
if (config.afterScaffold) {
await runAfterScaffoldHook(config, writtenFiles)
}
}
/** Resolves included template paths into GlobInfo objects. */
async function resolveTemplateGlobs(
config: ScaffoldConfig,
includes: string[],
): Promise<GlobInfo[]> {
const templates: GlobInfo[] = []
for (const includedTemplate of includes) {
try {
templates.push(await getTemplateGlobInfo(config, includedTemplate))
} catch (e: unknown) {
handleErr(e as NodeJS.ErrnoException)
}
}
return templates
}
/** Processes all files matching a single template glob pattern. Returns paths of written files. */
async function processTemplateGlob(
config: ScaffoldConfig,
tpl: GlobInfo,
excludes: string[],
): Promise<string[]> {
const written: string[] = []
// Load .scaffoldignore from the template base directory
const ignorePatterns = await loadIgnorePatterns(tpl.baseTemplatePath)
if (ignorePatterns.length > 0) {
log(config, LogLevel.debug, `Loaded .scaffoldignore patterns:`, ignorePatterns)
}
const allFiles = await getFileList(config, [tpl.template, ...excludes])
const files = filterIgnoredFiles(allFiles, ignorePatterns, tpl.baseTemplatePath)
for (const file of files) {
if (await isDir(file)) {
continue
}
log(config, LogLevel.debug, "Iterating files", { files, file })
const relPath = makeRelativePath(
path.dirname(removeGlob(file).replace(tpl.baseTemplatePath, "")),
)
const basePath = getBasePath(relPath)
log(config, LogLevel.debug, {
originalTemplate: tpl.origTemplate,
relativePath: relPath,
parsedTemplate: tpl.template,
inputFilePath: file,
baseTemplatePath: tpl.baseTemplatePath,
basePath,
isDirOrGlob: tpl.isDirOrGlob,
isGlob: tpl.isGlob,
})
const outputPath = await handleTemplateFile(config, { templatePath: file, basePath })
if (outputPath) {
written.push(outputPath)
}
}
return written
}
/** Executes the afterScaffold hook — either a function or a shell command string. */
async function runAfterScaffoldHook(config: ScaffoldConfig, files: string[]): Promise<void> {
const hook = config.afterScaffold!
if (typeof hook === "function") {
log(config, LogLevel.debug, "Running afterScaffold function hook")
await hook({ config, files })
return
}
// Shell command string
const outputDir = typeof config.output === "string" ? config.output : process.cwd()
const cwd = path.resolve(process.cwd(), outputDir)
log(config, LogLevel.info, `Running afterScaffold command: ${hook}`)
await new Promise<void>((resolve, reject) => {
const proc = exec(hook, { cwd })
proc.stdout?.on("data", (data: string) => {
log(config, LogLevel.info, data.toString().trimEnd())
})
proc.stderr?.on("data", (data: string) => {
log(config, LogLevel.warning, data.toString().trimEnd())
})
proc.on("close", (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`afterScaffold command exited with code ${code}`))
}
})
proc.on("error", reject)
})
}
/**
* 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 (
pathOrUrl: string,
config: MinimalConfig,
overrides?: Resolver<ScaffoldCmdConfig, Partial<Omit<ScaffoldConfig, "name">>>,
): Promise<void> {
const tmpPath = path.resolve(os.tmpdir(), `scaffold-config-${Date.now()}`)
const _cmdConfig: ScaffoldCmdConfig = {
dryRun: false,
output: process.cwd(),
logLevel: LogLevel.info,
overwrite: false,
templates: [],
subdir: false,
quiet: false,
config: pathOrUrl,
version: false,
tmpDir: tmpPath,
...config,
}
const _overrides = resolve(overrides, _cmdConfig)
const _config = await parseConfigFile(_cmdConfig)
return Scaffold({ ..._config, ..._overrides })
}
export default Scaffold

545
src/types.ts Normal file
View File

@@ -0,0 +1,545 @@
import { HelperDelegate } from "handlebars/runtime"
/**
* The config object for defining a scaffolding group.
*
* @see {@link https://chenasraf.github.io/simple-scaffold/docs/usage/node| Node.js usage}
* @see {@link https://chenasraf.github.io/simple-scaffold/docs/usage/cli| CLI usage}
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
*
* @category Config
*/
export interface ScaffoldConfig {
/**
* Name to be passed to the generated files. `{{name}}` and `{{Name}}` inside contents and file names will be replaced
* accordingly.
*/
name: string
/**
* 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.
*
* You may omit files from output by prepending a `!` to their glob pattern.
*
* For example, `["components/**", "!components/README.md"]` will include everything in the directory `components`
* except the `README.md` file inside.
*
* @default Current working directory
*/
templates: string[]
/**
* Path to output to. If `subdir` is `true`, the subdir will be created inside this path.
*
* May also be a {@link FileResponseHandler} which returns a new output path to override the default one.
*
* @see {@link FileResponse}
* @see {@link FileResponseHandler}
*/
output: FileResponse<string>
/**
* Whether to create subdir with the input name.
*
* When `true`, you may also use {@link subdirHelper} to determine a pre-process helper on
* the directory name.
*
* @default `false`
*/
subdir?: boolean
/**
* Add custom data to the templates. By default, only your app name is included as `{{name}}` and `{{Name}}`.
*
* This can be any object that will be usable by Handlebars.
*/
data?: Record<string, unknown>
/**
* 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 boolean for each file.
*
* May also be a {@link FileResponseHandler} which returns a boolean value per file.
*
* @see {@link FileResponse}
* @see {@link FileResponseHandler}
*
* @default `false`
*/
overwrite?: FileResponse<boolean>
/**
* Determine amount of logs to display.
*
* The values are: `0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4 (error)`. The provided level will display messages
* of the same level or higher.
*
* @see {@link LogLevel}
*
* @default `2 (info)`
*/
logLevel?: LogLevel
/**
* Don't emit files. This is good for testing your scaffolds and making sure they don't fail, without having to write
* actual file contents or create directories.
*
* @default `false`
*/
dryRun?: boolean
/**
* Additional helpers to add to the template parser. Provide an object whose keys are the name of the function to add,
* and the value is the helper function itself. The signature of helpers is as follows:
* ```typescript
* (text: string, ...args: any[]) => string
* ```
*
* A full example might be:
*
* ```typescript
* Scaffold({
* //...
* helpers: {
* upperKebabCase: (text) => kebabCase(text).toUpperCase()
* }
* })
* ```
*
* Which will allow:
*
* ```
* {{ upperKebabCase "my value" }}
* ```
*
* To transform to:
*
* ```
* MY-VALUE
* ```
*
* See {@link DefaultHelpers} for a list of all the built-in available helpers.
*
* Simple Scaffold uses Handlebars.js, so all the syntax from there is supported. See
* [their docs](https://handlebarsjs.com/guide/#custom-helpers) for more information.
*
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
* @see {@link https://chenasraf.github.io/simple-scaffold/docs/usage/templates| Templates}
* */
helpers?: Record<string, Helper>
/**
* Default transformer to apply to subdir name when using `subdir: true`. Can be one of the default
* capitalization helpers, or a custom one you provide to `helpers`. Defaults to `undefined`, which means no
* transformation is done.
*
* @see {@link subdir}
* @see {@link CaseHelpers}
* @see {@link DefaultHelpers}
*/
subdirHelper?: DefaultHelpers | string
/**
* This callback runs right before content is being written to the disk. If you supply this function, you may return
* a string that represents the final content of your file, you may process the content as you see fit. For example,
* you may run formatters on a file, fix output in edge-cases not supported by helpers or data, etc.
*
* If the return value of this function is `undefined`, the original content will be used.
*
* @param content The original template after token replacement
* @param rawContent The original template before token replacement
* @param outputPath The final output path of the processed file
*
* @returns {Promise<String | Buffer | undefined> | String | Buffer | undefined} The final output of the file
* contents-only, after further modifications - or `undefined` to use the original content (i.e. `content.toString()`)
*/
beforeWrite?(
content: Buffer,
rawContent: Buffer,
outputPath: string,
): string | Buffer | undefined | Promise<string | Buffer | undefined>
/**
* Defines interactive inputs for the template. Each input becomes a template data variable.
*
* When running interactively, required inputs that are not already provided via `data` or CLI args
* will be prompted for. Optional inputs without a value will use their `default` if defined.
*
* @example
* ```typescript
* Scaffold({
* // ...
* inputs: {
* author: { message: "Author name", required: true },
* license: { message: "License", default: "MIT" },
* },
* })
* ```
*
* In templates: `{{ author }}`, `{{ license }}`
*
* @see {@link ScaffoldInput}
*/
inputs?: Record<string, ScaffoldInput>
/**
* A callback or shell command that runs after all files have been written.
*
* When provided as a **function** (Node.js API), it receives a context object with the scaffold
* config and the list of files that were written:
*
* ```typescript
* Scaffold({
* // ...
* afterScaffold: async ({ config, files }) => {
* console.log(`Created ${files.length} files`)
* execSync("npm install", { cwd: config.output })
* },
* })
* ```
*
* When provided as a **string** (CLI `--after` flag), it is executed as a shell command
* in the output directory after scaffolding completes.
*
* @see {@link AfterScaffoldContext}
*/
afterScaffold?: AfterScaffoldHook
/** @internal */
tmpDir?: string
}
/**
* Context passed to the {@link ScaffoldConfig.afterScaffold} hook.
*
* @category Config
*/
export interface AfterScaffoldContext {
/** The resolved scaffold config that was used. */
config: ScaffoldConfig
/** List of absolute paths to files that were written. */
files: string[]
}
/**
* A hook that runs after scaffolding completes.
* Can be a function receiving context, or a shell command string.
*
* @category Config
*/
export type AfterScaffoldHook = ((context: AfterScaffoldContext) => void | Promise<void>) | string
/**
* The type of an interactive input prompt.
*
* - `"text"` — free-form text input (default)
* - `"select"` — choose from a list of options
* - `"confirm"` — yes/no boolean prompt
* - `"number"` — numeric input
*
* @category Config
*/
export type ScaffoldInputType = "text" | "select" | "confirm" | "number"
/**
* Defines a single interactive input for a scaffold template.
*
* @example
* ```typescript
* inputs: {
* author: { message: "Author name", required: true },
* license: { type: "select", message: "License", options: ["MIT", "Apache-2.0", "GPL-3.0"] },
* private: { type: "confirm", message: "Private package?", default: false },
* port: { type: "number", message: "Dev server port", default: 3000 },
* }
* ```
*
* @category Config
*/
export interface ScaffoldInput {
/** The type of prompt. Defaults to `"text"`. */
type?: ScaffoldInputType
/** The prompt message shown to the user. Defaults to the input key name if omitted. */
message?: string
/** Whether this input must be provided. If true and missing, the user will be prompted interactively. */
required?: boolean
/** Default value. Type depends on the input type: string for text/select, boolean for confirm, number for number. */
default?: string | boolean | number
/** List of options for `type: "select"`. Each can be a string or `{ name, value }`. */
options?: (string | { name: string; value: string })[]
}
/**
* The names of the available helper functions that relate to text capitalization.
*
* These are available for `subdirHelper`.
*
* | Helper name | Example code | Example output |
* | ------------ | ----------------------- | -------------- |
* | [None] | `{{ name }}` | my name |
* | `camelCase` | `{{ camelCase name }}` | myName |
* | `snakeCase` | `{{ snakeCase name }}` | my_name |
* | `startCase` | `{{ startCase name }}` | My Name |
* | `kebabCase` | `{{ kebabCase name }}` | my-name |
* | `hyphenCase` | `{{ hyphenCase name }}` | my-name |
* | `pascalCase` | `{{ pascalCase name }}` | MyName |
* | `upperCase` | `{{ upperCase name }}` | MY NAME |
* | `lowerCase` | `{{ lowerCase name }}` | my name |
*
* @see {@link DefaultHelpers}
* @see {@link DateHelpers}
* @see {@link ScaffoldConfig}
* @see {@link ScaffoldConfig.subdirHelper}
*
* @category Helpers
*/
export type CaseHelpers =
| "camelCase"
| "hyphenCase"
| "kebabCase"
| "lowerCase"
| "pascalCase"
| "snakeCase"
| "startCase"
| "upperCase"
/**
* The names of the available helper functions that relate to dates.
*
* | Helper name | Description | Example code | Example output |
* | -------------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------ |
* | `now` | Current date with format | `{{ now "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
* | `now` (with offset) | Current date with format, and with offset | `{{ now "yyyy-MM-dd HH:mm" -1 "hours" }}` | `2042-01-01 14:00` |
* | `date` | Custom date with format | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" }}` | `2042-01-01 15:00` |
* | `date` (with offset) | Custom date with format, and with offset | `{{ date "2042-01-01T15:00:00Z" "yyyy-MM-dd HH:mm" -1 "days" }}` | `2041-31-12 15:00` |
* | `date` (with date from `--data`) | Custom date with format, with data from the `data` config option | `{{ date myCustomDate "yyyy-MM-dd HH:mm" }}` | `2042-01-01 12:00` |
*
* Further details:
*
* - We use [`date-fns`](https://date-fns.org/docs/) for parsing/manipulating the dates. If you want
* more information on the date tokens to use, refer to
* [their format documentation](https://date-fns.org/docs/format).
*
* - The date helper format takes the following arguments:
*
* ```typescript
* (
* date: string,
* format: string,
* offsetAmount?: number,
* offsetType?: "years" | "months" | "weeks" | "days" | "hours" | "minutes" | "seconds"
* )
* ```
*
* - **The now helper** (for current time) takes the same arguments, minus the first one (`date`) as it is implicitly
* the current date.
*
* @see {@link DefaultHelpers}
* @see {@link CaseHelpers}
* @see {@link ScaffoldConfig}
*
* @category Helpers
*/
export type DateHelpers = "date" | "now"
/**
* The names of all the available helper functions in templates.
* Simple-Scaffold provides some built-in text transformation filters usable by Handlebars.js.
*
* For example, you may use `{{ snakeCase name }}` inside a template file or filename, and it will
* replace `My Name` with `my_name` when producing the final value.
*
* @see {@link CaseHelpers}
* @see {@link DateHelpers}
* @see {@link ScaffoldConfig}
*
* @category Helpers
*/
export type DefaultHelpers = CaseHelpers | DateHelpers
/**
* Helper function, see https://handlebarsjs.com/guide/#custom-helpers
*
* @category Helpers
*/
export type Helper = HelperDelegate
/**
* The amount of information to log when generating scaffold.
* When not `none`, the selected level will be the lowest level included.
*
* For example, level `info` will include `info`, `warning` and `error`, but not `debug`; and `warning` will only
* show `warning` and `error`, but not `info` or `debug`.
*
* @default `info`
*
* @category Logging (const)
*/
export const LogLevel = {
/** Silent output */
none: "none",
/** Debugging information. Very verbose and only recommended for troubleshooting. */
debug: "debug",
/**
* The regular level of logging. Major actions are logged to show the scaffold progress.
*
* @default
*/
info: "info",
/** Warnings such as when file fails to replace token values properly in template. */
warning: "warning",
/** Errors, such as missing files, bad replacement token syntax, or un-writable directories. */
error: "error",
} as const
/**
* The amount of information to log when generating scaffold.
* When not `none`, the selected level will be the lowest level included.
*
* For example, level `info` will include `info`, `warning` and `error`, but not `debug`; and `warning` will only
* show `warning` and `error`, but not `info` or `debug`.
*
* @default `info`
*
* @category Logging (type)
*/
export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]
/**
* A function that takes path information about file, and returns a value of type `T`
*
* @template T The return type for the function
* @param {string} fullPath The full path of the current file
* @param {string} basedir The directory containing the current file
* @param {string} basename The name of the file
*
* @returns {T} A return value
*
* @category Config
*/
export type FileResponseHandler<T> = (fullPath: string, basedir: string, basename: string) => T
/**
* Represents a response for file path information.
* Can either be:
*
* 1. `T` - static value
* 2. A function with the following signature which returns `T`:
* ```typescript
* (fullPath: string, basedir: string, basename: string) => T
* ```
*
* @see {@link FileResponseHandler}
*
* @category Config
* */
export type FileResponse<T> = T | FileResponseHandler<T>
/**
* The Scaffold config for CLI
* Contains less and more specific options than {@link ScaffoldConfig}.
*
* For more information about each option, see {@link ScaffoldConfig}.
*
* @internal
*/
export type ScaffoldCmdConfig = {
/** The name of the scaffold template to use. */
name: string
/** The templates to use for generation */
templates: string[]
/** The output path to write to */
output: string
/** Whether to create subdir with the input name */
subdir: boolean
/** Default transformer to apply to subdir name when using `subdir: true` */
subdirHelper?: string
/** Add custom data to the templates */
data?: Record<string, string>
/** Add custom data to the template in a CLI-friendly syntax (and not JSON) */
appendData?: Record<string, string>
/** Enable to override output files, even if they already exist */
overwrite: boolean
/** Silence logs, same as `logLevel: "none"` */
quiet: boolean
/**
* Determine amount of logs to display.
*
* @see {@link LogLevel}
*/
logLevel: LogLevel
/** Don't emit files. This is good for testing your scaffolds and making sure they don't fail, without having to write actual file contents or create directories. */
dryRun: boolean
/** Config file path to use */
config?: string
/** The key of the template to use */
key?: string
/** The git repository to use to fetch the config file */
git?: string
/** Display version */
version: boolean
/** Run a script before writing the files. This can be a command or a path to a file. The file contents will be passed to the given command. */
beforeWrite?: string
/** Run a shell command after all files have been written. Executed in the output directory. */
afterScaffold?: string
/** @internal */
tmpDir?: 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}
*
* @internal
* @category Config
*/
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
*
* @internal
* @category Config
*/
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, "logLevel">
/** @internal */
export type ConfigLoadConfig = LogConfig & Pick<ScaffoldCmdConfig, "config">
/** @internal */
export type RemoteConfigLoadConfig = LogConfig &
Pick<ScaffoldCmdConfig, "config" | "git" | "tmpDir">
/** @internal */
export type MinimalConfig = Pick<ScaffoldCmdConfig, "name" | "key">
/** @internal */
export type ListCommandCliOptions = Pick<ScaffoldCmdConfig, "config" | "git" | "logLevel" | "quiet">

23
src/utils.ts Normal file
View File

@@ -0,0 +1,23 @@
import { Resolver } from "./types"
// Re-export colors for backward compatibility
export { colorize, type TermColor } from "./colors"
/** Throws the error if non-null, no-ops otherwise. */
export function handleErr(err: NodeJS.ErrnoException | null): void {
if (err) throw err
}
/** Resolves a value that may be either a static value or a function that produces one. */
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)
}
/** Wraps a static value in a resolver function. If already a function, returns as-is. */
export function wrapNoopResolver<T, R = T>(value: Resolver<T, R>): Resolver<T, R> {
if (typeof value === "function") {
return value
}
return (_) => value
}

171
src/validate.ts Normal file
View File

@@ -0,0 +1,171 @@
import { z } from "zod/v4"
import { pathExists } from "./fs-utils"
// --- Reusable schemas ---
/** Schema for a JavaScript function value. */
const functionSchema = z
.any()
.refine((v) => typeof v === "function", { message: "Expected a function" })
/** Schema for a value that can be either a string or a function. */
const stringOrFunctionSchema = z.union([z.string(), functionSchema])
/** Schema for a value that can be either a boolean or a function. */
const booleanOrFunctionSchema = z.union([z.boolean(), functionSchema])
/** Schema for a select input option — either a plain string or a `{ name, value }` object. */
const selectOptionSchema = z.union([z.string(), z.object({ name: z.string(), value: z.string() })])
/** Schema for the input type enum. */
const inputTypeSchema = z.enum(["text", "select", "confirm", "number"])
/** Schema for the log level enum. */
const logLevelSchema = z.enum(["none", "debug", "info", "warning", "error"])
// --- Input schema ---
/** Zod schema for a single scaffold input definition. */
const scaffoldInputSchema = z.object({
type: inputTypeSchema.optional(),
message: z.string().optional(),
required: z.boolean().optional(),
default: z.union([z.string(), z.boolean(), z.number()]).optional(),
options: z.array(selectOptionSchema).optional(),
})
type InputDef = z.infer<typeof scaffoldInputSchema>
function validateInputSemantics(
key: string,
input: InputDef,
): { path: (string | number)[]; message: string }[] {
const issues: { path: (string | number)[]; message: string }[] = []
if (input.type === "select" && (!input.options || input.options.length === 0)) {
issues.push({
path: ["inputs", key, "options"],
message: "select input must have a non-empty options array",
})
}
if (
input.type === "confirm" &&
input.default !== undefined &&
typeof input.default !== "boolean"
) {
issues.push({
path: ["inputs", key, "default"],
message: "confirm input default must be a boolean",
})
}
if (input.type === "number" && input.default !== undefined && typeof input.default !== "number") {
issues.push({
path: ["inputs", key, "default"],
message: "number input default must be a number",
})
}
return issues
}
// --- Config schema ---
/** Zod schema for ScaffoldConfig. */
const scaffoldConfigSchema = z
.object({
name: z.string().min(1, "name is required"),
templates: z.array(z.string()).min(1, "templates must contain at least one entry"),
output: stringOrFunctionSchema,
subdir: z.boolean().optional(),
data: z.record(z.string(), z.unknown()).optional(),
overwrite: booleanOrFunctionSchema.optional(),
logLevel: logLevelSchema.optional(),
dryRun: z.boolean().optional(),
helpers: z.record(z.string(), functionSchema).optional(),
subdirHelper: z.string().optional(),
inputs: z.record(z.string(), scaffoldInputSchema).optional(),
beforeWrite: functionSchema.optional(),
afterScaffold: stringOrFunctionSchema.optional(),
tmpDir: z.string().optional(),
})
.check((ctx) => {
const config = ctx.value
if (config.subdirHelper && !config.subdir) {
ctx.issues.push({
code: "custom",
message: "subdirHelper is set but subdir is not enabled",
path: ["subdirHelper"],
input: config,
})
}
if (config.inputs) {
for (const [key, val] of Object.entries(config.inputs)) {
for (const issue of validateInputSemantics(key, val)) {
ctx.issues.push({ code: "custom", ...issue, input: config })
}
}
}
})
export {
scaffoldConfigSchema,
scaffoldInputSchema,
functionSchema,
stringOrFunctionSchema,
booleanOrFunctionSchema,
selectOptionSchema,
inputTypeSchema,
logLevelSchema,
}
/**
* Validates a scaffold config and returns a list of human-readable errors.
* Returns an empty array if the config is valid.
*/
export function validateConfig(config: unknown): string[] {
const result = scaffoldConfigSchema.safeParse(config)
if (result.success) {
return []
}
return result.error.issues.map((issue) => {
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)"
return `${path}: ${issue.message}`
})
}
/**
* Validates template paths exist on disk.
* Only checks non-glob, non-negation paths.
*/
export async function validateTemplatePaths(templates: string[]): Promise<string[]> {
const errors: string[] = []
for (const tpl of templates) {
if (tpl.startsWith("!") || tpl.includes("*")) continue
if (!(await pathExists(tpl))) {
errors.push(`templates: path does not exist: ${tpl}`)
}
}
return errors
}
/**
* Validates the config and throws a formatted error if any issues are found.
* Checks both schema validity and template path existence.
*/
export async function assertConfigValid(config: unknown): Promise<void> {
const schemaErrors = validateConfig(config)
const pathErrors =
config &&
typeof config === "object" &&
"templates" in config &&
Array.isArray((config as { templates: unknown }).templates)
? await validateTemplatePaths((config as { templates: string[] }).templates)
: []
const allErrors = [...schemaErrors, ...pathErrors]
if (allErrors.length > 0) {
const lines = allErrors.map((e) => ` - ${e}`)
throw new Error(`Invalid scaffold config:\n${lines.join("\n")}`)
}
}

23
test.ts
View File

@@ -1,23 +0,0 @@
import SimpleScaffold from './scaffold'
import * as path from 'path'
const templateDir = path.join(process.cwd(), 'examples')
new SimpleScaffold({
templates: [templateDir + '/test-input/Component/**/*'],
output: templateDir + '/test-output/no-create-subpath',
createSubfolder: false,
locals: {
property: 'myProp',
value: '"value"'
}
}).run()
new SimpleScaffold({
templates: [templateDir + '/test-input/Component/**/*'],
output: templateDir + '/test-output',
locals: {
property: 'myProp',
value: '"value"'
}
}).run()

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

@@ -0,0 +1,539 @@
import { describe, test, expect, beforeEach, afterEach, beforeAll, vi } from "vitest"
import mockFs from "mock-fs"
import FileSystem from "mock-fs/lib/filesystem"
import { Console } from "console"
import { LogLevel, ScaffoldCmdConfig, ScaffoldConfig } from "../src/types"
import * as config from "../src/config"
import { resolve } from "../src/utils"
import configFile from "./test-config"
import { findConfigFile, getOptionValueForFile } from "../src/config"
import { registerHelpers } from "../src/parser"
import path from "path"
vi.mock("../src/git", async () => {
const actual = await vi.importActual<typeof import("../src/git")>("../src/git")
return {
...actual,
getGitConfig: () => {
return Promise.resolve(blankCliConf)
},
}
})
const { githubPartToUrl, parseAppendData, parseConfigFile } = config
const blankCliConf: ScaffoldCmdConfig = {
logLevel: LogLevel.none,
name: "",
output: "",
templates: [],
data: { name: "test" },
overwrite: false,
subdir: false,
dryRun: false,
quiet: false,
version: false,
}
const blankConfig: ScaffoldCmdConfig = {
...blankCliConf,
data: {},
}
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",
})
})
test("handles JSON array values with :=", () => {
expect(parseAppendData('items:=["a","b"]', blankCliConf)).toEqual({
items: ["a", "b"],
name: "test",
})
})
test("handles JSON boolean with :=", () => {
expect(parseAppendData("flag:=true", blankCliConf)).toEqual({ flag: true, name: "test" })
expect(parseAppendData("flag:=false", blankCliConf)).toEqual({ flag: false, name: "test" })
})
test("handles JSON null with :=", () => {
expect(parseAppendData("val:=null", blankCliConf)).toEqual({ val: null, name: "test" })
})
test("handles JSON object with :=", () => {
expect(parseAppendData('obj:={"a":1}', blankCliConf)).toEqual({ obj: { a: 1 }, name: "test" })
})
test("handles single quoted values", () => {
expect(parseAppendData("key='value test'", blankCliConf)).toEqual({
key: "value test",
name: "test",
})
})
test("handles empty string value", () => {
expect(parseAppendData("key=", blankCliConf)).toEqual({ key: "", name: "test" })
})
test("handles negative number with :=", () => {
expect(parseAppendData("num:=-42", blankCliConf)).toEqual({ num: -42, name: "test" })
})
test("handles float with :=", () => {
expect(parseAppendData("num:=3.14", blankCliConf)).toEqual({ num: 3.14, 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",
)
})
test("handles organization repos", () => {
expect(githubPartToUrl("org/sub-repo")).toEqual("https://github.com/org/sub-repo.git")
})
test("handles repos with dots in name", () => {
expect(githubPartToUrl("user/my.repo")).toEqual("https://github.com/user/my.repo.git")
})
})
describe("parseConfigFile", () => {
test("normal config does not change", async () => {
const tmpDir = `/tmp/scaffold-config-${Date.now()}`
const { quiet: _, tmpDir: _tmpDir, version: __, ...conf } = blankCliConf
expect(
await parseConfigFile({
...blankCliConf,
name: "-",
tmpDir,
}),
).toEqual({ ...conf, name: "-", tmpDir, subdirHelper: undefined, beforeWrite: undefined })
})
describe("appendData", () => {
test("appends", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "-",
appendData: { key: "value" },
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result?.data?.key).toEqual("value")
})
test("overwrites existing value", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "-",
data: { num: "123" },
appendData: { num: "1234" },
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result?.data?.num).toEqual("1234")
})
test("CLI output overrides config file output", async () => {
const tmpDir = `/tmp/scaffold-config-${Date.now()}`
const result = await parseConfigFile({
...blankCliConf,
config: path.resolve(__dirname, "test-config.js"),
key: "component",
output: "examples/test-output/override",
name: "Component",
tmpDir,
})
expect(result.output).toEqual("examples/test-output/override")
})
})
test("throws when name is missing", async () => {
await expect(
parseConfigFile({
...blankCliConf,
name: "",
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
}),
).rejects.toThrow("Missing required option: name")
})
test("preserves dryRun setting", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "test",
dryRun: true,
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result.dryRun).toBe(true)
})
test("preserves subdir setting", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "test",
subdir: true,
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result.subdir).toBe(true)
})
test("preserves overwrite setting", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "test",
overwrite: true,
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result.overwrite).toBe(true)
})
test("merges data from config and appendData", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "test",
data: { key1: "val1" },
appendData: { key2: "val2" },
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result.data).toEqual({ key1: "val1", key2: "val2" })
})
test("appendData overrides data", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "test",
data: { key: "original" },
appendData: { key: "overridden" },
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result.data?.key).toEqual("overridden")
})
test("sets subdirHelper from config", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "test",
subdirHelper: "pascalCase",
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result.subdirHelper).toEqual("pascalCase")
})
test("handles empty templates array", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "test",
templates: [],
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result.templates).toEqual([])
})
test("throws when config key not found", async () => {
await expect(
parseConfigFile({
...blankCliConf,
name: "test",
config: path.resolve(__dirname, "test-config.js"),
key: "nonexistent",
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
}),
).rejects.toThrow('Template "nonexistent" not found')
})
test("uses default key when key not specified", async () => {
const result = await parseConfigFile({
...blankCliConf,
name: "MyComponent",
templates: undefined as unknown as string[],
config: path.resolve(__dirname, "test-config.js"),
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
expect(result.templates.length).toBeGreaterThan(0)
})
})
describe("getConfig", () => {
test("gets git config", async () => {
const resultFn = await config.getRemoteConfig({
git: "https://github.com/chenasraf/simple-scaffold.git",
logLevel: LogLevel.none,
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
})
const result = await resolve(resultFn, blankCliConf)
expect(result).toEqual(blankCliConf)
})
test("gets local file config", async () => {
const resultFn = await config.getLocalConfig({
config: path.join(__dirname, "test-config.js"),
logLevel: LogLevel.none,
})
const result = (await resolve(resultFn, {} as ScaffoldCmdConfig)).default
expect(result).toEqual(configFile)
})
})
describe("getRemoteConfig", () => {
test("throws for unsupported protocol", async () => {
await expect(
config.getRemoteConfig({
git: "ftp://example.com/repo.git",
logLevel: LogLevel.none,
tmpDir: `/tmp/scaffold-config-${Date.now()}`,
}),
).rejects.toThrow("Unsupported protocol")
})
})
describe("getOptionValueForFile", () => {
const conf: ScaffoldConfig = {
name: "test",
output: "output",
templates: [],
logLevel: LogLevel.none,
data: { name: "test" },
}
beforeAll(() => {
registerHelpers(conf)
})
test("returns static string value", () => {
expect(getOptionValueForFile(conf, "/some/path", "static-value")).toEqual("static-value")
})
test("returns static boolean value", () => {
expect(getOptionValueForFile(conf, "/some/path", true)).toBe(true)
expect(getOptionValueForFile(conf, "/some/path", false)).toBe(false)
})
test("calls function with file path info", () => {
const fn = vi.fn().mockReturnValue("custom-output")
const result = getOptionValueForFile(conf, "/home/user/file.txt", fn)
expect(result).toEqual("custom-output")
expect(fn).toHaveBeenCalledWith("/home/user/file.txt", expect.any(String), expect.any(String))
})
test("returns default value when fn is not a function and no value", () => {
expect(
getOptionValueForFile(conf, "/some/path", undefined as unknown as string, "default"),
).toEqual("default")
})
test("function receives parsed basename", () => {
const fn = (_fullPath: string, _basedir: string, basename: string) => basename
const result = getOptionValueForFile(conf, "/home/user/{{name}}.txt", fn)
expect(result).toEqual("test.txt")
})
})
describe("findConfigFile", () => {
const struct1 = {
"scaffold.config.js": `module.exports = '${JSON.stringify(blankConfig)}'`,
}
const struct2 = {
"scaffold.js": `module.exports = '${JSON.stringify(blankConfig)}'`,
}
const struct3 = {
"scaffold.cjs": `module.exports = '${JSON.stringify(blankConfig)}'`,
}
const struct4 = {
"scaffold.json": JSON.stringify(blankConfig),
}
function withMock(fileStruct: FileSystem.DirectoryItems, testFn: () => void): () => void {
return () => {
beforeEach(() => {
// console.log("Mocking:", fileStruct)
console = new Console(process.stdout, process.stderr)
mockFs(fileStruct)
// logMock = jest.spyOn(console, 'log').mockImplementation((...args) => {
// logsTemp.push(args)
// })
})
testFn()
afterEach(() => {
// console.log("Restoring mock")
mockFs.restore()
})
}
}
for (const struct of [struct1, struct2, struct3, struct4]) {
const [k] = Object.keys(struct)
describe(
`finds config file ${k}`,
withMock(struct, () => {
test(`finds ${k}`, async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual(k)
})
}),
)
}
describe(
"finds .mjs config file",
withMock({ "scaffold.config.mjs": "export default {}" }, () => {
test("finds scaffold.config.mjs", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.config.mjs")
})
}),
)
describe(
"priority order",
withMock(
{
"scaffold.config.js": "module.exports = {}",
"scaffold.js": "module.exports = {}",
},
() => {
test("prefers scaffold.config.js over scaffold.js", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.config.js")
})
},
),
)
describe(
"throws when no config found",
withMock({ "unrelated-file.txt": "content" }, () => {
test("throws error when no config file exists", async () => {
await expect(findConfigFile(process.cwd())).rejects.toThrow("Could not find config file")
})
}),
)
describe(
"finds scaffold.config.cjs",
withMock({ "scaffold.config.cjs": "module.exports = {}" }, () => {
test("finds .cjs config file", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.config.cjs")
})
}),
)
describe(
"finds scaffold.config.json",
withMock({ "scaffold.config.json": "{}" }, () => {
test("finds .json config file", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.config.json")
})
}),
)
describe(
"finds scaffold.mjs",
withMock({ "scaffold.mjs": "export default {}" }, () => {
test("finds scaffold.mjs", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.mjs")
})
}),
)
describe(
"finds scaffold.cjs",
withMock({ "scaffold.cjs": "module.exports = {}" }, () => {
test("finds scaffold.cjs", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.cjs")
})
}),
)
describe(
"finds scaffold.json",
withMock({ "scaffold.json": "{}" }, () => {
test("finds scaffold.json", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.json")
})
}),
)
describe(
"finds .scaffold.js",
withMock({ ".scaffold.js": "module.exports = {}" }, () => {
test("finds dotfile config", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual(".scaffold.js")
})
}),
)
describe(
"finds .scaffold.json",
withMock({ ".scaffold.json": "{}" }, () => {
test("finds dotfile json config", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual(".scaffold.json")
})
}),
)
describe(
"prefers scaffold.config over .scaffold",
withMock(
{
"scaffold.config.js": "module.exports = {}",
".scaffold.js": "module.exports = {}",
},
() => {
test("prefers scaffold.config.js over .scaffold.js", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.config.js")
})
},
),
)
describe(
"prefers scaffold over .scaffold",
withMock(
{
"scaffold.js": "module.exports = {}",
".scaffold.js": "module.exports = {}",
},
() => {
test("prefers scaffold.js over .scaffold.js", async () => {
const result = await findConfigFile(process.cwd())
expect(result).toEqual("scaffold.js")
})
},
),
)
})
})

456
tests/file.test.ts Normal file
View File

@@ -0,0 +1,456 @@
import { describe, test, expect, beforeEach, afterEach } from "vitest"
import mockFs from "mock-fs"
import FileSystem from "mock-fs/lib/filesystem"
import { Console } from "console"
import path from "node:path"
import os from "node:os"
import {
removeGlob,
makeRelativePath,
getBasePath,
getOutputDir,
getUniqueTmpPath,
pathExists,
createDirIfNotExists,
getTemplateGlobInfo,
getTemplateFileInfo,
copyFileTransformed,
handleTemplateFile,
getFileList,
} from "../src/file"
import { ScaffoldConfig, LogLevel } from "../src/types"
import { registerHelpers } from "../src/parser"
import { readFileSync } from "fs"
function withMock(fileStruct: FileSystem.DirectoryItems, testFn: () => void): () => void {
return () => {
beforeEach(() => {
console = new Console(process.stdout, process.stderr)
mockFs(fileStruct)
})
testFn()
afterEach(() => {
mockFs.restore()
})
}
}
const baseConfig: ScaffoldConfig = {
name: "test_app",
output: "output",
templates: ["input"],
logLevel: LogLevel.none,
data: { name: "test_app" },
tmpDir: ".",
}
describe("file utilities", () => {
describe("removeGlob", () => {
test("removes single wildcard", () => {
expect(removeGlob("input/*")).toEqual(path.normalize("input/"))
})
test("removes double wildcard", () => {
expect(removeGlob("input/**/*")).toEqual(path.normalize("input///"))
})
test("returns path unchanged when no glob", () => {
expect(removeGlob("input/file.txt")).toEqual(path.normalize("input/file.txt"))
})
test("removes wildcards from nested path", () => {
expect(removeGlob("a/b/*/c/**")).toEqual(path.normalize("a/b//c//"))
})
test("handles empty string", () => {
expect(removeGlob("")).toEqual(".")
})
})
describe("makeRelativePath", () => {
test("removes leading separator", () => {
expect(makeRelativePath(path.sep + "some/path")).toEqual("some/path")
})
test("returns path unchanged if no leading separator", () => {
expect(makeRelativePath("some/path")).toEqual("some/path")
})
test("handles empty string", () => {
expect(makeRelativePath("")).toEqual("")
})
test("removes only the first separator", () => {
expect(makeRelativePath(path.sep + "a" + path.sep + "b")).toEqual("a" + path.sep + "b")
})
})
describe("getBasePath", () => {
test("resolves relative path against cwd", () => {
const result = getBasePath("some/path")
expect(result).toEqual("some/path")
})
test("handles empty string", () => {
const result = getBasePath("")
expect(result).toEqual("")
})
test("handles current directory", () => {
const result = getBasePath(".")
expect(result).toEqual("")
})
})
describe("getOutputDir", () => {
test("returns output path without subdir", () => {
const config: ScaffoldConfig = { ...baseConfig, subdir: false }
registerHelpers(config)
const result = getOutputDir(config, "output", "")
expect(result).toEqual(path.resolve(process.cwd(), "output"))
})
test("returns output path with subdir", () => {
const config: ScaffoldConfig = { ...baseConfig, subdir: true }
registerHelpers(config)
const result = getOutputDir(config, "output", "")
expect(result).toEqual(path.resolve(process.cwd(), "output", "test_app"))
})
test("applies subdirHelper to subdir name", () => {
const config: ScaffoldConfig = {
...baseConfig,
subdir: true,
subdirHelper: "pascalCase",
}
registerHelpers(config)
const result = getOutputDir(config, "output", "")
expect(result).toEqual(path.resolve(process.cwd(), "output", "TestApp"))
})
test("includes basePath in output", () => {
const config: ScaffoldConfig = { ...baseConfig, subdir: false }
registerHelpers(config)
const result = getOutputDir(config, "output", "nested/dir")
expect(result).toEqual(path.resolve(process.cwd(), "output", "nested/dir"))
})
test("combines output, basePath, and subdir", () => {
const config: ScaffoldConfig = { ...baseConfig, subdir: true }
registerHelpers(config)
const result = getOutputDir(config, "output", "nested")
expect(result).toEqual(path.resolve(process.cwd(), "output", "nested", "test_app"))
})
})
describe("getUniqueTmpPath", () => {
test("returns a path in os temp directory", () => {
const result = getUniqueTmpPath()
expect(result.startsWith(os.tmpdir())).toBe(true)
})
test("includes scaffold-config prefix", () => {
const result = getUniqueTmpPath()
expect(path.basename(result)).toMatch(/^scaffold-config-/)
})
test("generates unique paths", () => {
const a = getUniqueTmpPath()
const b = getUniqueTmpPath()
expect(a).not.toEqual(b)
})
})
describe(
"pathExists",
withMock(
{
"existing-file.txt": "content",
"existing-dir": {},
},
() => {
test("returns true for existing file", async () => {
expect(await pathExists("existing-file.txt")).toBe(true)
})
test("returns true for existing directory", async () => {
expect(await pathExists("existing-dir")).toBe(true)
})
test("returns false for non-existing path", async () => {
expect(await pathExists("non-existing")).toBe(false)
})
},
),
)
describe(
"createDirIfNotExists",
withMock({}, () => {
test("creates directory", async () => {
await createDirIfNotExists("new-dir", { logLevel: LogLevel.none, dryRun: false })
expect(await pathExists("new-dir")).toBe(true)
})
test("creates nested directories recursively", async () => {
await createDirIfNotExists("a/b/c", { logLevel: LogLevel.none, dryRun: false })
expect(await pathExists("a/b/c")).toBe(true)
})
test("does not create directory in dry run mode", async () => {
await createDirIfNotExists("dry-dir", { logLevel: LogLevel.none, dryRun: true })
expect(await pathExists("dry-dir")).toBe(false)
})
test("does not throw if directory already exists", async () => {
await createDirIfNotExists("existing", { logLevel: LogLevel.none, dryRun: false })
await expect(
createDirIfNotExists("existing", { logLevel: LogLevel.none, dryRun: false }),
).resolves.toBeUndefined()
})
}),
)
describe(
"getTemplateGlobInfo",
withMock(
{
"template-dir": {
"file1.txt": "content1",
"file2.txt": "content2",
},
"single-file.txt": "content",
},
() => {
test("detects directory template", async () => {
const result = await getTemplateGlobInfo(baseConfig, "template-dir")
expect(result.isDirOrGlob).toBe(true)
expect(result.isGlob).toBe(false)
expect(result.template).toEqual(path.join("template-dir", "**", "*"))
})
test("detects glob template", async () => {
const result = await getTemplateGlobInfo(baseConfig, "template-dir/**/*.txt")
expect(result.isDirOrGlob).toBe(true)
expect(result.isGlob).toBe(true)
})
test("preserves non-glob single file", async () => {
const result = await getTemplateGlobInfo(baseConfig, "single-file.txt")
expect(result.isDirOrGlob).toBe(false)
expect(result.isGlob).toBe(false)
expect(result.template).toEqual("single-file.txt")
})
test("stores original template", async () => {
const result = await getTemplateGlobInfo(baseConfig, "template-dir")
expect(result.origTemplate).toEqual("template-dir")
})
},
),
)
describe(
"getFileList",
withMock(
{
templates: {
"file1.txt": "content1",
"file2.js": "content2",
".hidden": "hidden content",
nested: {
"file3.txt": "content3",
},
},
},
() => {
test("lists all files with glob", async () => {
const files = await getFileList(baseConfig, ["templates/**/*"])
expect(files.length).toBe(4)
})
test("includes dotfiles", async () => {
const files = await getFileList(baseConfig, ["templates/**/*"])
expect(files.some((f) => f.includes(".hidden"))).toBe(true)
})
test("filters by extension", async () => {
const files = await getFileList(baseConfig, ["templates/**/*.txt"])
expect(files.length).toBe(2)
expect(files.every((f) => f.endsWith(".txt"))).toBe(true)
})
test("supports exclusion patterns", async () => {
const files = await getFileList(baseConfig, ["templates/**/*.txt"])
expect(files.some((f) => f.includes(".hidden"))).toBe(false)
expect(files.every((f) => f.endsWith(".txt"))).toBe(true)
})
},
),
)
describe(
"getTemplateFileInfo",
withMock(
{
input: {
"{{name}}.txt": "Hello {{name}}",
},
output: {},
},
() => {
test("calculates correct output path", async () => {
const config: ScaffoldConfig = { ...baseConfig, tmpDir: "." }
registerHelpers(config)
const info = await getTemplateFileInfo(config, {
templatePath: "input/{{name}}.txt",
basePath: "",
})
expect(info.inputPath).toEqual(path.resolve(process.cwd(), "input/{{name}}.txt"))
expect(info.outputPath).toContain("test_app.txt")
expect(info.exists).toBe(false)
})
test("detects existing output file", async () => {
mockFs.restore()
mockFs({
input: { "{{name}}.txt": "Hello {{name}}" },
output: { "test_app.txt": "existing" },
})
const config: ScaffoldConfig = { ...baseConfig, tmpDir: "." }
registerHelpers(config)
const info = await getTemplateFileInfo(config, {
templatePath: "input/{{name}}.txt",
basePath: "",
})
expect(info.exists).toBe(true)
})
},
),
)
describe(
"copyFileTransformed",
withMock(
{
"input.txt": "Hello {{name}}",
output: {},
},
() => {
test("writes transformed content", async () => {
const config: ScaffoldConfig = { ...baseConfig }
registerHelpers(config)
await createDirIfNotExists("output", { logLevel: LogLevel.none, dryRun: false })
await copyFileTransformed(config, {
exists: false,
overwrite: false,
outputPath: "output/result.txt",
inputPath: "input.txt",
})
const content = readFileSync("output/result.txt").toString()
expect(content).toEqual("Hello test_app")
})
test("does not write in dry run mode", async () => {
const config: ScaffoldConfig = { ...baseConfig, dryRun: true }
registerHelpers(config)
await copyFileTransformed(config, {
exists: false,
overwrite: false,
outputPath: "output/result.txt",
inputPath: "input.txt",
})
expect(await pathExists("output/result.txt")).toBe(false)
})
test("skips existing file without overwrite", async () => {
mockFs.restore()
mockFs({
"input.txt": "Hello {{name}}",
output: { "result.txt": "original" },
})
const config: ScaffoldConfig = { ...baseConfig }
registerHelpers(config)
await copyFileTransformed(config, {
exists: true,
overwrite: false,
outputPath: "output/result.txt",
inputPath: "input.txt",
})
const content = readFileSync("output/result.txt").toString()
expect(content).toEqual("original")
})
test("overwrites existing file with overwrite flag", async () => {
mockFs.restore()
mockFs({
"input.txt": "Hello {{name}}",
output: { "result.txt": "original" },
})
const config: ScaffoldConfig = { ...baseConfig }
registerHelpers(config)
await copyFileTransformed(config, {
exists: true,
overwrite: true,
outputPath: "output/result.txt",
inputPath: "input.txt",
})
const content = readFileSync("output/result.txt").toString()
expect(content).toEqual("Hello test_app")
})
test("calls beforeWrite callback", async () => {
const config: ScaffoldConfig = {
...baseConfig,
beforeWrite: (content) => content.toString().toUpperCase(),
}
registerHelpers(config)
await createDirIfNotExists("output", { logLevel: LogLevel.none, dryRun: false })
await copyFileTransformed(config, {
exists: false,
overwrite: false,
outputPath: "output/result.txt",
inputPath: "input.txt",
})
const content = readFileSync("output/result.txt").toString()
expect(content).toEqual("HELLO TEST_APP")
})
},
),
)
describe(
"handleTemplateFile",
withMock(
{
input: {
"{{name}}.txt": "Content for {{name}}",
},
output: {},
},
() => {
test("processes template file end to end", async () => {
const config: ScaffoldConfig = { ...baseConfig, tmpDir: "." }
registerHelpers(config)
await handleTemplateFile(config, {
templatePath: "input/{{name}}.txt",
basePath: "",
})
const content = readFileSync(path.join("output", "test_app.txt")).toString()
expect(content).toEqual("Content for test_app")
})
test("throws for non-existing template", async () => {
const config: ScaffoldConfig = { ...baseConfig, tmpDir: "." }
registerHelpers(config)
await expect(
handleTemplateFile(config, {
templatePath: "non-existing.txt",
basePath: "",
}),
).rejects.toThrow()
})
},
),
)
})

122
tests/ignore.test.ts Normal file
View File

@@ -0,0 +1,122 @@
import { describe, test, expect, beforeEach, afterEach } from "vitest"
import mockFs from "mock-fs"
import { Console } from "console"
import path from "node:path"
import { parseIgnoreFile, loadIgnorePatterns, filterIgnoredFiles } from "../src/ignore"
describe("ignore", () => {
describe("parseIgnoreFile", () => {
test("parses patterns", () => {
const result = parseIgnoreFile("node_modules\n*.log\n")
expect(result).toEqual(["node_modules", "*.log"])
})
test("skips comments", () => {
const result = parseIgnoreFile("# comment\nfoo\n# another\nbar")
expect(result).toEqual(["foo", "bar"])
})
test("skips empty lines", () => {
const result = parseIgnoreFile("foo\n\n\nbar\n")
expect(result).toEqual(["foo", "bar"])
})
test("trims whitespace", () => {
const result = parseIgnoreFile(" foo \n bar ")
expect(result).toEqual(["foo", "bar"])
})
test("handles empty file", () => {
expect(parseIgnoreFile("")).toEqual([])
})
test("handles comments-only file", () => {
expect(parseIgnoreFile("# just comments\n# nothing here")).toEqual([])
})
})
describe("filterIgnoredFiles", () => {
test("filters files matching patterns", () => {
const files = ["templates/file.txt", "templates/debug.log", "templates/other.js"]
const result = filterIgnoredFiles(files, ["*.log"], "templates")
expect(result).toEqual(["templates/file.txt", "templates/other.js"])
})
test("always excludes .scaffoldignore", () => {
const files = ["templates/file.txt", "templates/.scaffoldignore"]
const result = filterIgnoredFiles(files, [], "templates")
expect(result).toEqual(["templates/file.txt"])
})
test("matches by relative path", () => {
const files = ["templates/src/index.ts", "templates/dist/index.js", "templates/src/utils.ts"]
const result = filterIgnoredFiles(files, ["dist/**"], "templates")
expect(result).toEqual(["templates/src/index.ts", "templates/src/utils.ts"])
})
test("matches by basename", () => {
const files = ["templates/README.md", "templates/nested/README.md", "templates/file.txt"]
const result = filterIgnoredFiles(files, ["README.md"], "templates")
expect(result).toEqual(["templates/file.txt"])
})
test("handles multiple patterns", () => {
const files = ["tpl/a.txt", "tpl/b.log", "tpl/c.tmp", "tpl/d.ts"]
const result = filterIgnoredFiles(files, ["*.log", "*.tmp"], "tpl")
expect(result).toEqual(["tpl/a.txt", "tpl/d.ts"])
})
test("returns all files when no patterns", () => {
const files = ["tpl/a.txt", "tpl/b.txt"]
const result = filterIgnoredFiles(files, [], "tpl")
expect(result).toEqual(files)
})
test("handles glob patterns with directories", () => {
const files = [
path.join("tpl", "src", "index.ts"),
path.join("tpl", "node_modules", "pkg", "index.js"),
path.join("tpl", "file.txt"),
]
const result = filterIgnoredFiles(files, ["node_modules/**"], "tpl")
expect(result).toEqual([path.join("tpl", "src", "index.ts"), path.join("tpl", "file.txt")])
})
})
describe("loadIgnorePatterns", () => {
beforeEach(() => {
console = new Console(process.stdout, process.stderr)
})
afterEach(() => {
mockFs.restore()
})
test("returns empty array when no .scaffoldignore exists", async () => {
mockFs({ templates: { "file.txt": "content" } })
const result = await loadIgnorePatterns("templates")
expect(result).toEqual([])
})
test("reads and parses .scaffoldignore", async () => {
mockFs({
templates: {
".scaffoldignore": "*.log\nnode_modules\n",
"file.txt": "content",
},
})
const result = await loadIgnorePatterns("templates")
expect(result).toEqual(["*.log", "node_modules"])
})
test("ignores comments in .scaffoldignore", async () => {
mockFs({
templates: {
".scaffoldignore": "# This is a comment\n*.tmp\n",
},
})
const result = await loadIgnorePatterns("templates")
expect(result).toEqual(["*.tmp"])
})
})
})

115
tests/init.test.ts Normal file
View File

@@ -0,0 +1,115 @@
import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"
import mockFs from "mock-fs"
import { Console } from "console"
import { readFileSync, existsSync } from "fs"
import path from "path"
vi.mock("@inquirer/input", () => ({
default: vi.fn(),
}))
vi.mock("@inquirer/select", () => ({
default: vi.fn(),
}))
import selectMock from "@inquirer/select"
import { initScaffold } from "../src/init"
describe("init", () => {
beforeEach(() => {
console = new Console(process.stdout, process.stderr)
vi.clearAllMocks()
mockFs({})
})
afterEach(() => {
mockFs.restore()
})
test("creates js config and example template", async () => {
await initScaffold({ format: "js", dir: process.cwd() })
expect(existsSync("scaffold.config.js")).toBe(true)
const config = readFileSync("scaffold.config.js", "utf-8")
expect(config).toContain("module.exports")
expect(config).toContain("templates/default")
expect(existsSync(path.join("templates", "default", "{{name}}.md"))).toBe(true)
const template = readFileSync(path.join("templates", "default", "{{name}}.md"), "utf-8")
expect(template).toContain("{{ name }}")
})
test("creates mjs config", async () => {
await initScaffold({ format: "mjs", dir: process.cwd() })
expect(existsSync("scaffold.config.mjs")).toBe(true)
const config = readFileSync("scaffold.config.mjs", "utf-8")
expect(config).toContain("export default")
})
test("creates json config", async () => {
await initScaffold({ format: "json", dir: process.cwd() })
expect(existsSync("scaffold.config.json")).toBe(true)
const config = readFileSync("scaffold.config.json", "utf-8")
const parsed = JSON.parse(config)
expect(parsed.default).toBeDefined()
expect(parsed.default.templates).toEqual(["templates/default"])
})
test("does not overwrite existing config", async () => {
mockFs.restore()
mockFs({
"scaffold.config.js": "// existing config",
})
await initScaffold({ format: "js", dir: process.cwd() })
const config = readFileSync("scaffold.config.js", "utf-8")
expect(config).toBe("// existing config")
})
test("does not overwrite existing template dir", async () => {
mockFs.restore()
mockFs({
templates: {
default: {
"existing.md": "# Existing",
},
},
})
await initScaffold({ format: "js", dir: process.cwd() })
expect(existsSync(path.join("templates", "default", "existing.md"))).toBe(true)
expect(existsSync(path.join("templates", "default", "{{name}}.md"))).toBe(false)
})
test("skips example template when createExample is false", async () => {
await initScaffold({ format: "js", dir: process.cwd(), createExample: false })
expect(existsSync("scaffold.config.js")).toBe(true)
expect(existsSync("templates")).toBe(false)
})
test("prompts for format when not provided", async () => {
vi.mocked(selectMock).mockResolvedValue("js")
await initScaffold({ dir: process.cwd() })
expect(selectMock).toHaveBeenCalledOnce()
expect(existsSync("scaffold.config.js")).toBe(true)
})
test("creates config in custom directory", async () => {
mockFs.restore()
mockFs({
"my-project": {},
})
await initScaffold({ format: "js", dir: path.resolve("my-project") })
expect(existsSync(path.join("my-project", "scaffold.config.js"))).toBe(true)
expect(existsSync(path.join("my-project", "templates", "default", "{{name}}.md"))).toBe(true)
})
})

175
tests/logger.test.ts Normal file
View File

@@ -0,0 +1,175 @@
import { describe, test, expect, beforeEach, afterEach, vi, type MockInstance } from "vitest"
import { log, logInitStep, logInputFile } from "../src/logger"
import { LogLevel, ScaffoldConfig } from "../src/types"
describe("logger", () => {
let consoleSpy: {
log: MockInstance
warn: MockInstance
error: MockInstance
}
beforeEach(() => {
consoleSpy = {
log: vi.spyOn(console, "log").mockImplementation(() => void 0),
warn: vi.spyOn(console, "warn").mockImplementation(() => void 0),
error: vi.spyOn(console, "error").mockImplementation(() => void 0),
}
})
afterEach(() => {
consoleSpy.log.mockRestore()
consoleSpy.warn.mockRestore()
consoleSpy.error.mockRestore()
})
describe("log", () => {
test("does not log when logLevel is none", () => {
log({ logLevel: LogLevel.none }, LogLevel.info, "test")
expect(consoleSpy.log).not.toHaveBeenCalled()
})
test("logs info messages with console.log", () => {
log({ logLevel: LogLevel.info }, LogLevel.info, "test message")
expect(consoleSpy.log).toHaveBeenCalled()
})
test("logs warning messages with console.warn", () => {
log({ logLevel: LogLevel.warning }, LogLevel.warning, "warning message")
expect(consoleSpy.warn).toHaveBeenCalled()
})
test("logs error messages with console.error", () => {
log({ logLevel: LogLevel.error }, LogLevel.error, "error message")
expect(consoleSpy.error).toHaveBeenCalled()
})
test("filters out messages below configured level", () => {
log({ logLevel: LogLevel.warning }, LogLevel.info, "should be filtered")
expect(consoleSpy.log).not.toHaveBeenCalled()
})
test("filters out debug messages when level is info", () => {
log({ logLevel: LogLevel.info }, LogLevel.debug, "debug message")
expect(consoleSpy.log).not.toHaveBeenCalled()
})
test("shows debug messages when level is debug", () => {
log({ logLevel: LogLevel.debug }, LogLevel.debug, "debug message")
expect(consoleSpy.log).toHaveBeenCalled()
})
test("shows all levels when configured as debug", () => {
log({ logLevel: LogLevel.debug }, LogLevel.debug, "d")
log({ logLevel: LogLevel.debug }, LogLevel.info, "i")
log({ logLevel: LogLevel.debug }, LogLevel.warning, "w")
log({ logLevel: LogLevel.debug }, LogLevel.error, "e")
expect(consoleSpy.log).toHaveBeenCalledTimes(2) // debug + info
expect(consoleSpy.warn).toHaveBeenCalledTimes(1)
expect(consoleSpy.error).toHaveBeenCalledTimes(1)
})
test("handles Error objects", () => {
log({ logLevel: LogLevel.error }, LogLevel.error, new Error("test error"))
expect(consoleSpy.error).toHaveBeenCalled()
})
test("handles objects with util.inspect", () => {
log({ logLevel: LogLevel.info }, LogLevel.info, { key: "value" })
expect(consoleSpy.log).toHaveBeenCalled()
})
test("handles multiple arguments", () => {
log({ logLevel: LogLevel.info }, LogLevel.info, "a", "b", "c")
expect(consoleSpy.log).toHaveBeenCalled()
// First call, should have 3 arguments
expect(consoleSpy.log.mock.calls[0].length).toBe(3)
})
test("defaults to info when logLevel is undefined", () => {
log({ logLevel: undefined as unknown as LogLevel }, LogLevel.info, "test")
expect(consoleSpy.log).toHaveBeenCalled()
})
test("error level shows when logLevel is info", () => {
log({ logLevel: LogLevel.info }, LogLevel.error, "error")
expect(consoleSpy.error).toHaveBeenCalled()
})
test("warning level shows when logLevel is info", () => {
log({ logLevel: LogLevel.info }, LogLevel.warning, "warning")
expect(consoleSpy.warn).toHaveBeenCalled()
})
})
describe("logInitStep", () => {
test("logs config at debug level", () => {
const config: ScaffoldConfig = {
name: "test",
output: "output",
templates: ["input"],
logLevel: LogLevel.debug,
data: { name: "test" },
}
logInitStep(config)
expect(consoleSpy.log).toHaveBeenCalled()
})
test("does not log at info level (debug only)", () => {
const config: ScaffoldConfig = {
name: "test",
output: "output",
templates: ["input"],
logLevel: LogLevel.info,
data: { name: "test" },
}
logInitStep(config)
// Full config is debug-only, nothing logged at info
expect(consoleSpy.log).not.toHaveBeenCalled()
})
})
describe("logInputFile", () => {
test("logs file info at debug level", () => {
const config: ScaffoldConfig = {
name: "test",
output: "output",
templates: ["input"],
logLevel: LogLevel.debug,
data: { name: "test" },
}
logInputFile(config, {
originalTemplate: "input",
relativePath: ".",
parsedTemplate: "input/**/*",
inputFilePath: "input/file.txt",
nonGlobTemplate: "input",
basePath: "",
isDirOrGlob: true,
isGlob: false,
})
expect(consoleSpy.log).toHaveBeenCalled()
})
test("does not log at info level", () => {
const config: ScaffoldConfig = {
name: "test",
output: "output",
templates: ["input"],
logLevel: LogLevel.info,
data: { name: "test" },
}
logInputFile(config, {
originalTemplate: "input",
relativePath: ".",
parsedTemplate: "input/**/*",
inputFilePath: "input/file.txt",
nonGlobTemplate: "input",
basePath: "",
isDirOrGlob: true,
isGlob: false,
})
expect(consoleSpy.log).not.toHaveBeenCalled()
})
})
})

Some files were not shown because too many files have changed in this diff Show More