mirror of
https://github.com/chenasraf/simple-scaffold.git
synced 2026-05-18 01:29:09 +00:00
Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e26a434dba | ||
|
|
1783ddf230 | ||
|
|
4575f73d69 | ||
|
|
f07df79e82 | ||
|
|
cb6e06f1c9 | ||
|
|
a043a05bc9 | ||
|
|
52cb3e7353 | ||
|
|
89d7897f4e | ||
|
|
d6e1693074 | ||
|
|
56f1340093 | ||
|
|
8782f18a73 | ||
|
|
21c4ab6e1a | ||
|
|
d797e5b640 | ||
|
|
c3835a7b04 | ||
|
|
81ba5f50fd | ||
|
|
edcf1aceaa | ||
|
|
d0a0db0f82 | ||
|
|
819f84e5ca | ||
|
|
36f8b87514 | ||
|
|
d06c0d64b3 | ||
|
|
5ab2637485 | ||
|
|
0af639254b | ||
|
|
391a08ac63 | ||
|
|
cd34930e04 | ||
|
|
956b00700f | ||
|
|
91116bba69 | ||
|
|
f4cc44cf17 | ||
|
|
54b90235e5 | ||
|
|
d03d0e0812 | ||
|
|
5b72b6c166 | ||
|
|
d96992cd2d | ||
|
|
925993948a | ||
|
|
09403e18f1 | ||
|
|
c2bc8b7606 | ||
|
|
b1b1aca802 | ||
|
|
2305083c7d | ||
|
|
8413225202 | ||
|
|
559b5ad7f3 | ||
|
|
a21a35f773 | ||
|
|
84e6207891 | ||
|
|
6b57406369 | ||
|
|
53e8bc4cc0 | ||
|
|
54848f9c50 | ||
|
|
2623b787e6 | ||
|
|
ad30ee0c0c | ||
|
|
8575b1e0c3 | ||
|
|
d8aba21d0e | ||
|
|
3f2945eaa4 | ||
|
|
930344656a | ||
|
|
99c9055208 | ||
|
|
f1698d2a46 | ||
|
|
c17e6304e6 | ||
|
|
535260a0c0 | ||
|
|
14988576f3 | ||
|
|
6f03ed9d60 | ||
|
|
c7749a8d33 | ||
|
|
a59f29d71d | ||
|
|
bc224d93e1 | ||
|
|
cf923d8889 | ||
|
|
01e458ee0c | ||
|
|
93853712f5 | ||
|
|
474a3dcc1f | ||
|
|
27e84d1093 | ||
|
|
a6f25facc0 | ||
|
|
3ee66b24f5 | ||
|
|
0ce19a7e1a | ||
|
|
7273538d79 | ||
|
|
43b64965e3 | ||
|
|
4f81654e05 | ||
|
|
8fcc7a6dc6 | ||
|
|
b4b0de6f2f | ||
|
|
2d5626ca70 | ||
|
|
564e821419 | ||
|
|
a52f9a0b84 | ||
|
|
aeddd44778 | ||
|
|
7cdf5e461b | ||
|
|
c42a58c2f2 | ||
|
|
d0c0152717 | ||
|
|
208ee307c8 | ||
|
|
54834909b9 | ||
|
|
40b592072e | ||
|
|
3cb9a6fcd8 | ||
|
|
12974b5561 | ||
|
|
7f98d469a3 | ||
|
|
cd25b04886 | ||
|
|
5cd637f41f | ||
|
|
0a4467ae5f | ||
|
|
713a0ed44f | ||
|
|
edec2d1c26 | ||
|
|
2e12907412 | ||
|
|
5b7e0e30f1 | ||
|
|
09238300cd | ||
|
|
552614ca3f | ||
|
|
813f706cf0 | ||
|
|
1bc2221472 | ||
|
|
f07affa124 | ||
|
|
ce22a2c34c | ||
|
|
7c0c347002 | ||
|
|
977288ae5a | ||
|
|
4afafa5a4a | ||
|
|
7bee2a51c7 | ||
|
|
d4c049baaf | ||
|
|
06590c4b6e | ||
|
|
c4f2dfb04f | ||
|
|
a410b79195 | ||
|
|
71d544aff4 |
5
.editorconfig
Normal file
5
.editorconfig
Normal 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
12
.github/FUNDING.yml
vendored
Normal 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
52
.github/workflows/alpha.yml
vendored
Normal 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
50
.github/workflows/main.yml
vendored
Normal 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
17
.github/workflows/pull_requests.yml
vendored
Normal 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
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -58,4 +58,6 @@ typings/
|
||||
.env
|
||||
|
||||
examples/test-output/**/*
|
||||
!examples/test-output/.gitkeep
|
||||
dist/
|
||||
.DS_Store
|
||||
tmp/
|
||||
|
||||
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2
|
||||
}
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -1,3 +1,13 @@
|
||||
{
|
||||
"typescript.tsdk": "./node_modules/typescript/lib"
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"npm.packageManager": "yarn",
|
||||
"cSpell.words": [
|
||||
"massarg",
|
||||
"nobrace",
|
||||
"nocomment",
|
||||
"nodir",
|
||||
"noext",
|
||||
"nonegate",
|
||||
"subdir"
|
||||
]
|
||||
}
|
||||
|
||||
56
.vscode/tasks.json
vendored
56
.vscode/tasks.json
vendored
@@ -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": [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
21
MIGRATION.md
Normal file
21
MIGRATION.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Migrating from 0.x to 1.0
|
||||
|
||||
In Simple Scaffold v1.0, the entire codebase was overhauled, yet usage remains mostly the same
|
||||
between versions. With these notable exceptions:
|
||||
|
||||
- Some of the argument names have changed
|
||||
- Template syntax has been improved
|
||||
- The command to run Scaffold has been simplified from `new SimpleScaffold(opts).run()` to `SimpleScaffold(opts)`, which now returns a promise that you can await to know when the process has been completed.
|
||||
|
||||
## 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.
|
||||
336
README.md
336
README.md
@@ -1,7 +1,19 @@
|
||||
# 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.
|
||||
|
||||
```bash
|
||||
@@ -9,140 +21,274 @@ You can either use it as a command line tool or import into your own code and ru
|
||||
npm install [-g] simple-scaffold
|
||||
# yarn
|
||||
yarn [global] add simple-scaffold
|
||||
# run without installing
|
||||
npx simple-scaffold <...args>
|
||||
```
|
||||
|
||||
## Use as a command line tool
|
||||
The first non-token argument (that has no `--` prefix) will be used as the scaffold name.
|
||||
The rest is ignored, of course except for the available arguments below.
|
||||
|
||||
```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.
|
||||
(default: current dir)
|
||||
|
||||
--templates|-t Template files to use as input. You may provide multiple
|
||||
files, each of which can be a relative or absolute path, or a
|
||||
glob pattern for multiple file matching easily.
|
||||
|
||||
--overwrite|-w Enable to override output files, even if they already
|
||||
exist. (default: false)
|
||||
|
||||
--data|-d Add custom data to the templates. By default, only your
|
||||
app name is included.
|
||||
|
||||
--create-sub-folder|-s Create subfolder with the input name
|
||||
(default: false)
|
||||
|
||||
--sub-folder-name-helper|-sh Default helper to apply to subfolder name when using
|
||||
`--create-sub-folder true`.
|
||||
|
||||
--quiet|-q Suppress output logs (Same as --verbose 0)
|
||||
(default: false)
|
||||
|
||||
--verbose|-v Determine amount of logs to display. The values are:
|
||||
0 (none) | 1 (debug) | 2 (info) | 3 (warn) | 4
|
||||
(error). The provided level will display messages of
|
||||
the same level or higher. (default:
|
||||
2)
|
||||
|
||||
--dry-run|-dr Don't emit files. This is good for testing your
|
||||
scaffolds and making sure they don't fail, without having to
|
||||
write actual file contents or create directories.
|
||||
(default: false)
|
||||
```
|
||||
|
||||
You can add this as a script in your `package.json`:
|
||||
You can also add this as a script in your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
...
|
||||
"scripts": {
|
||||
"scaffold": "node node_modules/simple-scaffold/dist/cmd.js --template scaffolds/component/**/* --output src/components --locals myProp=\"propname\",myVal=123"
|
||||
...
|
||||
"scaffold": "yarn simple-scaffold --templates scaffolds/component/**/* --output src/components --data '{\"myProp\": \"propName\", \"myVal\": \"123\"}'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Scaffolding
|
||||
Scaffolding will replace {{vars}} in both the file name and its contents and put the transformed files
|
||||
in `<output>/<{{Name}}`.
|
||||
## Use in Node.js
|
||||
|
||||
Your context will be pre-populated with the following:
|
||||
- `{{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 `}}`.
|
||||
They are all stringified, so be sure to parse them accordingly by creating a script, if necessary.
|
||||
|
||||
### Command line options
|
||||
##### `--template glob [--template glob2 [...]]` (required) (alias: -t)
|
||||
A glob pattern of template files to load.
|
||||
|
||||
A template file may be of any type and extension, and supports [Handlebars](https://handlebarsjs.com) as a parsing engine for the file names and contents, so you may customize both with variables from your configuration.
|
||||
|
||||
You can load more than one template list by simple adding more `--template` arguments.
|
||||
|
||||
##### `--output path` (optional) (alias -o)
|
||||
The output directory to put the new files in. They will attempt to maintain their regular structure as they are found, if possible.
|
||||
|
||||
Your new scaffold will be placed under a directory with the scaffold name from the argumemts.
|
||||
|
||||
You may also pass a function to transform the output path for each file individually.
|
||||
This function takes 2 arguments: filename, and base glob path
|
||||
|
||||
##### `--locals key=value [--locals key=value [,...]]` (optional) (alias: -l)
|
||||
Pass a KV map to the template for parsing.
|
||||
|
||||
##### `--create-sub-folder [true|false]` (optional) (alias -S) (default: true)
|
||||
Whether to create a subfolder for the output with all the files inside, or simply dump them directly in the output folder.
|
||||
|
||||
#####
|
||||
|
||||
### Use in Node.js
|
||||
You can also build the scaffold yourself, if you want to create more complex arguments or scaffold groups.
|
||||
Simply pass a config object to the constructor, and invoke `run()` when you are ready to start.
|
||||
Simply pass a config object to the Scaffold function when you are ready to start.
|
||||
The config takes similar arguments to the command line:
|
||||
|
||||
```javascript
|
||||
const SimpleScaffold = require('simple-scaffold').default
|
||||
```typescript
|
||||
import Scaffold from "simple-scaffold"
|
||||
|
||||
const scaffold = new SimpleScaffold({
|
||||
name: 'component',
|
||||
templates: [path.join(__dirname, 'scaffolds', 'component')],
|
||||
output: path.join(__dirname, 'src', 'components'),
|
||||
const config = {
|
||||
name: "component",
|
||||
templates: [path.join(__dirname, "scaffolds", "component")],
|
||||
output: path.join(__dirname, "src", "components"),
|
||||
createSubFolder: true,
|
||||
locals: {
|
||||
property: 'value',
|
||||
subFolderNameHelper: "upperCase"
|
||||
data: {
|
||||
property: "value",
|
||||
},
|
||||
helpers: {
|
||||
twice: (text) => [text, text].join(" ")
|
||||
}
|
||||
}).run()
|
||||
}
|
||||
|
||||
const scaffold = Scaffold(config)
|
||||
```
|
||||
|
||||
## Example Scaffold Input
|
||||
### Additional Node.js options
|
||||
|
||||
### Input Directory structure
|
||||
```
|
||||
- project
|
||||
- scaffold
|
||||
- {{Name}}.js
|
||||
- src
|
||||
- components
|
||||
- ...
|
||||
```
|
||||
In addition to all the options available in the command line, there are some JS-specific options
|
||||
available:
|
||||
|
||||
#### project/scaffold/{{Name}}.js
|
||||
```js
|
||||
const React = require('react')
|
||||
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:
|
||||
|
||||
module.exports = class {{Name}} extends React.Component {
|
||||
render() {
|
||||
<div className="{{className}}">{{Name}} Component</div>
|
||||
}
|
||||
```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(),
|
||||
}
|
||||
```
|
||||
|
||||
### Run Example
|
||||
These helpers will also be available to you when using `subFolderNameHelper` or
|
||||
`--sub-folder-name-helper` as a possible value.
|
||||
|
||||
## 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.js
|
||||
- ...
|
||||
- scaffold
|
||||
- {{Name}}.js
|
||||
- src
|
||||
- components
|
||||
- ...
|
||||
```
|
||||
|
||||
With `createSubfolder = false`:
|
||||
```
|
||||
- project
|
||||
- src
|
||||
- components
|
||||
- MyComponent.js
|
||||
- ...
|
||||
```
|
||||
#### Contents of `project/scaffold/{{Name}}.jsx`
|
||||
|
||||
#### project/scaffold/MyComponent/MyComponent.js
|
||||
```js
|
||||
const React = require('react')
|
||||
|
||||
module.exports = class MyComponent extends React.Component {
|
||||
render() {
|
||||
<div className="my-component">MyComponent Component</div>
|
||||
}
|
||||
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` to watch
|
||||
> for changes and build, or `yarn build` before running this, or use `yarn build-cmd` instead,
|
||||
> which triggers a build right before running the command with the rest of the given arguments.
|
||||
|
||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
plugins: ["@babel/plugin-proposal-nullish-coalescing-operator"],
|
||||
}
|
||||
55
cmd.ts
55
cmd.ts
@@ -1,55 +0,0 @@
|
||||
import SimpleScaffold from './scaffold'
|
||||
import * as fs from 'fs'
|
||||
import IScaffold from './index'
|
||||
import * as cliArgs from 'command-line-args'
|
||||
import * as cliUsage from 'command-line-usage'
|
||||
import * as path from 'path'
|
||||
|
||||
type Def = cliArgs.OptionDefinition & { description?: string }
|
||||
|
||||
function localsParser(content: string) {
|
||||
const [key, value] = content.split('=')
|
||||
return { [key]: value }
|
||||
}
|
||||
|
||||
function filePathParser(content: string) {
|
||||
if (content.startsWith('/')) {
|
||||
return content
|
||||
}
|
||||
return [process.cwd(), content].join(path.sep)
|
||||
}
|
||||
|
||||
const defs: Def[] = [
|
||||
{ name: 'name', alias: 'n', type: String, description: 'Component output name', defaultOption: true },
|
||||
{ name: 'templates', alias: 't', type: filePathParser, multiple: true },
|
||||
{ name: 'output', alias: 'o', type: filePathParser, multiple: true },
|
||||
{ name: 'locals', alias: 'l', multiple: true, type: localsParser },
|
||||
{ name: 'create-sub-folder', alias: 'S', type: (text: string) => text && text.trim().length ? ['true', '1', 'on'].includes(text.trim()) : true },
|
||||
{ name: 'help', alias: 'h', type: Boolean, description: 'Display this help message' },
|
||||
]
|
||||
|
||||
const args = cliArgs(defs, { camelCase: true })
|
||||
|
||||
const help = [
|
||||
{ header: 'Scaffold Generator', content: 'Generate scaffolds for your project based on file templates.' },
|
||||
{ header: 'Options', optionList: defs }
|
||||
]
|
||||
|
||||
args.locals = (args.locals || []).reduce((all: object, cur: object) => ({ ...all, ...cur }), {} as IScaffold.Config['locals'])
|
||||
if (args.createSubFolder === null) {
|
||||
args.createSubFolder = true
|
||||
}
|
||||
console.info('Config:', args)
|
||||
|
||||
if (args.help || !args.name) {
|
||||
console.log(cliUsage(help))
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
new SimpleScaffold({
|
||||
name: args.name,
|
||||
templates: args.templates,
|
||||
output: args.output,
|
||||
locals: args.locals,
|
||||
createSubfolder: args.createSubFolder,
|
||||
}).run()
|
||||
292
dist/cmd.js
vendored
292
dist/cmd.js
vendored
@@ -1,292 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
(function webpackUniversalModuleDefinition(root, factory) {
|
||||
if(typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = factory();
|
||||
else if(typeof define === 'function' && define.amd)
|
||||
define([], factory);
|
||||
else if(typeof exports === 'object')
|
||||
exports["library"] = factory();
|
||||
else
|
||||
root["library"] = factory();
|
||||
})(typeof self !== 'undefined' ? self : this, function() {
|
||||
return /******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, {
|
||||
/******/ configurable: false,
|
||||
/******/ enumerable: true,
|
||||
/******/ get: getter
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 6);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([
|
||||
/* 0 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("path");
|
||||
|
||||
/***/ }),
|
||||
/* 1 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __assign = (this && this.__assign) || Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var fs = __webpack_require__(2);
|
||||
var path = __webpack_require__(0);
|
||||
var glob = __webpack_require__(3);
|
||||
var handlebars = __webpack_require__(4);
|
||||
var SimpleScaffold = /** @class */ (function () {
|
||||
function SimpleScaffold(config) {
|
||||
this.locals = {};
|
||||
var DefaultConfig = {
|
||||
name: 'scaffold',
|
||||
templates: [],
|
||||
output: process.cwd(),
|
||||
createSubfolder: true,
|
||||
};
|
||||
this.config = __assign({}, DefaultConfig, config);
|
||||
var DefaultLocals = {
|
||||
Name: this.config.name[0].toUpperCase() + this.config.name.slice(1),
|
||||
name: this.config.name[0].toLowerCase() + this.config.name.slice(1)
|
||||
};
|
||||
this.locals = __assign({}, DefaultLocals, config.locals);
|
||||
}
|
||||
SimpleScaffold.prototype.parseLocals = function (text) {
|
||||
var template = handlebars.compile(text, {
|
||||
noEscape: true
|
||||
});
|
||||
return template(this.locals);
|
||||
};
|
||||
SimpleScaffold.prototype.fileList = function (input) {
|
||||
var output = [];
|
||||
for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
|
||||
var checkPath = input_1[_i];
|
||||
var files = glob.sync(checkPath, { dot: true })
|
||||
.map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
|
||||
var idx = checkPath.indexOf('*');
|
||||
var cleanCheckPath = checkPath;
|
||||
if (idx >= 0) {
|
||||
cleanCheckPath = checkPath.slice(0, idx - 1);
|
||||
}
|
||||
for (var _a = 0, files_1 = files; _a < files_1.length; _a++) {
|
||||
var file = files_1[_a];
|
||||
output.push({ base: cleanCheckPath, file: file });
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
SimpleScaffold.prototype.getFileContents = function (filePath) {
|
||||
console.log(fs.readFileSync(filePath));
|
||||
return fs.readFileSync(filePath).toString();
|
||||
};
|
||||
SimpleScaffold.prototype.getOutputPath = function (file, basePath) {
|
||||
var out;
|
||||
if (typeof this.config.output === 'function') {
|
||||
out = this.config.output(file, basePath);
|
||||
}
|
||||
else {
|
||||
var outputDir = this.config.output + (this.config.createSubfolder ? "/" + this.config.name + "/" : '/');
|
||||
var idx = file.indexOf(basePath);
|
||||
var relativeFilePath = file;
|
||||
if (idx >= 0) {
|
||||
relativeFilePath = file.slice(idx + basePath.length + 1);
|
||||
}
|
||||
out = outputDir + relativeFilePath;
|
||||
}
|
||||
return this.parseLocals(out);
|
||||
};
|
||||
SimpleScaffold.prototype.writeFile = function (filePath, fileContents) {
|
||||
if (!fs.existsSync(path.dirname(filePath))) {
|
||||
fs.mkdirSync(path.dirname(filePath));
|
||||
}
|
||||
console.info('Writing file:', filePath);
|
||||
fs.writeFile(filePath, fileContents, { encoding: 'utf-8' }, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
};
|
||||
SimpleScaffold.prototype.run = function () {
|
||||
console.log("Generating scaffold: " + this.config.name + "...");
|
||||
var templates = this.fileList(this.config.templates);
|
||||
var fileConf, count = 0;
|
||||
for (var _i = 0, templates_1 = templates; _i < templates_1.length; _i++) {
|
||||
fileConf = templates_1[_i];
|
||||
count++;
|
||||
var file = fileConf.file, base = fileConf.base;
|
||||
var outputPath = this.getOutputPath(file, base);
|
||||
var contents = this.getFileContents(file);
|
||||
var outputContents = this.parseLocals(contents);
|
||||
this.writeFile(outputPath, outputContents);
|
||||
console.info('Parsing:', { file: file, base: base, outputPath: outputPath, outputContents: outputContents.replace("\n", "\\n") });
|
||||
}
|
||||
if (!count) {
|
||||
throw new Error('No files to scaffold!');
|
||||
}
|
||||
console.log('Done');
|
||||
};
|
||||
return SimpleScaffold;
|
||||
}());
|
||||
exports.default = SimpleScaffold;
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 2 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("fs");
|
||||
|
||||
/***/ }),
|
||||
/* 3 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("glob");
|
||||
|
||||
/***/ }),
|
||||
/* 4 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("handlebars");
|
||||
|
||||
/***/ }),
|
||||
/* 5 */,
|
||||
/* 6 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __assign = (this && this.__assign) || Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var scaffold_1 = __webpack_require__(1);
|
||||
var cliArgs = __webpack_require__(7);
|
||||
var cliUsage = __webpack_require__(8);
|
||||
var path = __webpack_require__(0);
|
||||
function localsParser(content) {
|
||||
var _a;
|
||||
var _b = content.split('='), key = _b[0], value = _b[1];
|
||||
return _a = {}, _a[key] = value, _a;
|
||||
}
|
||||
function filePathParser(content) {
|
||||
if (content.startsWith('/')) {
|
||||
return content;
|
||||
}
|
||||
return [process.cwd(), content].join(path.sep);
|
||||
}
|
||||
var defs = [
|
||||
{ name: 'name', alias: 'n', type: String, description: 'Component output name', defaultOption: true },
|
||||
{ name: 'templates', alias: 't', type: filePathParser, multiple: true },
|
||||
{ name: 'output', alias: 'o', type: filePathParser, multiple: true },
|
||||
{ name: 'locals', alias: 'l', multiple: true, type: localsParser },
|
||||
{ name: 'create-sub-folder', alias: 'S', type: function (text) { return text && text.trim().length ? ['true', '1', 'on'].includes(text.trim()) : true; } },
|
||||
{ name: 'help', alias: 'h', type: Boolean, description: 'Display this help message' },
|
||||
];
|
||||
var args = cliArgs(defs, { camelCase: true });
|
||||
var help = [
|
||||
{ header: 'Scaffold Generator', content: 'Generate scaffolds for your project based on file templates.' },
|
||||
{ header: 'Options', optionList: defs }
|
||||
];
|
||||
args.locals = (args.locals || []).reduce(function (all, cur) { return (__assign({}, all, cur)); }, {});
|
||||
if (args.createSubFolder === null) {
|
||||
args.createSubFolder = true;
|
||||
}
|
||||
console.info('Config:', args);
|
||||
if (args.help || !args.name) {
|
||||
console.log(cliUsage(help));
|
||||
process.exit(0);
|
||||
}
|
||||
new scaffold_1.default({
|
||||
name: args.name,
|
||||
templates: args.templates,
|
||||
output: args.output,
|
||||
locals: args.locals,
|
||||
createSubfolder: args.createSubFolder,
|
||||
}).run();
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 7 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("command-line-args");
|
||||
|
||||
/***/ }),
|
||||
/* 8 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("command-line-usage");
|
||||
|
||||
/***/ })
|
||||
/******/ ]);
|
||||
});
|
||||
//# sourceMappingURL=cmd.js.map
|
||||
1
dist/cmd.js.map
vendored
1
dist/cmd.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/dist/cmd.d.ts
vendored
1
dist/dist/cmd.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export {};
|
||||
13
dist/dist/scaffold.d.ts
vendored
13
dist/dist/scaffold.d.ts
vendored
@@ -1,13 +0,0 @@
|
||||
import IScaffold from './index';
|
||||
declare class SimpleScaffold {
|
||||
config: IScaffold.Config;
|
||||
locals: IScaffold.Config['locals'];
|
||||
constructor(config: IScaffold.Config);
|
||||
private parseLocals;
|
||||
private fileList;
|
||||
private getFileContents;
|
||||
private getOutputPath;
|
||||
private writeFile;
|
||||
run(): void;
|
||||
}
|
||||
export default SimpleScaffold;
|
||||
1
dist/dist/test.d.ts
vendored
1
dist/dist/test.d.ts
vendored
@@ -1 +0,0 @@
|
||||
export {};
|
||||
96
dist/main.js
vendored
96
dist/main.js
vendored
@@ -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
1
dist/main.js.map
vendored
@@ -1 +0,0 @@
|
||||
{"version":3,"sources":["webpack/universalModuleDefinition","webpack/bootstrap c994c2c400fa1fc61abe","external \"jest\""],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;ACVA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;;AAGA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,aAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,mCAA2B,0BAA0B,EAAE;AACvD,yCAAiC,eAAe;AAChD;AACA;AACA;;AAEA;AACA,8DAAsD,+DAA+D;;AAErH;AACA;;AAEA;AACA;;;;;;;;;;;;;;;;;AC7DA,iC","file":"main.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"library\"] = factory();\n\telse\n\t\troot[\"library\"] = factory();\n})(this, function() {\nreturn \n\n\n// WEBPACK FOOTER //\n// webpack/universalModuleDefinition"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 7);\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap c994c2c400fa1fc61abe","module.exports = require(\"jest\");\n\n\n//////////////////\n// WEBPACK FOOTER\n// external \"jest\"\n// module id = 8\n// module chunks = 3"],"sourceRoot":""}
|
||||
217
dist/scaffold.js
vendored
217
dist/scaffold.js
vendored
@@ -1,217 +0,0 @@
|
||||
(function webpackUniversalModuleDefinition(root, factory) {
|
||||
if(typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = factory();
|
||||
else if(typeof define === 'function' && define.amd)
|
||||
define([], factory);
|
||||
else if(typeof exports === 'object')
|
||||
exports["library"] = factory();
|
||||
else
|
||||
root["library"] = factory();
|
||||
})(typeof self !== 'undefined' ? self : this, function() {
|
||||
return /******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, {
|
||||
/******/ configurable: false,
|
||||
/******/ enumerable: true,
|
||||
/******/ get: getter
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 1);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([
|
||||
/* 0 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("path");
|
||||
|
||||
/***/ }),
|
||||
/* 1 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __assign = (this && this.__assign) || Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var fs = __webpack_require__(2);
|
||||
var path = __webpack_require__(0);
|
||||
var glob = __webpack_require__(3);
|
||||
var handlebars = __webpack_require__(4);
|
||||
var SimpleScaffold = /** @class */ (function () {
|
||||
function SimpleScaffold(config) {
|
||||
this.locals = {};
|
||||
var DefaultConfig = {
|
||||
name: 'scaffold',
|
||||
templates: [],
|
||||
output: process.cwd(),
|
||||
createSubfolder: true,
|
||||
};
|
||||
this.config = __assign({}, DefaultConfig, config);
|
||||
var DefaultLocals = {
|
||||
Name: this.config.name[0].toUpperCase() + this.config.name.slice(1),
|
||||
name: this.config.name[0].toLowerCase() + this.config.name.slice(1)
|
||||
};
|
||||
this.locals = __assign({}, DefaultLocals, config.locals);
|
||||
}
|
||||
SimpleScaffold.prototype.parseLocals = function (text) {
|
||||
var template = handlebars.compile(text, {
|
||||
noEscape: true
|
||||
});
|
||||
return template(this.locals);
|
||||
};
|
||||
SimpleScaffold.prototype.fileList = function (input) {
|
||||
var output = [];
|
||||
for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
|
||||
var checkPath = input_1[_i];
|
||||
var files = glob.sync(checkPath, { dot: true })
|
||||
.map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
|
||||
var idx = checkPath.indexOf('*');
|
||||
var cleanCheckPath = checkPath;
|
||||
if (idx >= 0) {
|
||||
cleanCheckPath = checkPath.slice(0, idx - 1);
|
||||
}
|
||||
for (var _a = 0, files_1 = files; _a < files_1.length; _a++) {
|
||||
var file = files_1[_a];
|
||||
output.push({ base: cleanCheckPath, file: file });
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
SimpleScaffold.prototype.getFileContents = function (filePath) {
|
||||
console.log(fs.readFileSync(filePath));
|
||||
return fs.readFileSync(filePath).toString();
|
||||
};
|
||||
SimpleScaffold.prototype.getOutputPath = function (file, basePath) {
|
||||
var out;
|
||||
if (typeof this.config.output === 'function') {
|
||||
out = this.config.output(file, basePath);
|
||||
}
|
||||
else {
|
||||
var outputDir = this.config.output + (this.config.createSubfolder ? "/" + this.config.name + "/" : '/');
|
||||
var idx = file.indexOf(basePath);
|
||||
var relativeFilePath = file;
|
||||
if (idx >= 0) {
|
||||
relativeFilePath = file.slice(idx + basePath.length + 1);
|
||||
}
|
||||
out = outputDir + relativeFilePath;
|
||||
}
|
||||
return this.parseLocals(out);
|
||||
};
|
||||
SimpleScaffold.prototype.writeFile = function (filePath, fileContents) {
|
||||
if (!fs.existsSync(path.dirname(filePath))) {
|
||||
fs.mkdirSync(path.dirname(filePath));
|
||||
}
|
||||
console.info('Writing file:', filePath);
|
||||
fs.writeFile(filePath, fileContents, { encoding: 'utf-8' }, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
};
|
||||
SimpleScaffold.prototype.run = function () {
|
||||
console.log("Generating scaffold: " + this.config.name + "...");
|
||||
var templates = this.fileList(this.config.templates);
|
||||
var fileConf, count = 0;
|
||||
for (var _i = 0, templates_1 = templates; _i < templates_1.length; _i++) {
|
||||
fileConf = templates_1[_i];
|
||||
count++;
|
||||
var file = fileConf.file, base = fileConf.base;
|
||||
var outputPath = this.getOutputPath(file, base);
|
||||
var contents = this.getFileContents(file);
|
||||
var outputContents = this.parseLocals(contents);
|
||||
this.writeFile(outputPath, outputContents);
|
||||
console.info('Parsing:', { file: file, base: base, outputPath: outputPath, outputContents: outputContents.replace("\n", "\\n") });
|
||||
}
|
||||
if (!count) {
|
||||
throw new Error('No files to scaffold!');
|
||||
}
|
||||
console.log('Done');
|
||||
};
|
||||
return SimpleScaffold;
|
||||
}());
|
||||
exports.default = SimpleScaffold;
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 2 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("fs");
|
||||
|
||||
/***/ }),
|
||||
/* 3 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("glob");
|
||||
|
||||
/***/ }),
|
||||
/* 4 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("handlebars");
|
||||
|
||||
/***/ })
|
||||
/******/ ]);
|
||||
});
|
||||
//# sourceMappingURL=scaffold.js.map
|
||||
1
dist/scaffold.js.map
vendored
1
dist/scaffold.js.map
vendored
File diff suppressed because one or more lines are too long
246
dist/test.js
vendored
246
dist/test.js
vendored
@@ -1,246 +0,0 @@
|
||||
(function webpackUniversalModuleDefinition(root, factory) {
|
||||
if(typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = factory();
|
||||
else if(typeof define === 'function' && define.amd)
|
||||
define([], factory);
|
||||
else if(typeof exports === 'object')
|
||||
exports["library"] = factory();
|
||||
else
|
||||
root["library"] = factory();
|
||||
})(typeof self !== 'undefined' ? self : this, function() {
|
||||
return /******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, {
|
||||
/******/ configurable: false,
|
||||
/******/ enumerable: true,
|
||||
/******/ get: getter
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 5);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([
|
||||
/* 0 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("path");
|
||||
|
||||
/***/ }),
|
||||
/* 1 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __assign = (this && this.__assign) || Object.assign || function(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
||||
t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var fs = __webpack_require__(2);
|
||||
var path = __webpack_require__(0);
|
||||
var glob = __webpack_require__(3);
|
||||
var handlebars = __webpack_require__(4);
|
||||
var SimpleScaffold = /** @class */ (function () {
|
||||
function SimpleScaffold(config) {
|
||||
this.locals = {};
|
||||
var DefaultConfig = {
|
||||
name: 'scaffold',
|
||||
templates: [],
|
||||
output: process.cwd(),
|
||||
createSubfolder: true,
|
||||
};
|
||||
this.config = __assign({}, DefaultConfig, config);
|
||||
var DefaultLocals = {
|
||||
Name: this.config.name[0].toUpperCase() + this.config.name.slice(1),
|
||||
name: this.config.name[0].toLowerCase() + this.config.name.slice(1)
|
||||
};
|
||||
this.locals = __assign({}, DefaultLocals, config.locals);
|
||||
}
|
||||
SimpleScaffold.prototype.parseLocals = function (text) {
|
||||
var template = handlebars.compile(text, {
|
||||
noEscape: true
|
||||
});
|
||||
return template(this.locals);
|
||||
};
|
||||
SimpleScaffold.prototype.fileList = function (input) {
|
||||
var output = [];
|
||||
for (var _i = 0, input_1 = input; _i < input_1.length; _i++) {
|
||||
var checkPath = input_1[_i];
|
||||
var files = glob.sync(checkPath, { dot: true })
|
||||
.map(function (g) { return g[0] == '/' ? g : path.join(process.cwd(), g); });
|
||||
var idx = checkPath.indexOf('*');
|
||||
var cleanCheckPath = checkPath;
|
||||
if (idx >= 0) {
|
||||
cleanCheckPath = checkPath.slice(0, idx - 1);
|
||||
}
|
||||
for (var _a = 0, files_1 = files; _a < files_1.length; _a++) {
|
||||
var file = files_1[_a];
|
||||
output.push({ base: cleanCheckPath, file: file });
|
||||
}
|
||||
}
|
||||
return output;
|
||||
};
|
||||
SimpleScaffold.prototype.getFileContents = function (filePath) {
|
||||
console.log(fs.readFileSync(filePath));
|
||||
return fs.readFileSync(filePath).toString();
|
||||
};
|
||||
SimpleScaffold.prototype.getOutputPath = function (file, basePath) {
|
||||
var out;
|
||||
if (typeof this.config.output === 'function') {
|
||||
out = this.config.output(file, basePath);
|
||||
}
|
||||
else {
|
||||
var outputDir = this.config.output + (this.config.createSubfolder ? "/" + this.config.name + "/" : '/');
|
||||
var idx = file.indexOf(basePath);
|
||||
var relativeFilePath = file;
|
||||
if (idx >= 0) {
|
||||
relativeFilePath = file.slice(idx + basePath.length + 1);
|
||||
}
|
||||
out = outputDir + relativeFilePath;
|
||||
}
|
||||
return this.parseLocals(out);
|
||||
};
|
||||
SimpleScaffold.prototype.writeFile = function (filePath, fileContents) {
|
||||
if (!fs.existsSync(path.dirname(filePath))) {
|
||||
fs.mkdirSync(path.dirname(filePath));
|
||||
}
|
||||
console.info('Writing file:', filePath);
|
||||
fs.writeFile(filePath, fileContents, { encoding: 'utf-8' }, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
};
|
||||
SimpleScaffold.prototype.run = function () {
|
||||
console.log("Generating scaffold: " + this.config.name + "...");
|
||||
var templates = this.fileList(this.config.templates);
|
||||
var fileConf, count = 0;
|
||||
for (var _i = 0, templates_1 = templates; _i < templates_1.length; _i++) {
|
||||
fileConf = templates_1[_i];
|
||||
count++;
|
||||
var file = fileConf.file, base = fileConf.base;
|
||||
var outputPath = this.getOutputPath(file, base);
|
||||
var contents = this.getFileContents(file);
|
||||
var outputContents = this.parseLocals(contents);
|
||||
this.writeFile(outputPath, outputContents);
|
||||
console.info('Parsing:', { file: file, base: base, outputPath: outputPath, outputContents: outputContents.replace("\n", "\\n") });
|
||||
}
|
||||
if (!count) {
|
||||
throw new Error('No files to scaffold!');
|
||||
}
|
||||
console.log('Done');
|
||||
};
|
||||
return SimpleScaffold;
|
||||
}());
|
||||
exports.default = SimpleScaffold;
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 2 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("fs");
|
||||
|
||||
/***/ }),
|
||||
/* 3 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("glob");
|
||||
|
||||
/***/ }),
|
||||
/* 4 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = require("handlebars");
|
||||
|
||||
/***/ }),
|
||||
/* 5 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var scaffold_1 = __webpack_require__(1);
|
||||
var path = __webpack_require__(0);
|
||||
var templateDir = path.join(process.cwd(), 'examples');
|
||||
new scaffold_1.default({
|
||||
templates: [templateDir + '/test-input/Component/**/*'],
|
||||
output: templateDir + '/test-output/no-create-subpath',
|
||||
createSubfolder: false,
|
||||
locals: {
|
||||
property: 'myProp',
|
||||
value: '"value"'
|
||||
}
|
||||
}).run();
|
||||
new scaffold_1.default({
|
||||
templates: [templateDir + '/test-input/Component/**/*'],
|
||||
output: templateDir + '/test-output',
|
||||
locals: {
|
||||
property: 'myProp',
|
||||
value: '"value"'
|
||||
}
|
||||
}).run();
|
||||
|
||||
|
||||
/***/ })
|
||||
/******/ ]);
|
||||
});
|
||||
//# sourceMappingURL=test.js.map
|
||||
1
dist/test.js.map
vendored
1
dist/test.js.map
vendored
File diff suppressed because one or more lines are too long
1
examples/test-input/Component/inner/inner-{{name}}.txt
Normal file
1
examples/test-input/Component/inner/inner-{{name}}.txt
Normal file
@@ -0,0 +1 @@
|
||||
{{name}}
|
||||
@@ -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}} } />
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
index.d.ts
vendored
21
index.d.ts
vendored
@@ -1,21 +0,0 @@
|
||||
declare namespace IScaffold {
|
||||
|
||||
export interface Config {
|
||||
name?: string
|
||||
templates: string[]
|
||||
output: string | ((path: string, base: string) => string)
|
||||
locals?: Locals
|
||||
createSubfolder?: boolean
|
||||
}
|
||||
|
||||
export interface Locals {
|
||||
[k: string]: string
|
||||
}
|
||||
|
||||
export interface FileRepr {
|
||||
base: string
|
||||
file: string
|
||||
}
|
||||
}
|
||||
|
||||
export default IScaffold
|
||||
196
jest.config.ts
Normal file
196
jest.config.ts
Normal 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"],
|
||||
}
|
||||
54
package.json
54
package.json
@@ -1,45 +1,41 @@
|
||||
{
|
||||
"name": "simple-scaffold",
|
||||
"version": "0.4.3",
|
||||
"version": "1.0.3",
|
||||
"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": {
|
||||
"command-line-args": "^5.0.2",
|
||||
"command-line-usage": "^5.0.5",
|
||||
"glob": "^7.1.2",
|
||||
"handlebars": "^4.0.11",
|
||||
"ts-loader": "^3.1.1",
|
||||
"typescript": "^2.6.1",
|
||||
"webpack": "^3.8.1",
|
||||
"webpack-dev-server": "^2.9.4",
|
||||
"webpack-node-externals": "^1.6.0"
|
||||
"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": {
|
||||
"@types/command-line-args": "^5.0.0",
|
||||
"@types/command-line-usage": "^5.0.1",
|
||||
"@types/glob": "^5.0.34",
|
||||
"@types/handlebars": "^4.0.36",
|
||||
"@types/node": "^8.0.50",
|
||||
"jest": "^22.0.4"
|
||||
},
|
||||
"jest": {
|
||||
"testPathIgnorePatterns": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
|
||||
112
scaffold.ts
112
scaffold.ts
@@ -1,112 +0,0 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import IScaffold from './index'
|
||||
import * as glob from 'glob'
|
||||
import * as handlebars from 'handlebars'
|
||||
|
||||
class SimpleScaffold {
|
||||
public config: IScaffold.Config
|
||||
public locals: IScaffold.Config['locals'] = {} as any
|
||||
|
||||
constructor(config: IScaffold.Config) {
|
||||
const DefaultConfig: IScaffold.Config = {
|
||||
name: 'scaffold',
|
||||
templates: [],
|
||||
output: process.cwd(),
|
||||
createSubfolder: true,
|
||||
}
|
||||
|
||||
this.config = { ...DefaultConfig, ...config }
|
||||
|
||||
const DefaultLocals = {
|
||||
Name: this.config.name![0].toUpperCase() + this.config.name!.slice(1),
|
||||
name: this.config.name![0].toLowerCase() + this.config.name!.slice(1)
|
||||
}
|
||||
|
||||
this.locals = { ...DefaultLocals, ...config.locals }
|
||||
}
|
||||
|
||||
private parseLocals(text: string): string {
|
||||
const template = handlebars.compile(text, {
|
||||
noEscape: true
|
||||
})
|
||||
return template(this.locals)
|
||||
}
|
||||
|
||||
private fileList(input: string[]): IScaffold.FileRepr[] {
|
||||
const output: IScaffold.FileRepr[] = []
|
||||
for (const checkPath of input) {
|
||||
const files = glob.sync(checkPath, { dot: true })
|
||||
.map(g => g[0] == '/' ? g : path.join(process.cwd(), g))
|
||||
const idx = checkPath.indexOf('*')
|
||||
let cleanCheckPath = checkPath
|
||||
if (idx >= 0) {
|
||||
cleanCheckPath = checkPath.slice(0, idx - 1)
|
||||
}
|
||||
for (const file of files) {
|
||||
output.push({ base: cleanCheckPath, file })
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private getFileContents(filePath: string): string {
|
||||
console.log(fs.readFileSync(filePath))
|
||||
return fs.readFileSync(filePath).toString()
|
||||
}
|
||||
|
||||
private getOutputPath(file: string, basePath: string): string {
|
||||
let out: string
|
||||
|
||||
if (typeof this.config.output === 'function') {
|
||||
out = this.config.output(file, basePath)
|
||||
} else {
|
||||
const outputDir = this.config.output + (this.config.createSubfolder ? `/${this.config.name}/` : '/')
|
||||
const idx = file.indexOf(basePath)
|
||||
let relativeFilePath = file
|
||||
if (idx >= 0) {
|
||||
relativeFilePath = file.slice(idx + basePath.length + 1)
|
||||
}
|
||||
out = outputDir + relativeFilePath
|
||||
}
|
||||
|
||||
return this.parseLocals(out)
|
||||
}
|
||||
|
||||
private writeFile(filePath: string, fileContents: string): void {
|
||||
if (!fs.existsSync(path.dirname(filePath))) {
|
||||
fs.mkdirSync(path.dirname(filePath))
|
||||
}
|
||||
console.info('Writing file:', filePath)
|
||||
fs.writeFile(filePath, fileContents, { encoding: 'utf-8' }, (err) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public run(): void {
|
||||
console.log(`Generating scaffold: ${this.config.name}...`)
|
||||
const templates = this.fileList(this.config.templates)
|
||||
|
||||
let fileConf, count = 0
|
||||
for (fileConf of templates) {
|
||||
count++
|
||||
const { file, base } = fileConf
|
||||
const outputPath = this.getOutputPath(file, base)
|
||||
const contents = this.getFileContents(file)
|
||||
const outputContents = this.parseLocals(contents)
|
||||
|
||||
this.writeFile(outputPath, outputContents)
|
||||
console.info('Parsing:', { file, base, outputPath, outputContents: outputContents.replace("\n", "\\n") })
|
||||
}
|
||||
|
||||
if (!count) {
|
||||
throw new Error('No files to scaffold!')
|
||||
}
|
||||
|
||||
console.log('Done')
|
||||
}
|
||||
}
|
||||
|
||||
export default SimpleScaffold
|
||||
102
src/cmd.ts
Normal file
102
src/cmd.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/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: "sub-folder-name-helper",
|
||||
aliases: ["sh"],
|
||||
description: "Default helper to apply to subfolder name when using `--create-sub-folder true`.",
|
||||
})
|
||||
.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()
|
||||
4
src/index.ts
Normal file
4
src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from "./scaffold"
|
||||
export * from "./types"
|
||||
import Scaffold from "./scaffold"
|
||||
export default Scaffold
|
||||
133
src/scaffold.ts
Normal file
133
src/scaffold.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import path from "path"
|
||||
|
||||
import {
|
||||
createDirIfNotExists,
|
||||
getOptionValueForFile,
|
||||
handleErr,
|
||||
log,
|
||||
pascalCase,
|
||||
isDir,
|
||||
removeGlob,
|
||||
makeRelativePath,
|
||||
registerHelpers,
|
||||
getTemplateGlobInfo,
|
||||
ensureFileExists,
|
||||
getFileList,
|
||||
getBasePath,
|
||||
copyFileTransformed,
|
||||
getTemplateFileInfo,
|
||||
logInitStep,
|
||||
logInputFile,
|
||||
} from "./utils"
|
||||
import { LogLevel, ScaffoldConfig } from "./types"
|
||||
|
||||
/**
|
||||
* Create a scaffold using given `options`.
|
||||
*
|
||||
* #### Create files
|
||||
* To create a file structure to output, use any directory and file structure you would like.
|
||||
* Inside folder names, file names or file contents, you may place `{{ var }}` where `var` is either
|
||||
* `name` which is the scaffold name you provided or one of the keys you provided in the `data` option.
|
||||
*
|
||||
* The contents and names will be replaced with the transformed values so you can use your original structure as a
|
||||
* boilerplate for other projects, components, modules, or even single files.
|
||||
*
|
||||
* #### Helpers
|
||||
* Helpers are functions you can use to transform your `{{ var }}` contents into other values without having to
|
||||
* pre-define the data and use a duplicated key. Common cases are transforming name-case format
|
||||
* (e.g. `MyName` → `my_name`), so these have been provided as defaults:
|
||||
*
|
||||
* | Helper name | Example code | Example output |
|
||||
* | ----------- | ----------------------- | -------------- |
|
||||
* | camelCase | `{{ camelCase name }}` | myName |
|
||||
* | snakeCase | `{{ snakeCase name }}` | my_name |
|
||||
* | startCase | `{{ startCase name }}` | My Name |
|
||||
* | kebabCase | `{{ kebabCase name }}` | my-name |
|
||||
* | hyphenCase | `{{ hyphenCase name }}` | my-name |
|
||||
* | pascalCase | `{{ pascalCase name }}` | MyName |
|
||||
* | upperCase | `{{ upperCase name }}` | MYNAME |
|
||||
* | lowerCase | `{{ lowerCase name }}` | myname |
|
||||
*
|
||||
* Any functions you provide in `helpers` option will also be available to you to make custom formatting as you see fit
|
||||
* (for example, formatting a date)
|
||||
*/
|
||||
export async function Scaffold({ ...options }: ScaffoldConfig) {
|
||||
options.output ??= process.cwd()
|
||||
|
||||
registerHelpers(options)
|
||||
try {
|
||||
options.data = { name: options.name, Name: pascalCase(options.name), ...options.data }
|
||||
logInitStep(options)
|
||||
for (let _template of options.templates) {
|
||||
try {
|
||||
const { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template } = await getTemplateGlobInfo(
|
||||
options,
|
||||
_template
|
||||
)
|
||||
await ensureFileExists(template, isDirOrGlob)
|
||||
const files = await getFileList(options, template)
|
||||
for (const inputFilePath of files) {
|
||||
if (await isDir(inputFilePath)) {
|
||||
continue
|
||||
}
|
||||
const relPath = makeRelativePath(path.dirname(removeGlob(inputFilePath).replace(nonGlobTemplate, "")))
|
||||
const basePath = getBasePath(relPath)
|
||||
logInputFile(options, {
|
||||
origTemplate,
|
||||
relPath,
|
||||
template,
|
||||
inputFilePath,
|
||||
nonGlobTemplate,
|
||||
basePath,
|
||||
isDirOrGlob,
|
||||
isGlob,
|
||||
})
|
||||
await handleTemplateFile(options, options.data, { templatePath: inputFilePath, basePath })
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleErr(e)
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
log(options, LogLevel.Error, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
async function handleTemplateFile(
|
||||
options: ScaffoldConfig,
|
||||
data: Record<string, string>,
|
||||
{ templatePath, basePath }: { templatePath: string; basePath: string }
|
||||
): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const { inputPath, outputPathOpt, outputDir, outputPath, exists } = await getTemplateFileInfo(options, data, {
|
||||
templatePath,
|
||||
basePath,
|
||||
})
|
||||
const overwrite = getOptionValueForFile(options, inputPath, data, options.overwrite ?? false)
|
||||
|
||||
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`
|
||||
)
|
||||
|
||||
await createDirIfNotExists(path.dirname(outputPath), options)
|
||||
|
||||
log(options, LogLevel.Info, `Writing to ${outputPath}`)
|
||||
await copyFileTransformed(options, data, { exists, overwrite, outputPath, inputPath })
|
||||
resolve()
|
||||
} catch (e: any) {
|
||||
handleErr(e)
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default Scaffold
|
||||
127
src/types.ts
Normal file
127
src/types.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
export enum LogLevel {
|
||||
None = 0,
|
||||
Debug = 1,
|
||||
Info = 2,
|
||||
Warning = 3,
|
||||
Error = 4,
|
||||
}
|
||||
|
||||
export type FileResponseFn<T> = (fullPath: string, basedir: string, basename: string) => T
|
||||
|
||||
export type FileResponse<T> = T | FileResponseFn<T>
|
||||
|
||||
export type DefaultHelperKeys =
|
||||
| "camelCase"
|
||||
| "snakeCase"
|
||||
| "startCase"
|
||||
| "kebabCase"
|
||||
| "hyphenCase"
|
||||
| "pascalCase"
|
||||
| "lowerCase"
|
||||
| "upperCase"
|
||||
|
||||
export type HelperKeys<T> = DefaultHelperKeys | T
|
||||
|
||||
export type Helper = (text: string) => string
|
||||
|
||||
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()
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* 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 |
|
||||
*/
|
||||
helpers?: Record<string, Helper>
|
||||
|
||||
/**
|
||||
* Default transformer to apply to subfolder name when using `createSubFolder: true`. Can be one of the default
|
||||
* helpers, or a custom one you provide to `helpers`. Defaults to `undefined`, which means no transformation is done.
|
||||
*/
|
||||
subFolderNameHelper?: DefaultHelperKeys | string
|
||||
}
|
||||
export interface ScaffoldCmdConfig {
|
||||
name: string
|
||||
templates: string[]
|
||||
output: string
|
||||
createSubFolder: boolean
|
||||
data?: Record<string, string>
|
||||
overwrite: boolean
|
||||
quiet: boolean
|
||||
verbose: LogLevel
|
||||
dryRun: boolean
|
||||
}
|
||||
331
src/utils.ts
Normal file
331
src/utils.ts
Normal file
@@ -0,0 +1,331 @@
|
||||
import path from "path"
|
||||
import { F_OK } from "constants"
|
||||
import { DefaultHelperKeys, FileResponse, FileResponseFn, Helper, LogLevel, ScaffoldConfig } from "./types"
|
||||
import camelCase from "lodash/camelCase"
|
||||
import snakeCase from "lodash/snakeCase"
|
||||
import kebabCase from "lodash/kebabCase"
|
||||
import startCase from "lodash/startCase"
|
||||
import Handlebars from "handlebars"
|
||||
import { promises as fsPromises } from "fs"
|
||||
import chalk from "chalk"
|
||||
const { stat, access, mkdir } = fsPromises
|
||||
|
||||
import { glob } from "glob"
|
||||
import { promisify } from "util"
|
||||
const { readFile, writeFile } = fsPromises
|
||||
|
||||
export const defaultHelpers: Record<DefaultHelperKeys, Helper> = {
|
||||
camelCase,
|
||||
snakeCase,
|
||||
startCase,
|
||||
kebabCase,
|
||||
hyphenCase: kebabCase,
|
||||
pascalCase,
|
||||
lowerCase: (text) => text.toLowerCase(),
|
||||
upperCase: (text) => text.toUpperCase(),
|
||||
}
|
||||
|
||||
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>(
|
||||
options: ScaffoldConfig,
|
||||
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(options, filePath, { isPath: true }).toString()),
|
||||
path.basename(handlebarsParse(options, filePath, { isPath: true }).toString())
|
||||
)
|
||||
}
|
||||
|
||||
export function handlebarsParse(
|
||||
options: ScaffoldConfig,
|
||||
templateBuffer: Buffer | string,
|
||||
{ isPath = false }: { isPath?: boolean } = {}
|
||||
) {
|
||||
const { data } = options
|
||||
try {
|
||||
let str = templateBuffer.toString()
|
||||
if (isPath) {
|
||||
str = str.replace(/\\/g, "/")
|
||||
}
|
||||
const parser = Handlebars.compile(str, { noEscape: true })
|
||||
let outputContents = parser(data)
|
||||
if (isPath && path.sep !== "/") {
|
||||
outputContents = outputContents.replace(/\//g, "\\")
|
||||
}
|
||||
return outputContents
|
||||
} catch {
|
||||
log(options, LogLevel.Warning, "Couldn't parse file with handlebars, returning original content")
|
||||
return templateBuffer
|
||||
}
|
||||
}
|
||||
|
||||
export async function pathExists(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
await access(filePath, F_OK)
|
||||
return true
|
||||
} catch (e: any) {
|
||||
if (e.code === "ENOENT") {
|
||||
return false
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export function pascalCase(s: string): string {
|
||||
return startCase(s).replace(/\s+/g, "")
|
||||
}
|
||||
|
||||
export async function isDir(path: string): Promise<boolean> {
|
||||
const tplStat = await stat(path)
|
||||
return tplStat.isDirectory()
|
||||
}
|
||||
|
||||
export function removeGlob(template: string) {
|
||||
return template.replace(/\*/g, "").replace(/(\/\/|\\\\)/g, path.sep)
|
||||
}
|
||||
|
||||
export function makeRelativePath(str: string): string {
|
||||
return str.startsWith(path.sep) ? str.slice(1) : str
|
||||
}
|
||||
|
||||
export function getBasePath(relPath: string) {
|
||||
return path
|
||||
.resolve(process.cwd(), relPath)
|
||||
.replace(process.cwd() + path.sep, "")
|
||||
.replace(process.cwd(), "")
|
||||
}
|
||||
|
||||
export async function getFileList(options: ScaffoldConfig, template: string) {
|
||||
return (
|
||||
await promisify(glob)(template, {
|
||||
dot: true,
|
||||
debug: options.verbose === LogLevel.Debug,
|
||||
nodir: true,
|
||||
})
|
||||
).map((f) => f.replace(/\//g, path.sep))
|
||||
}
|
||||
|
||||
export interface GlobInfo {
|
||||
nonGlobTemplate: string
|
||||
origTemplate: string
|
||||
isDirOrGlob: boolean
|
||||
isGlob: boolean
|
||||
template: string
|
||||
}
|
||||
|
||||
export async function getTemplateGlobInfo(options: ScaffoldConfig, template: string): Promise<GlobInfo> {
|
||||
const isGlob = glob.hasMagic(template)
|
||||
log(options, LogLevel.Debug, "before isDir", "isGlob:", isGlob, template)
|
||||
let _template = template
|
||||
const nonGlobTemplate = isGlob ? removeGlob(template) : template
|
||||
const isDirOrGlob = isGlob ? true : await isDir(template)
|
||||
log(options, LogLevel.Debug, "after isDir", isDirOrGlob)
|
||||
const _shouldAddGlob = !isGlob && isDirOrGlob
|
||||
const origTemplate = template
|
||||
if (_shouldAddGlob) {
|
||||
_template = path.join(template, "**", "*")
|
||||
}
|
||||
return { nonGlobTemplate, origTemplate, isDirOrGlob, isGlob, template: _template }
|
||||
}
|
||||
|
||||
export async function ensureFileExists(template: string, isGlob: boolean) {
|
||||
if (!isGlob && !(await pathExists(template))) {
|
||||
const err: NodeJS.ErrnoException = new Error(`ENOENT, no such file or directory ${template}`)
|
||||
err.code = "ENOENT"
|
||||
err.path = template
|
||||
err.errno = -2
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
export interface OutputFileInfo {
|
||||
inputPath: string
|
||||
outputPathOpt: string
|
||||
outputDir: string
|
||||
outputPath: string
|
||||
exists: boolean
|
||||
}
|
||||
|
||||
export async function getTemplateFileInfo(
|
||||
options: ScaffoldConfig,
|
||||
data: Record<string, string>,
|
||||
{ templatePath, basePath }: { templatePath: string; basePath: string }
|
||||
): Promise<OutputFileInfo> {
|
||||
const inputPath = path.resolve(process.cwd(), templatePath)
|
||||
const outputPathOpt = getOptionValueForFile(options, inputPath, data, options.output)
|
||||
const outputDir = getOutputDir(options, data, outputPathOpt, basePath)
|
||||
const outputPath = handlebarsParse(options, path.join(outputDir, path.basename(inputPath)), {
|
||||
isPath: true,
|
||||
}).toString()
|
||||
const exists = await pathExists(outputPath)
|
||||
return { inputPath, outputPathOpt, outputDir, outputPath, exists }
|
||||
}
|
||||
|
||||
export async function copyFileTransformed(
|
||||
options: ScaffoldConfig,
|
||||
data: Record<string, string>,
|
||||
{
|
||||
exists,
|
||||
overwrite,
|
||||
outputPath,
|
||||
inputPath,
|
||||
}: { exists: boolean; overwrite: boolean; outputPath: string; inputPath: string }
|
||||
) {
|
||||
if (!exists || overwrite) {
|
||||
if (exists && overwrite) {
|
||||
log(options, LogLevel.Info, `File ${outputPath} exists, overwriting`)
|
||||
}
|
||||
const templateBuffer = await readFile(inputPath)
|
||||
const outputContents = handlebarsParse(options, templateBuffer)
|
||||
|
||||
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`)
|
||||
}
|
||||
}
|
||||
|
||||
export function getOutputDir(
|
||||
options: ScaffoldConfig,
|
||||
data: Record<string, string>,
|
||||
outputPathOpt: string,
|
||||
basePath: string
|
||||
) {
|
||||
return path.resolve(
|
||||
process.cwd(),
|
||||
...([
|
||||
outputPathOpt,
|
||||
basePath,
|
||||
options.createSubFolder
|
||||
? options.subFolderNameHelper
|
||||
? handlebarsParse(options, `{{ ${options.subFolderNameHelper} name }}`)
|
||||
: options.name
|
||||
: undefined,
|
||||
].filter(Boolean) as string[])
|
||||
)
|
||||
}
|
||||
|
||||
export function logInputFile(
|
||||
options: ScaffoldConfig,
|
||||
{
|
||||
origTemplate,
|
||||
relPath,
|
||||
template,
|
||||
inputFilePath,
|
||||
nonGlobTemplate,
|
||||
basePath,
|
||||
isDirOrGlob,
|
||||
isGlob,
|
||||
}: {
|
||||
origTemplate: string
|
||||
relPath: string
|
||||
template: string
|
||||
inputFilePath: string
|
||||
nonGlobTemplate: string
|
||||
basePath: string
|
||||
isDirOrGlob: boolean
|
||||
isGlob: boolean
|
||||
}
|
||||
) {
|
||||
log(
|
||||
options,
|
||||
LogLevel.Debug,
|
||||
`\nprocess.cwd(): ${process.cwd()}`,
|
||||
`\norigTemplate: ${origTemplate}`,
|
||||
`\nrelPath: ${relPath}`,
|
||||
`\ntemplate: ${template}`,
|
||||
`\ninputFilePath: ${inputFilePath}`,
|
||||
`\nnonGlobTemplate: ${nonGlobTemplate}`,
|
||||
`\nbasePath: ${basePath}`,
|
||||
`\nisDirOrGlob: ${isDirOrGlob}`,
|
||||
`\nisGlob: ${isGlob}`,
|
||||
`\n`
|
||||
)
|
||||
}
|
||||
|
||||
export function logInitStep(options: ScaffoldConfig) {
|
||||
log(options, LogLevel.Debug, "Full config:", {
|
||||
name: options.name,
|
||||
templates: options.templates,
|
||||
output: options.output,
|
||||
createSubfolder: options.createSubFolder,
|
||||
data: options.data,
|
||||
overwrite: options.overwrite,
|
||||
quiet: options.quiet,
|
||||
subFolderTransformHelper: options.subFolderNameHelper,
|
||||
helpers: Object.keys(options.helpers ?? {}),
|
||||
verbose: `${options.verbose} (${Object.keys(LogLevel).find(
|
||||
(k) => (LogLevel[k as any] as unknown as number) === options.verbose!
|
||||
)})`,
|
||||
})
|
||||
log(options, LogLevel.Info, "Data:", options.data)
|
||||
}
|
||||
23
test.ts
23
test.ts
@@ -1,23 +0,0 @@
|
||||
import SimpleScaffold from './scaffold'
|
||||
import * as path from 'path'
|
||||
|
||||
const templateDir = path.join(process.cwd(), 'examples')
|
||||
|
||||
new SimpleScaffold({
|
||||
templates: [templateDir + '/test-input/Component/**/*'],
|
||||
output: templateDir + '/test-output/no-create-subpath',
|
||||
createSubfolder: false,
|
||||
locals: {
|
||||
property: 'myProp',
|
||||
value: '"value"'
|
||||
}
|
||||
}).run()
|
||||
|
||||
new SimpleScaffold({
|
||||
templates: [templateDir + '/test-input/Component/**/*'],
|
||||
output: templateDir + '/test-output',
|
||||
locals: {
|
||||
property: 'myProp',
|
||||
value: '"value"'
|
||||
}
|
||||
}).run()
|
||||
330
tests/scaffold.test.ts
Normal file
330
tests/scaffold.test.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
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"
|
||||
import { join } from "path"
|
||||
|
||||
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 fileStructSubdirTransformer = {
|
||||
input: {
|
||||
"{{name}}.txt": "Hello, my app is {{name}}",
|
||||
},
|
||||
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(join(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(join(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(join(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(join(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(join(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) => join("custom-output", `${basename.split(".")[0]}`),
|
||||
templates: ["input"],
|
||||
data: { value: "1" },
|
||||
verbose: 0,
|
||||
})
|
||||
const data = readFileSync(join(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(join(process.cwd(), "output"))
|
||||
const dir = readdirSync(join(process.cwd(), "output", "AppName"))
|
||||
const nestedDir = readdirSync(join(process.cwd(), "output", "AppName", "moreNesting"))
|
||||
expect(rootDir).toHaveProperty("length")
|
||||
expect(dir).toHaveProperty("length")
|
||||
expect(nestedDir).toHaveProperty("length")
|
||||
|
||||
const rootFile = readFileSync(join(process.cwd(), "output", "app_name-1.txt"))
|
||||
const oneDeepFile = readFileSync(join(process.cwd(), "output", "AppName/app_name-2.txt"))
|
||||
const twoDeepFile = readFileSync(join(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(join(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(join(process.cwd(), "output", "custom", `${key}.txt`))
|
||||
expect(file.toString()).toEqual(results[key as keyof typeof results])
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
describe(
|
||||
"transform subfolder",
|
||||
withMock(fileStructSubdirTransformer, () => {
|
||||
test("should work with no helper", async () => {
|
||||
await Scaffold({
|
||||
name: "app_name",
|
||||
output: "output",
|
||||
templates: ["input"],
|
||||
createSubFolder: true,
|
||||
verbose: 0,
|
||||
})
|
||||
|
||||
const data = readFileSync(join(process.cwd(), "output", "app_name/app_name.txt"))
|
||||
expect(data.toString()).toBe("Hello, my app is app_name")
|
||||
})
|
||||
|
||||
test("should work with default helper", async () => {
|
||||
await Scaffold({
|
||||
name: "app_name",
|
||||
output: "output",
|
||||
templates: ["input"],
|
||||
createSubFolder: true,
|
||||
verbose: 0,
|
||||
subFolderNameHelper: "upperCase",
|
||||
})
|
||||
|
||||
const data = readFileSync(join(process.cwd(), "output", "APP_NAME/app_name.txt"))
|
||||
expect(data.toString()).toBe("Hello, my app is app_name")
|
||||
})
|
||||
|
||||
test("should work with custom helper", async () => {
|
||||
await Scaffold({
|
||||
name: "app_name",
|
||||
output: "output",
|
||||
templates: ["input"],
|
||||
createSubFolder: true,
|
||||
verbose: 0,
|
||||
subFolderNameHelper: "test",
|
||||
helpers: {
|
||||
test: () => "REPLACED",
|
||||
},
|
||||
})
|
||||
|
||||
const data = readFileSync(join(process.cwd(), "output", "REPLACED/app_name.txt"))
|
||||
expect(data.toString()).toBe("Hello, my app is app_name")
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
54
tests/utils.test.ts
Normal file
54
tests/utils.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { handlebarsParse } from "../src/utils"
|
||||
import { ScaffoldConfig } from "../src/types"
|
||||
import path from "path"
|
||||
|
||||
const blankConf: ScaffoldConfig = {
|
||||
verbose: 0,
|
||||
name: "",
|
||||
output: "",
|
||||
templates: [],
|
||||
data: { name: "test" },
|
||||
}
|
||||
|
||||
describe("Utils", () => {
|
||||
describe("handlebarsParse", () => {
|
||||
let origSep: any
|
||||
describe("windows paths", () => {
|
||||
beforeAll(() => {
|
||||
origSep = path.sep
|
||||
Object.defineProperty(path, "sep", { value: "\\" })
|
||||
})
|
||||
afterAll(() => {
|
||||
Object.defineProperty(path, "sep", { value: origSep })
|
||||
})
|
||||
test("should work for windows paths", async () => {
|
||||
expect(handlebarsParse(blankConf, "C:\\exports\\{{name}}.txt", { isPath: true })).toEqual(
|
||||
"C:\\exports\\test.txt"
|
||||
)
|
||||
})
|
||||
})
|
||||
describe("non-windows paths", () => {
|
||||
beforeAll(() => {
|
||||
origSep = path.sep
|
||||
Object.defineProperty(path, "sep", { value: "/" })
|
||||
})
|
||||
afterAll(() => {
|
||||
Object.defineProperty(path, "sep", { value: origSep })
|
||||
})
|
||||
test("should work for non-windows paths", async () => {
|
||||
expect(handlebarsParse(blankConf, "/home/test/{{name}}.txt", { isPath: true })).toEqual("/home/test/test.txt")
|
||||
})
|
||||
})
|
||||
test("should not do path escaping on non-path compiles", async () => {
|
||||
expect(
|
||||
handlebarsParse(
|
||||
{ ...blankConf, data: { ...blankConf.data, escaped: "value" } },
|
||||
"/home/test/{{name}} \\{{escaped}}.txt",
|
||||
{
|
||||
isPath: false,
|
||||
}
|
||||
)
|
||||
).toEqual("/home/test/test {{escaped}}.txt")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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/,
|
||||
})
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user