Compare commits

..

96 Commits

Author SHA1 Message Date
Chen Asraf
5af283292e update README.md, default output fix 2021-12-06 00:47:10 +02:00
Chen Asraf
dcf0d04b68 fix cmd 2021-12-06 00:27:57 +02:00
Chen Asraf
41076366a6 update workflows [skip publish] 2021-12-06 00:16:12 +02:00
Chen Asraf
f35baebf3a update alpha workflow 2021-12-06 00:09:04 +02:00
Chen Asraf
79f9b33631 helpers fix 2021-12-06 00:08:01 +02:00
Chen Asraf
027f5576ba fix copyright 2021-12-06 00:03:51 +02:00
Chen Asraf
5d630e7e62 added custom helpers 2021-12-06 00:00:18 +02:00
Chen Asraf
7073add836 update deps + add MIGRATION.md 2021-12-05 23:28:23 +02:00
Chen Asraf
561477f07b update deps + update cmd requirements 2021-12-05 00:49:10 +02:00
Chen Asraf
bfcc59c20a update readme [skip ci] 2021-12-03 01:28:40 +02:00
Chen Asraf
ba5332d550 update readme [skip ci] 2021-12-03 01:11:18 +02:00
Chen Asraf
f9bc0419f3 update docs [skip publish] 2021-12-03 01:02:14 +02:00
Chen Asraf
381c55835f fix basename in some cases 2021-12-03 00:39:03 +02:00
Chen Asraf
efdb7dc00b run tests on ci [skip publish] 2021-12-02 11:48:57 +02:00
Chen Asraf
385829aa27 fix errors, fix nested output 2021-12-02 11:46:08 +02:00
Chen Asraf
33c357bccc remove types from package.json 2021-11-28 15:50:27 +02:00
Chen Asraf
b74b781a5b fix yarn.lock 2021-11-28 15:45:51 +02:00
Chen Asraf
57410c8d74 fix log level 0 2021-11-28 15:34:59 +02:00
Chen Asraf
f81cfd8ae1 add export for cmd_util 2021-11-28 15:28:48 +02:00
Chen Asraf
414494734d bump version number 2021-11-28 14:24:01 +02:00
Chen Asraf
89b588f64e update workflows [skip publish] 2021-11-28 14:09:25 +02:00
Chen Asraf
cf22e2e62f fixes + add log level [skip publish] 2021-11-28 14:07:25 +02:00
Chen Asraf
246c139061 fix readme [skip publish] 2021-11-18 15:25:43 +02:00
Chen Asraf
711d8f0333 try fix release upload 2021-11-18 14:22:23 +02:00
Chen Asraf
8b22e96329 try new release version 2021-11-18 14:19:50 +02:00
Chen Asraf
53c0842ab8 fixed release tarball file location 2021-11-18 14:18:18 +02:00
Chen Asraf
48631325c1 fixed release tarball file location 2021-11-18 14:16:58 +02:00
Chen Asraf
045ad0118a fix build output files 2021-11-18 14:09:21 +02:00
Chen Asraf
b4dca7a954 add build step 2021-11-18 13:57:57 +02:00
Chen Asraf
7c42808f63 try to fix workflow 2021-11-18 13:56:24 +02:00
Chen Asraf
fd42013e8b publish: debug mode off, try to fix workflow 2021-11-18 13:55:30 +02:00
Chen Asraf
961a72fcdc publish: debug mode on 2021-11-18 13:51:29 +02:00
Chen Asraf
d6d99cfdf2 try fix workflow 2021-11-18 13:45:37 +02:00
Chen Asraf
ea4ecabe02 Merge pull request #12 from chenasraf/v1.0
v1.0
2021-11-18 13:44:09 +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
Chen Asraf
0a2d7c08f3 v0.4.2 2019-02-25 18:17:15 +02:00
Chen Asraf
a92c471243 bugfixes 2019-02-25 18:17:03 +02:00
Chen Asraf
07b1c4b1f0 mapfile 2019-02-25 18:01:10 +02:00
Chen Asraf
ec91fbf639 v0.4.1 2019-02-25 18:00:39 +02:00
Chen Asraf
85aa9f953b Bugfix: dotfiles 2019-02-25 18:00:23 +02:00
Chen Asraf
d6195c6c1d added 'createSubFolder' option, cleaned up CMD file 2019-02-25 17:49:03 +02:00
Chen Asraf
b14e3d2d76 Update README.md 2018-01-15 02:25:29 +02:00
Chen Asraf
fa2ddca57e v0.3.1 2018-01-15 02:23:14 +02:00
Chen Asraf
686b0bf227 Update README.md 2018-01-15 02:23:05 +02:00
40 changed files with 4086 additions and 4094 deletions

5
.editorconfig Normal file
View File

@@ -0,0 +1,5 @@
[*]
tab_width = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

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

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: casraf
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: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

52
.github/workflows/alpha.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
name: Alpha Releases
on:
push:
branches: [alpha]
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: "12.x"
- run: yarn install --frozen-lockfile
- run: yarn test
- run: yarn build
- run: cd ./dist && yarn pack --filename=../package.tgz
if: "!contains(github.event.head_commit.message, '[skip publish]')"
- uses: Klemensas/action-autotag@stable
if: "!contains(github.event.head_commit.message, '[skip publish]')"
id: update_tag
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
tag_prefix: "v"
- name: Publish on NPM
uses: JS-DevTools/npm-publish@v1
if: "!contains(github.event.head_commit.message, '[skip publish]')"
with:
package: ./dist/package.json
token: "${{ secrets.NPM_TOKEN }}"
- name: Create Release
if: steps.update_tag.outputs.tagname && !contains(github.event.head_commit.message, '[skip publish]')
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prerelease: true
tag_name: ${{ steps.update_tag.outputs.tagname }}
release_name: Release ${{ steps.update_tag.outputs.tagname }}
- name: Upload Release Asset
if: steps.update_tag.outputs.tagname && !contains(github.event.head_commit.message, '[skip publish]')
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./package.tgz
asset_name: simple-scaffold ${{ steps.update_tag.outputs.tagname }}.tgz
asset_content_type: application/tgz

50
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,50 @@
name: Releases
on:
push:
branches: [master]
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: "12.x"
- run: yarn install --frozen-lockfile
- run: yarn test
- run: yarn build
- run: cd ./dist && yarn pack --filename=../package.tgz
if: "!contains(github.event.head_commit.message, '[skip publish]')"
- uses: Klemensas/action-autotag@stable
if: "!contains(github.event.head_commit.message, '[skip publish]')"
id: update_tag
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
tag_prefix: "v"
- name: Publish on NPM
uses: JS-DevTools/npm-publish@v1
with:
package: ./dist/package.json
token: "${{ secrets.NPM_TOKEN }}"
- name: Create Release
if: steps.update_tag.outputs.tagname && !contains(github.event.head_commit.message, '[skip publish]')
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.update_tag.outputs.tagname }}
release_name: Release ${{ steps.update_tag.outputs.tagname }}
- name: Upload Release Asset
if: steps.update_tag.outputs.tagname && !contains(github.event.head_commit.message, '[skip publish]')
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./package.tgz
asset_name: simple-scaffold ${{ steps.update_tag.outputs.tagname }}.tgz
asset_content_type: application/tgz

17
.github/workflows/pull_requests.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
name: Pull Requests
on:
pull_request:
branches: [master, alpha, beta]
jobs:
build:
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: "12.x"
- run: yarn install --frozen-lockfile
- run: yarn build
- run: yarn test

5
.gitignore vendored
View File

@@ -57,4 +57,7 @@ typings/
# dotenv environment variables file
.env
examples/test-output
examples/test-output/**/*
dist/
.DS_Store
tmp/

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"semi": false,
"printWidth": 120,
"tabWidth": 2
}

11
.vscode/settings.json vendored
View File

@@ -1,3 +1,12 @@
{
"typescript.tsdk": "./node_modules/typescript/lib"
"typescript.tsdk": "./node_modules/typescript/lib",
"npm.packageManager": "yarn",
"cSpell.words": [
"massarg",
"nodir",
"nobrace",
"noext",
"nocomment",
"nonegate"
]
}

56
.vscode/tasks.json vendored
View File

@@ -1,7 +1,53 @@
{
"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": [],
},
{
"script": "start",
"label": "start",
"type": "npm",
"problemMatcher": [],
},
{
"script": "test",
"label": "test",
"type": "npm",
"problemMatcher": [],
},
{
"command": "yarn test --watchAll",
"label": "yarn 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": [],
},
],
}

20
MIGRATION.md Normal file
View File

@@ -0,0 +1,20 @@
# Migrating from 0.x to 1.0
In Simple Scaffold v1.0, the entire codebase was overhauled, yet usage remains mostly the same
between versions. With these notable exceptions:
- Some of the argument names have changed
- Template syntax has been improved
## Argument changes
- `locals` has been renamed to `data`. The appropriate command line args have been updated as
well to `--data` | `-d`.
## Template syntax changes
Simple Scaffold still uses Handlebars.js to handle template content and file names. However, helpers
have been added to remove the need for you to pre-process the template data on simple use-cases such
as case type manipulation (converting to camel case, snake case, etc)
See the readme for the full information on how to use these helpers and which are available.

349
README.md
View File

@@ -1,120 +1,285 @@
# simple-scaffolder
Scaffolder allows you to create your structured files based on templates.
# simple-scaffold
Simple Scaffold allows you to create your structured files based on templates.
Simply organize your commonly-created files in their original structure, and replace any variable
values (such as component or app name) inside the paths or contents of the files with tokens to be
populated upon scaffolding.
Then, run Simple Scaffold and it will generate your files for you in the desired structure,
with file names and contents that contain your dynamic information.
It's a simple way to easily create reusable components, common class files to start writing from,
or even entire app structures.
## Install
You can either use it as a command line tool or import into your own code and run from there.
You can install simple-scaffold globally like so:
You can either use it as a command line tool or import into your own code and run from there.
```bash
# npm
npm install -g simple-scaffold
npm install [-g] simple-scaffold
# yarn
yarn global add simple-scaffold
yarn [global] add simple-scaffold
# run without installing
npx simple-scaffold <...args>
```
## Use as a command line tool
### Command line options
```bash
simple-scaffold MyComponent --template scaffolds/component/**/* \
--output src/components \
--locals myProp="propname",myVal=123
### Command Line Options
```plaintext
Usage: simple-scaffold [options]
Create structured files based on templates.
Options:
--help|-h Display help information
--name|-n Name to be passed to the generated files. {{name}} and
{{Name}} inside contents and file names will be replaced
accordingly.
--output|-o Path to output to. If --create-sub-folder is enabled, the
subfolder will be created inside this path.
--templates|-t Template files to use as input. You may provide multiple
files, each of which can be a relative or absolute path, or a glob
pattern for multiple file matching easily. (default: current dir)
--overwrite|-w Enable to override output files, even if they already exist.
(default: false)
--data|-d Add custom data to the templates. By default, only your app
name is included.
--create-sub-folder|-s Create subfolder with the input name (default:
false)
--quiet|-q Suppress output logs (Same as --verbose 0)
(default: false)
--verbose|-v Determine amount of logs to display. The values are: 0
(none) | 1 (debug) | 2 (info) | 3 (warn) | 4 (error). The
provided level will display messages of the same level or higher.
(default: 2)
--dry-run|-dr Don't emit files. This is good for testing your scaffolds and
making sure they don't fail, without having to write actual file
contents or create directories. (default:
false)
```
##### `--template glob [--template glob2 [...]]`
A glob pattern of template files to load.
You can also add this as a script in your `package.json`:
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 may load more than one template list by simple adding more `--template` arguments.
##### `--output path`
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[,key=value[,...]]`
Pass a KV map to the template for parsing.
### Use in Node.js
You can also build the scaffold yourself.
Simply pass a config object to the constructor, and invoke `run()` when you are ready to start.
The config takes the same arguments as 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'),
locals: {
property: 'value',
```json
{
...
"scripts": {
...
"scaffold": "yarn simple-scaffold --templates scaffolds/component/**/* --output src/components --data '{\"myProp\": \"propName\", \"myVal\": \"123\"}'"
}
}).run()
```
## Example Scaffold Input
Scaffolding will replace interpolations in both the file name and contents.
Your context will be pre-populated with
- `{{Name}}`: CapitalizedName of the component
- `{{name}}`: camelCasedName of the component
Any `locals` you add in the config will populate with their names wrapped in `{{` and `}}`.
### Input Directory structure
```
- project
- scaffold
- {{Name}}.css
- src
- components
- ...
```
#### project/scaffold/{{Name}}.css
```css
.{{className}} {
border: 1px solid black;
}
.{{className}}-child {
background: red;
}
```
### Run Example
## 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 Scaffold function when you are ready to start.
The config takes similar arguments to the command line:
```typescript
import Scaffold from "simple-scaffold"
const config = {
name: "component",
templates: [path.join(__dirname, "scaffolds", "component")],
output: path.join(__dirname, "src", "components"),
createSubFolder: true,
locals: {
property: "value",
},
helpers: {
twice: (text) => [text, text].join(" ")
}
}
const scaffold = Scaffold(config)
```
### Additional Node.js options
In addition to all the options available in the command line, there are some JS-specific options
available:
1. When `output` is used in Node directly, it may also be passed a function for each input file to
output into a dynamic path:
```typescript
config.output = (fullPath, baseDir, baseName) => {
console.log({ fullPath, baseDir, baseName })
return path.resolve(baseDir, baseName)
}
```
2. You may add custom `helpers` to your scaffolds. Helpers are simple `(string) => string` functions
that transform your `data` variables into other values. See [Helpers](#helpers) for the list of
default helpers, or add your own to be loaded into the template parser.
## Preparing files
### Template files
Put your template files anywhere, and fill them with tokens for replacement.
### Variable/token replacement
Scaffolding will replace `{{ varName }}` in both the file name and its contents and put the
transformed files in the output directory.
The data available for the template parser is the data you pass to the `data` config option (or
`--data` argument in CLI).
Your `data` will be pre-populated with the following:
- `{{Name}}`: PascalCase of the component name
- `{{name}}`: raw name of the component
> Simple-Scaffold uses [Handlebars.js](https://handlebarsjs.com/) for outputting the file contents,
> see their documentation for more information on syntax.
> Any `data` you add in the config will be available for use with their names wrapped in
> `{{` and `}}`.
#### Helpers
Simple-Scaffold provides some built-in text transformation filters usable by handleBars.
For example, you may use `{{ snakeCase name }}` inside a template file or filename, and it will
replace `My Name` with `my_name` when producing the final value.
Here are the built-in helpers available for use:
| Helper name | Example code | Example output |
| ----------- | ----------------------- | -------------- |
| camelCase | `{{ camelCase name }}` | myName |
| snakeCase | `{{ snakeCase name }}` | my_name |
| startCase | `{{ startCase name }}` | My Name |
| kebabCase | `{{ kebabCase name }}` | my-name |
| hyphenCase | `{{ hyphenCase name }}` | my-name |
| pascalCase | `{{ pascalCase name }}` | MyName |
| upperCase | `{{ upperCase name }}` | MYNAME |
| lowerCase | `{{ lowerCase name }}` | myname |
> These helpers are available for any data property, not exclusive to `name`.
You may also add your own custom helpers using the `helpers` options when using the JS API (rather
than the CLI). The `helpers` option takes an object whose keys are helper names, and values are
the transformation functions. For example, `upperCase` is implemented like so:
```typescript
config.helpers = {
upperCase: (text) => text.toUpperCase(),
}
```
## Examples
### Command Example
```bash
simple-scaffold MyComponent \
--template project/scaffold/**/* \
--output src/components \
--locals 'className=my-component`
-t project/scaffold/**/* \
-o src/components \
-d '{"className": "myClassName"}'
MyComponent
```
## Example Scaffold Output
#### Directory structure
```
### Example Scaffold Input
#### Input Directory structure
```plaintext
- project
- src
- components
- MyComponent
- MyComponent.css
- ...
- scaffold
- {{Name}}.js
- src
- components
- ...
```
#### project/src/components/MyComponent/MyComponent.css
```css
.my-component {
border: 1px solid black;
}
#### Contents of `project/scaffold/{{Name}}.jsx`
.my-component-child {
background: red;
```js
const React = require('react')
module.exports = function {{Name}}(props) {
return (
<div className="{{className}}">{{Name}} Component</div>
)
}
```
### Example Scaffold Output
### Output directory structure
```plaintext
- project
- src
- components
- MyComponent
- MyComponent.js
- ...
```
With `createSubFolder = false`:
```plaintext
- project
- src
- components
- MyComponent.js
- ...
```
#### Contents of `project/scaffold/MyComponent/MyComponent.jsx`
```js
const React = require("react")
module.exports = function MyComponent(props) {
return (
<div className="myClassName">MyComponent Component</div>
)
}
```
## Contributing
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 `yarn install`
3. Run `yarn 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 `yarn dev` for development - it runs TypeScript compile in watch mode, allowing you to make
changes and immediately be able to try them using `yarn cmd`.
- Use `yarn build` to build the output
- Use `yarn test` to run tests
- Use `yarn cmd` to use the CLI feature of Simple Scaffold from within the root directory,
enabling you to test different behaviors. See `yarn cmd -h` for more information.
> This requires an updated build, and does not trigger one itself. Either use `yarn dev` or
> `yarn build` before running this, or use `yarn build-cmd` instead, which triggers a build right
> before running the command with the rest of the given arguments.

3
babel.config.js Normal file
View File

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

86
cmd.ts
View File

@@ -1,86 +0,0 @@
import SimpleScaffold from './scaffold'
import * as fs from 'fs'
import IScaffold from './index'
const args = process.argv.slice(2)
class ScaffoldCmd {
private config: IScaffold.IConfig
constructor() {
this.config = this.getOptionsFromArgs()
}
private getOptionsFromArgs(): IScaffold.IConfig {
let skipNext = false
const options = {} as any
args.forEach((arg, i) => {
if (skipNext) {
skipNext = false
return
}
if (arg.slice(0, 2) == '--') {
skipNext = true
let value: string
if (arg.indexOf('=') >= 0) {
value = arg.split('=').slice(1).join('')
} else if (args.length >= i + 1 && args[i + 1] && args[i + 1].slice(0, 2) !== '--') {
value = args[i + 1]
} else {
value = 'true'
}
const argName = arg.slice(2)
options[argName] = this.getArgValue(argName, value, options)
} else {
if (!options.name) {
options.name = arg
} else {
throw new TypeError(`Invalid argument: ${arg}`)
}
}
})
if (!['name', 'templates'].every(o => options[o] !== undefined)) {
throw new Error(`Config is missing keys: ${JSON.stringify(options)}`)
}
return options
}
private getArgValue(arg: string, value: string, options: IScaffold.IConfig) {
switch (arg) {
case 'templates':
return (options.templates || []).concat([value])
case 'output':
return value
case 'locals':
const split = value.split(',')
const locals = options.locals || {}
for (const item of split) {
const [k, v] = item.split('=')
locals[k] = v
}
return locals
default:
throw TypeError(`arguments invalid for config: arg=\`${arg}\`, value=\`${value}\``)
}
}
public run() {
const config: IScaffold.IConfig = this.config
console.info('Config:', config)
const scf = new SimpleScaffold({
name: config.name,
templates: config.templates,
output: config.output,
locals: config.locals,
}).run()
}
}
new ScaffoldCmd().run()

333
dist/cmd.js vendored
View File

@@ -1,333 +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();
})(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 __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
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(),
};
this.config = Object.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 = Object.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 _i, input_1, checkPath, files, idx, cleanCheckPath, _a, files_1, file;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_i = 0, input_1 = input;
_b.label = 1;
case 1:
if (!(_i < input_1.length)) return [3 /*break*/, 6];
checkPath = input_1[_i];
files = glob.sync(checkPath).map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
idx = checkPath.indexOf('*');
cleanCheckPath = checkPath;
if (idx >= 0) {
cleanCheckPath = checkPath.slice(0, idx - 1);
}
_a = 0, files_1 = files;
_b.label = 2;
case 2:
if (!(_a < files_1.length)) return [3 /*break*/, 5];
file = files_1[_a];
return [4 /*yield*/, { base: cleanCheckPath, file: file }];
case 3:
_b.sent();
_b.label = 4;
case 4:
_a++;
return [3 /*break*/, 2];
case 5:
_i++;
return [3 /*break*/, 1];
case 6: return [2 /*return*/];
}
});
};
SimpleScaffold.prototype.getFileContents = function (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.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.writeFileSync(filePath, fileContents, { encoding: 'utf-8' });
};
SimpleScaffold.prototype.run = function () {
console.log("Generating scaffold: " + this.config.name + "...");
var templates = this.fileList(this.config.templates);
var fileConf, count = 0;
while (fileConf = templates.next().value) {
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";
Object.defineProperty(exports, "__esModule", { value: true });
var scaffold_1 = __webpack_require__(1);
var args = process.argv.slice(2);
var ScaffoldCmd = /** @class */ (function () {
function ScaffoldCmd() {
this.config = this.getOptionsFromArgs();
}
ScaffoldCmd.prototype.getOptionsFromArgs = function () {
var _this = this;
var skipNext = false;
var options = {};
args.forEach(function (arg, i) {
if (skipNext) {
skipNext = false;
return;
}
if (arg.slice(0, 2) == '--') {
skipNext = true;
var value = void 0;
if (arg.indexOf('=') >= 0) {
value = arg.split('=').slice(1).join('');
}
else if (args.length >= i + 1 && args[i + 1] && args[i + 1].slice(0, 2) !== '--') {
value = args[i + 1];
}
else {
value = 'true';
}
var argName = arg.slice(2);
options[argName] = _this.getArgValue(argName, value, options);
}
else {
if (!options.name) {
options.name = arg;
}
else {
throw new TypeError("Invalid argument: " + arg);
}
}
});
if (!['name', 'templates', 'output'].every(function (o) { return options[o] !== undefined; })) {
throw new Error("Config is missing keys: " + JSON.stringify(options));
}
return options;
};
ScaffoldCmd.prototype.getArgValue = function (arg, value, options) {
switch (arg) {
case 'templates':
return (options.templates || []).concat([value]);
case 'output':
return value;
case 'locals':
var split = value.split(',');
var locals = options.locals || {};
for (var _i = 0, split_1 = split; _i < split_1.length; _i++) {
var item = split_1[_i];
var _a = item.split('='), k = _a[0], v = _a[1];
locals[k] = v;
}
return locals;
default:
throw TypeError("arguments invalid for config: arg=`" + arg + "`, value=`" + value + "`");
}
};
ScaffoldCmd.prototype.run = function () {
var config = this.config;
console.info('Config:', config);
var scf = new scaffold_1.default({
name: config.name,
templates: config.templates,
output: config.output,
locals: config.locals,
}).run();
};
return ScaffoldCmd;
}());
new ScaffoldCmd().run();
/***/ })
/******/ ]);
});
//# sourceMappingURL=cmd.js.map

1
dist/cmd.js.map vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,13 +0,0 @@
import IScaffold from './index';
declare class SimpleScaffold {
private config;
private locals;
constructor(config: IScaffold.IConfig);
private parseLocals(text);
private fileList(input);
private getFileContents(filePath);
private getOutputPath(file, basePath);
private writeFile(filePath, fileContents);
run(): void;
}
export default SimpleScaffold;

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":""}

246
dist/scaffold.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();
})(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 __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
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(),
};
this.config = Object.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 = Object.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 _i, input_1, checkPath, files, idx, cleanCheckPath, _a, files_1, file;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_i = 0, input_1 = input;
_b.label = 1;
case 1:
if (!(_i < input_1.length)) return [3 /*break*/, 6];
checkPath = input_1[_i];
files = glob.sync(checkPath).map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
idx = checkPath.indexOf('*');
cleanCheckPath = checkPath;
if (idx >= 0) {
cleanCheckPath = checkPath.slice(0, idx - 1);
}
_a = 0, files_1 = files;
_b.label = 2;
case 2:
if (!(_a < files_1.length)) return [3 /*break*/, 5];
file = files_1[_a];
return [4 /*yield*/, { base: cleanCheckPath, file: file }];
case 3:
_b.sent();
_b.label = 4;
case 4:
_a++;
return [3 /*break*/, 2];
case 5:
_i++;
return [3 /*break*/, 1];
case 6: return [2 /*return*/];
}
});
};
SimpleScaffold.prototype.getFileContents = function (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.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.writeFileSync(filePath, fileContents, { encoding: 'utf-8' });
};
SimpleScaffold.prototype.run = function () {
console.log("Generating scaffold: " + this.config.name + "...");
var templates = this.fileList(this.config.templates);
var fileConf, count = 0;
while (fileConf = templates.next().value) {
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

266
dist/test.js vendored
View File

@@ -1,266 +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 = 5);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = require("path");
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = y[op[0] & 2 ? "return" : op[0] ? "throw" : "next"]) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [0, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
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(),
};
this.config = Object.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 = Object.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 _i, input_1, checkPath, files, idx, cleanCheckPath, _a, files_1, file;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
_i = 0, input_1 = input;
_b.label = 1;
case 1:
if (!(_i < input_1.length)) return [3 /*break*/, 6];
checkPath = input_1[_i];
files = glob.sync(checkPath).map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
idx = checkPath.indexOf('*');
cleanCheckPath = checkPath;
if (idx >= 0) {
cleanCheckPath = checkPath.slice(0, idx - 1);
}
_a = 0, files_1 = files;
_b.label = 2;
case 2:
if (!(_a < files_1.length)) return [3 /*break*/, 5];
file = files_1[_a];
return [4 /*yield*/, { base: cleanCheckPath, file: file }];
case 3:
_b.sent();
_b.label = 4;
case 4:
_a++;
return [3 /*break*/, 2];
case 5:
_i++;
return [3 /*break*/, 1];
case 6: return [2 /*return*/];
}
});
};
SimpleScaffold.prototype.getFileContents = function (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.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.writeFileSync(filePath, fileContents, { encoding: 'utf-8' });
};
SimpleScaffold.prototype.run = function () {
console.log("Generating scaffold: " + this.config.name + "...");
var templates = this.fileList(this.config.templates);
var fileConf, count = 0;
while (fileConf = templates.next().value) {
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',
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

View File

View File

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

View File

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

16
index.d.ts vendored
View File

@@ -1,16 +0,0 @@
declare namespace IScaffold {
export interface IConfig {
name?: string
templates: string[]
output: string | ((path: string, base: string) => string)
locals?: any
}
export interface IFileRepr {
base: string
file: string
}
}
export default IScaffold

196
jest.config.ts Normal file
View File

@@ -0,0 +1,196 @@
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
export default {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/q9/0mns8fgd00b4t5j5lq2wh2yh0000gn/T/jest_dx",
// Automatically clear mock calls and instances between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: "v8",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
modulePathIgnorePatterns: ["<rootDir>/dist"],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: "ts-jest",
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state between every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state between every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: "node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
// testURL: "http://localhost",
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
// timers: "real",
// A map from regular expressions to paths to transformers
// transform: {},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
// extensionsToTreatAsEsm: [".ts"],
}

View File

@@ -1,41 +1,42 @@
{
"name": "simple-scaffold",
"version": "0.3.0",
"version": "1.0.0-alpha.17",
"description": "Create files based on templates",
"repository": "https://github.com/chenasraf/simple-scaffold.git",
"author": "Chen Asraf <inbox@casraf.com>",
"license": "MIT",
"main": "dist/scaffold.js",
"bin": "dist/cmd.js",
"types": "index.d.ts",
"main": "index.js",
"bin": "cmd.js",
"scripts": {
"build": "NODE_ENV=${NODE_ENV:-production} webpack -p && chmod -R +x ./dist",
"dev": "webpack --watch",
"clean": "rm -rf dist/",
"build": "yarn clean && tsc && chmod -R +x ./dist && cp ./package.json ./README.md ./dist/",
"dev": "tsc --watch",
"start": "node dist/scaffold.js",
"test": "jest",
"cmd": "dist/cmd.js",
"test": "jest --verbose",
"cmd": "node --trace-warnings dist/cmd.js",
"build-test": "yarn build && yarn test",
"build-cmd": "yarn build && yarn cmd"
},
"dependencies": {
"@types/glob": "^5.0.34",
"@types/handlebars": "^4.0.36",
"@types/node": "^8.0.50",
"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"
"args": "^5.0.1",
"chalk": "^4.1.2",
"glob": "^7.1.3",
"handlebars": "^4.7.7",
"lodash": "^4.17.21",
"massarg": "^1.0.5",
"util.promisify": "^1.1.1"
},
"devDependencies": {
"jest": "^22.0.4"
},
"jest": {
"testPathIgnorePatterns": [
"node_modules",
"dist"
]
"@types/args": "^3.0.1",
"@types/glob": "^7.1.1",
"@types/jest": "^26.0.24",
"@types/lodash": "^4.14.171",
"@types/mock-fs": "^4.13.1",
"@types/node": "^14.14.22",
"jest": "^27.0.6",
"mock-fs": "^5.0.0",
"ts-jest": "^27.0.3",
"ts-node": "^10.1.0",
"typescript": "^4.3.5"
}
}

View File

@@ -1,104 +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.IConfig
public locals = {} as any
constructor(config: IScaffold.IConfig) {
const DefaultConfig: IScaffold.IConfig = {
name: 'scaffold',
templates: [],
output: process.cwd(),
}
this.config = (Object as any).assign({}, 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 = (Object as any).assign({}, DefaultLocals, config.locals)
}
private parseLocals(text: string): string {
const template = handlebars.compile(text, {
noEscape: true
})
return template(this.locals)
}
private *fileList(input: string[]): IterableIterator<IScaffold.IFileRepr> {
for (const checkPath of input) {
const files = glob.sync(checkPath).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) {
yield {base: cleanCheckPath, file}
}
}
}
private getFileContents(filePath: string): string {
console.log(fs.readFileSync(filePath))
return fs.readFileSync(filePath).toString()
}
private getOutputPath(file: string, basePath: string): string {
let out
if (typeof this.config.output === 'function') {
out = this.config.output(file, basePath)
} else {
const outputDir = this.config.output + `/${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.writeFileSync(filePath, fileContents, { encoding: 'utf-8' })
}
public run(): void {
console.log(`Generating scaffold: ${this.config.name}...`)
const templates = this.fileList(this.config.templates)
let fileConf, count = 0
while (fileConf = templates.next().value) {
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

97
src/cmd.ts Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env node
import massarg from "massarg"
import chalk from "chalk"
import { LogLevel, ScaffoldCmdConfig } from "./types"
import { Scaffold } from "./scaffold"
export function parseCliArgs(args = process.argv.slice(2)) {
return (
massarg<ScaffoldCmdConfig & { help: boolean; extras: string[] }>()
.main(Scaffold)
.option({
name: "name",
aliases: ["n"],
description:
"Name to be passed to the generated files. {{name}} and {{Name}} inside contents and file names will be replaced accordingly.",
isDefault: true,
required: true,
})
.option({
name: "output",
aliases: ["o"],
description: `Path to output to. If --create-sub-folder is enabled, the subfolder will be created inside this path. ${chalk.reset`${chalk.white`(default: current dir)`}`}`,
required: true,
})
.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.",
required: true,
})
.option({
name: "overwrite",
aliases: ["w"],
boolean: true,
defaultValue: false,
description: "Enable to override output files, even if they already exist.",
})
.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: "create-sub-folder",
aliases: ["s"],
boolean: true,
defaultValue: false,
description: "Create subfolder with the input name",
})
.option({
name: "quiet",
aliases: ["q"],
boolean: true,
defaultValue: false,
description: "Suppress output logs (Same as --verbose 0)",
})
.option({
name: "verbose",
aliases: ["v"],
defaultValue: LogLevel.Info,
description: `Determine amount of logs to display. The values are: ${chalk.bold`0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4 (error)`}. The provided level will display messages of the same level or higher.`,
parse: Number,
})
.option({
name: "dry-run",
aliases: ["dr"],
boolean: true,
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.",
})
// .example({
// input: `yarn cmd -t examples/test-input/Component -o examples/test-output -d '{"property":"myProp","value":"10"}'`,
// description: "Usage",
// output: "",
// })
.help({
binName: "simple-scaffold",
useGlobalColumns: true,
usageExample: "[options]",
header: "Create structured files based on templates.",
footer: [
`Copyright © Chen Asraf 2021`,
`NPM: ${chalk.underline`https://npmjs.com/package/simple-scaffold`}`,
`GitHub: ${chalk.underline`https://github.com/chenasraf/simple-scaffold`}`,
].join("\n"),
})
.parse(args)
)
}
parseCliArgs()

0
dist/dist/test.d.ts → src/filters.ts Executable file → Normal file
View File

4
src/index.ts Normal file
View File

@@ -0,0 +1,4 @@
export * from "./scaffold"
export * from "./types"
import Scaffold from "./scaffold"
export default Scaffold

163
src/scaffold.ts Normal file
View File

@@ -0,0 +1,163 @@
import { glob } from "glob"
import path from "path"
import { promisify } from "util"
import { promises as fsPromises } from "fs"
const { readFile, writeFile } = fsPromises
import {
createDirIfNotExists,
getOptionValueForFile,
handleErr,
handlebarsParse,
log,
pathExists,
pascalCase,
isDir,
removeGlob,
makeRelativePath,
registerHelpers,
} from "./utils"
import { LogLevel, ScaffoldConfig } from "./types"
export async function Scaffold({ ...options }: ScaffoldConfig) {
options.output ??= process.cwd()
registerHelpers(options)
try {
options.data = { name: options.name, Name: pascalCase(options.name), ...options.data }
log(options, LogLevel.Debug, "Full config:", {
name: options.name,
templates: options.templates,
output: options.output,
createSubfolder: options.createSubFolder,
data: options.data,
overwrite: options.overwrite,
quiet: options.quiet,
helpers: Object.keys(options.helpers ?? {}),
verbose: `${options.verbose} (${Object.keys(LogLevel).find(
(k) => (LogLevel[k as any] as unknown as number) === options.verbose!
)})`,
})
log(options, LogLevel.Info, "Data:", options.data)
for (let template of options.templates) {
try {
const _isGlob = template.includes("*")
if (!_isGlob && !(await pathExists(template))) {
const err: NodeJS.ErrnoException = new Error(`ENOENT, no such file or directory ${template}`)
err.code = "ENOENT"
err.path = "non-existing-input"
err.errno = -2
throw err
}
const _nonGlobTemplate = _isGlob ? removeGlob(template) : template
log(options, LogLevel.Debug, "before isDir", "isGlob:", _isGlob, template)
const _isDir = _isGlob ? true : await isDir(template)
log(options, LogLevel.Debug, "after isDir", _isDir)
const _shouldAddGlob = !_isGlob && _isDir
const origTemplate = template
if (_shouldAddGlob) {
template = template + "/**/*"
}
log(options, LogLevel.Debug, "before glob")
const files = await promisify(glob)(template, {
dot: true,
debug: false,
nodir: options.verbose === LogLevel.Debug,
nobrace: true,
noext: true,
nocomment: true,
nonegate: true,
})
log(options, LogLevel.Debug, "after glob")
for (const inputFilePath of files) {
if (!(await isDir(inputFilePath))) {
const relPath = makeRelativePath(path.dirname(removeGlob(inputFilePath).replace(_nonGlobTemplate, "")))
const basePath = path
.resolve(process.cwd(), relPath)
.replace(process.cwd() + "/", "")
.replace(process.cwd(), "")
log(
options,
LogLevel.Debug,
`\nprocess.cwd(): ${process.cwd()}`,
`\norigTemplate: ${origTemplate}`,
`\nrelPath: ${relPath}`,
`\ntemplate: ${template}`,
`\ninputFilePath: ${inputFilePath}`,
`\nnonGlobTemplate: ${_nonGlobTemplate}`,
`\nbasePath: ${basePath}`,
`\nisDir: ${_isDir}`,
`\nisGlob: ${_isGlob}`,
`\n`
)
await handleTemplateFile(inputFilePath, basePath, options, options.data)
}
}
} catch (e: any) {
handleErr(e)
}
}
} catch (e: any) {
log(options, LogLevel.Error, e)
throw e
}
}
async function handleTemplateFile(
templatePath: string,
basePath: string,
options: ScaffoldConfig,
data: Record<string, string>
): Promise<void> {
return new Promise(async (resolve, reject) => {
try {
const inputPath = path.resolve(process.cwd(), templatePath)
const outputPathOpt = getOptionValueForFile(inputPath, data, options.output)
const outputDir = path.resolve(
process.cwd(),
...([outputPathOpt, basePath, options.createSubFolder ? options.name : undefined].filter(Boolean) as string[])
)
const outputPath = handlebarsParse(path.join(outputDir, path.basename(inputPath)), data)
log(
options,
LogLevel.Debug,
`\nParsing ${templatePath}`,
`\nBase path: ${basePath}`,
`\nFull input path: ${inputPath}`,
`\nOutput Path Opt: ${outputPathOpt}`,
`\nFull output dir: ${outputDir}`,
`\nFull output path: ${outputPath}`,
`\n`
)
const overwrite = getOptionValueForFile(inputPath, data, options.overwrite ?? false)
const exists = await pathExists(outputPath)
await createDirIfNotExists(path.dirname(outputPath), options)
log(options, LogLevel.Info, `Writing to ${outputPath}`)
if (!exists || overwrite) {
if (exists && overwrite) {
log(options, LogLevel.Info, `File ${outputPath} exists, overwriting`)
}
const templateBuffer = await readFile(inputPath)
const outputContents = handlebarsParse(templateBuffer, data)
if (!options.dryRun) {
await writeFile(outputPath, outputContents)
log(options, LogLevel.Info, "Done.")
} else {
log(options, LogLevel.Info, "Content output:")
log(options, LogLevel.Info, outputContents)
}
} else if (exists) {
log(options, LogLevel.Info, `File ${outputPath} already exists, skipping`)
}
resolve()
} catch (e: any) {
handleErr(e)
reject(e)
}
})
}
export default Scaffold

95
src/types.ts Normal file
View File

@@ -0,0 +1,95 @@
export enum LogLevel {
None = 0,
Debug = 1,
Info = 2,
Warning = 3,
Error = 4,
}
export type FileResponseFn<T> = (fullPath: string, basedir: string, basename: string) => T
export type FileResponse<T> = T | FileResponseFn<T>
export 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. (default: current working directory)
*/
templates: string[]
/** Path to output to. If `createSubFolder` is `true`, the subfolder will be created inside this path. */
output: FileResponse<string>
/**
* Create subfolder with the input name (default: `false`)
*/
createSubFolder?: boolean
/**
* Add custom data to the templates. By default, only your app name is included as `{{name}}` and `{{Name}}`.
*/
data?: Record<string, string>
/**
* Enable to override output files, even if they already exist. (default: `false`)
*
* You may supply a function to this option, which can take the arguments `(fullPath, baseDir, baseName)` and returns
* a string, to return a dynamic path for each file.
*/
overwrite?: FileResponse<boolean>
/** Suppress output logs (Same as `verbose: 0` or `verbose: LogLevel.None`) */
quiet?: boolean
/**
* Determine amount of logs to display.
*
* The values are: `0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4 (error)`. The provided level will display messages
* of the same level or higher. (default: `2 (info)`)
* @see LogLevel
*/
verbose?: LogLevel
/**
* Don't emit files. This is good for testing your scaffolds and making sure they don't fail, without having to write
* actual file contents or create directories. (default: `false`)
*/
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) => string
* ```
*
* A full example might be:
*
* ```typescript
* Scaffold({
* //...
* helpers: {
* upperCamelCase: (text) => camelCase(text).toUpperCase()
* }
* })
* ```
*/
helpers?: Record<string, (text: string) => string>
}
export interface ScaffoldCmdConfig {
name: string
templates: string[]
output: string
createSubFolder: boolean
data?: Record<string, string>
overwrite: boolean
quiet: boolean
verbose: LogLevel
dryRun: boolean
}

131
src/utils.ts Normal file
View File

@@ -0,0 +1,131 @@
import path from "path"
import { F_OK } from "constants"
import { FileResponse, FileResponseFn, LogLevel, ScaffoldConfig } from "./types"
import camelCase from "lodash/camelCase"
import snakeCase from "lodash/snakeCase"
import kebabCase from "lodash/kebabCase"
import startCase from "lodash/startCase"
import Handlebars from "handlebars"
import { promises as fsPromises } from "fs"
import chalk from "chalk"
const { stat, access, mkdir } = fsPromises
export const defaultHelpers: Exclude<ScaffoldConfig["helpers"], undefined> = {
camelCase,
snakeCase,
startCase,
kebabCase,
hyphenCase: kebabCase,
pascalCase,
lowerCase: (text) => text.toLowerCase(),
upperCase: (text) => text.toUpperCase(),
}
export function registerHelpers(options: ScaffoldConfig) {
const _helpers = { ...defaultHelpers, ...options.helpers }
for (const helperName in _helpers) {
log(options, LogLevel.Debug, `Registering helper: ${helperName}`)
Handlebars.registerHelper(helperName, _helpers[helperName as keyof typeof _helpers])
}
}
export function handleErr(err: NodeJS.ErrnoException | null) {
if (err) throw err
}
export function log(options: ScaffoldConfig, level: LogLevel, ...obj: any[]) {
if (options.quiet || options.verbose === LogLevel.None || level <= (options.verbose ?? LogLevel.Info)) {
return
}
const levelColor: Record<LogLevel, keyof typeof chalk> = {
[LogLevel.None]: "reset",
[LogLevel.Debug]: "blue",
[LogLevel.Info]: "dim",
[LogLevel.Warning]: "yellow",
[LogLevel.Error]: "red",
}
const chalkFn: any = chalk[levelColor[level]]
const key: "log" | "warn" | "error" = level === LogLevel.Error ? "error" : level === LogLevel.Warning ? "warn" : "log"
const logFn: any = console[key]
logFn(
...obj.map((i) =>
i instanceof Error
? chalkFn(i, JSON.stringify(i, undefined, 1), i.stack)
: typeof i === "object"
? chalkFn(JSON.stringify(i, undefined, 1))
: chalkFn(i)
)
)
}
export async function createDirIfNotExists(dir: string, options: ScaffoldConfig): Promise<void> {
const parentDir = path.dirname(dir)
if (!(await pathExists(parentDir))) {
await createDirIfNotExists(parentDir, options)
}
if (!(await pathExists(dir))) {
try {
log(options, LogLevel.Debug, `Creating dir ${dir}`)
await mkdir(dir)
return
} catch (e: any) {
if (e.code !== "EEXIST") {
throw e
}
return
}
}
}
export function getOptionValueForFile<T>(
filePath: string,
data: Record<string, string>,
fn: FileResponse<T>,
defaultValue?: T
): T {
if (typeof fn !== "function") {
return defaultValue ?? (fn as T)
}
return (fn as FileResponseFn<T>)(
filePath,
path.dirname(handlebarsParse(filePath, data)),
path.basename(handlebarsParse(filePath, data))
)
}
export function handlebarsParse(templateBuffer: Buffer | string, data: Record<string, string>) {
const parser = Handlebars.compile(templateBuffer.toString(), { noEscape: true })
const outputContents = parser(data)
return outputContents
}
export async function pathExists(filePath: string): Promise<boolean> {
try {
await access(filePath, F_OK)
return true
} catch (e: any) {
if (e.code === "ENOENT") {
return false
}
throw e
}
}
export function pascalCase(s: string): string {
return startCase(s).replace(/\s+/g, "")
}
export async function isDir(path: string): Promise<boolean> {
const tplStat = await stat(path)
return tplStat.isDirectory()
}
export function removeGlob(template: string) {
return template.replace(/\*/g, "").replace(/\/\//g, "/")
}
export function makeRelativePath(str: string): string {
return str.startsWith("/") ? str.slice(1) : str
}

13
test.ts
View File

@@ -1,13 +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',
locals: {
property: 'myProp',
value: '"value"'
}
}).run()

275
tests/scaffold.test.ts Normal file
View File

@@ -0,0 +1,275 @@
import mockFs from "mock-fs"
import FileSystem from "mock-fs/lib/filesystem"
import Scaffold from "../src/scaffold"
import { readdirSync, readFileSync } from "fs"
import { Console } from "console"
import { defaultHelpers } from "../src/utils"
const fileStructNormal = {
input: {
"{{name}}.txt": "Hello, my app is {{name}}",
},
output: {},
}
const fileStructWithData = {
input: {
"{{name}}.txt": "Hello, my value is {{value}}",
},
output: {},
}
const fileStructNested = {
input: {
"{{name}}-1.txt": "This should be in root",
"{{Name}}": {
"{{name}}-2.txt": "Hello, my value is {{value}}",
moreNesting: {
"{{name}}-3.txt": "Hi! My value is actually NOT {{value}}!",
},
},
},
output: {},
}
const defaultHelperNames = Object.keys(defaultHelpers)
const fileStructHelpers = {
input: {
defaults: defaultHelperNames.reduce<Record<string, string>>(
(all, cur) => ({ ...all, [cur + ".txt"]: `{{ ${cur} name }}` }),
{}
),
custom: {
"add1.txt": "{{ add1 name }}",
},
},
output: {},
}
// let logsTemp: any = []
// let logMock: any
function withMock(fileStruct: FileSystem.DirectoryItems, testFn: jest.EmptyFunction): jest.EmptyFunction {
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()
})
}
}
describe("Scaffold", () => {
describe(
"create subfolder",
withMock(fileStructNormal, () => {
test("should not create by default", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
})
const data = readFileSync(process.cwd() + "/output/app_name.txt")
expect(data.toString()).toBe("Hello, my app is app_name")
})
test("should create with config", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
createSubFolder: true,
verbose: 0,
})
const data = readFileSync(process.cwd() + "/output/app_name/app_name.txt")
expect(data.toString()).toBe("Hello, my app is app_name")
})
})
)
describe(
"overwrite",
withMock(fileStructWithData, () => {
test("should not overwrite by default", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
data: { value: "1" },
verbose: 0,
})
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
data: { value: "2" },
verbose: 0,
})
const data = readFileSync(process.cwd() + "/output/app_name.txt")
expect(data.toString()).toBe("Hello, my value is 1")
})
test("should overwrite with config", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
data: { value: "1" },
verbose: 0,
})
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
data: { value: "2" },
overwrite: true,
verbose: 0,
})
const data = readFileSync(process.cwd() + "/output/app_name.txt")
expect(data.toString()).toBe("Hello, my value is 2")
})
})
)
describe(
"errors",
withMock(fileStructNormal, () => {
let consoleMock1: jest.SpyInstance
beforeAll(() => {
consoleMock1 = jest.spyOn(console, "error").mockImplementation(() => void 0)
})
afterAll(() => {
consoleMock1.mockRestore()
})
test("should throw for bad input", async () => {
await expect(
Scaffold({
name: "app_name",
output: "output",
templates: ["non-existing-input"],
data: { value: "1" },
verbose: 0,
})
).rejects.toThrow()
expect(() => readFileSync(process.cwd() + "/output/app_name.txt")).toThrow()
})
})
)
describe(
"outputPath override",
withMock(fileStructNormal, () => {
test("should allow override function", async () => {
await Scaffold({
name: "app_name",
output: (fullPath, basedir, basename) => `custom-output/${basename.split(".")[0]}`,
templates: ["input"],
data: { value: "1" },
verbose: 0,
})
const data = readFileSync(process.cwd() + "/custom-output/app_name/app_name.txt")
expect(data.toString()).toBe("Hello, my app is app_name")
})
})
)
describe(
"output structure",
withMock(fileStructNested, () => {
test("should maintain input structure on output", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
data: { value: "1" },
verbose: 0,
})
const rootDir = readdirSync(process.cwd() + "/output")
const dir = readdirSync(process.cwd() + "/output/AppName")
const nestedDir = readdirSync(process.cwd() + "/output/AppName/moreNesting")
expect(rootDir).toHaveProperty("length")
expect(dir).toHaveProperty("length")
expect(nestedDir).toHaveProperty("length")
const rootFile = readFileSync(process.cwd() + "/output/app_name-1.txt")
const oneDeepFile = readFileSync(process.cwd() + "/output/AppName/app_name-2.txt")
const twoDeepFile = readFileSync(process.cwd() + "/output/AppName/moreNesting/app_name-3.txt")
expect(rootFile.toString()).toEqual("This should be in root")
expect(oneDeepFile.toString()).toEqual("Hello, my value is 1")
expect(twoDeepFile.toString()).toEqual("Hi! My value is actually NOT 1!")
})
})
)
describe(
"helpers",
withMock(fileStructHelpers, () => {
const _helpers: Record<string, (text: string) => string> = {
add1: (text) => text + " 1",
}
describe("default helpers", () => {
test("should work", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
helpers: _helpers,
})
const results = {
camelCase: "appName",
snakeCase: "app_name",
startCase: "App Name",
kebabCase: "app-name",
hyphenCase: "app-name",
pascalCase: "AppName",
lowerCase: "app_name",
upperCase: "APP_NAME",
}
for (const key in results) {
const file = readFileSync(process.cwd() + `/output/defaults/${key}.txt`)
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
})
describe("custom helpers", () => {
test("should work", async () => {
await Scaffold({
name: "app_name",
output: "output",
templates: ["input"],
verbose: 0,
helpers: _helpers,
})
const results = {
add1: "app_name 1",
}
for (const key in results) {
const file = readFileSync(process.cwd() + `/output/custom/${key}.txt`)
expect(file.toString()).toEqual(results[key as keyof typeof results])
}
})
})
})
)
})

View File

@@ -1,14 +1,23 @@
{
"compilerOptions": {
"target": "es5",
"target": "ES2019",
"module": "commonjs",
"lib": ["es2017", "es6"],
"moduleResolution": "node",
"esModuleInterop": true,
"lib": [
"ES2019",
],
"declaration": true,
"outDir": "dist",
"strict": true,
"sourceMap": true
"sourceMap": true,
"removeComments": false,
},
"include": [
"src/index.ts",
"src/cmd.ts",
],
"exclude": [
"examples"
"tests/*"
]
}

View File

@@ -1,43 +0,0 @@
const path = require('path')
const webpack = require('webpack')
const nodeExternals = require('webpack-node-externals')
module.exports = {
devtool: process.env.NODE_ENV === 'develop' ? 'inline-source-map' : 'source-map',
target: 'node',
entry: {
scaffold: './scaffold.ts',
test: './test.ts',
cmd: './cmd.ts',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
library: 'library',
libraryTarget: 'umd',
},
resolve: {
extensions: ['.ts']
},
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: ['./examples', 'node_modules']
}
]
},
plugins: [
new webpack.DefinePlugin({
'__dirname': '__dirname'
}),
new webpack.BannerPlugin({
banner: '#!/usr/bin/env node',
raw: true,
include: /cmd\.js/,
})
],
}

5333
yarn.lock

File diff suppressed because it is too large Load Diff