Compare commits

...

51 Commits

Author SHA1 Message Date
Chen Asraf
12974b5561 v0.7.4 2021-09-26 11:37:02 +03:00
Chen Asraf
7f98d469a3 added --quiet flag 2021-09-26 11:28:38 +03:00
Chen Asraf
cd25b04886 Update README.md 2021-05-20 11:03:19 +03:00
Chen Asraf
5cd637f41f Merge pull request #4 from chenasraf/dependabot/npm_and_yarn/handlebars-4.7.7
Bump handlebars from 4.7.6 to 4.7.7
2021-05-20 10:55:41 +03:00
Chen Asraf
0a4467ae5f Merge pull request #5 from chenasraf/dependabot/npm_and_yarn/url-parse-1.5.1
Bump url-parse from 1.4.7 to 1.5.1
2021-05-20 10:55:33 +03:00
Chen Asraf
713a0ed44f Merge pull request #6 from chenasraf/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.20 to 4.17.21
2021-05-20 10:55:18 +03:00
Chen Asraf
edec2d1c26 Merge pull request #7 from chenasraf/dependabot/npm_and_yarn/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.8 to 2.8.9
2021-05-20 10:55:03 +03:00
dependabot[bot]
2e12907412 Bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-07 02:51:26 +00:00
Chen Asraf
813f706cf0 update readme 2021-04-19 22:30:22 +03:00
Chen Asraf
1bc2221472 update readme 2021-04-19 22:28:22 +03:00
Chen Asraf
f07affa124 add basename to output config function (fixes #3) 2021-04-19 22:24:21 +03:00
Chen Asraf
ce22a2c34c disable overwriting files + parse JSON for locals 2021-02-28 01:38:51 +02:00
Chen Asraf
7c0c347002 fix: binary files 2021-02-01 15:24:57 +02:00
Chen Asraf
977288ae5a build: upgrade packages 2021-02-01 15:24:26 +02:00
Chen Asraf
4afafa5a4a fix: support deeper file structure 2021-02-01 15:23:50 +02:00
Chen Asraf
7bee2a51c7 0.5.0 2019-02-27 16:53:42 +02:00
Chen Asraf
d4c049baaf v0.5.0 2019-02-27 16:53:33 +02:00
Chen Asraf
06590c4b6e Fixed output argument + updated README 2019-02-27 16:20:33 +02:00
Chen Asraf
c4f2dfb04f v0.4.5 2019-02-27 16:20:12 +02:00
Chen Asraf
a410b79195 Improved docs 2019-02-27 16:14:56 +02:00
Chen Asraf
71d544aff4 v0.4.4 2019-02-27 16:14:22 +02:00
Chen Asraf
20389d7b33 v0.4.3 2019-02-27 16:12:43 +02:00
Chen Asraf
d7a4362725 mapfile 2019-02-25 19:54:12 +02:00
Chen Asraf
0a2d7c08f3 v0.4.2 2019-02-25 18:17:15 +02:00
Chen Asraf
a92c471243 bugfixes 2019-02-25 18:17:03 +02:00
Chen Asraf
07b1c4b1f0 mapfile 2019-02-25 18:01:10 +02:00
Chen Asraf
ec91fbf639 v0.4.1 2019-02-25 18:00:39 +02:00
Chen Asraf
85aa9f953b Bugfix: dotfiles 2019-02-25 18:00:23 +02:00
Chen Asraf
d6195c6c1d added 'createSubFolder' option, cleaned up CMD file 2019-02-25 17:49:03 +02:00
Chen Asraf
b14e3d2d76 Update README.md 2018-01-15 02:25:29 +02:00
Chen Asraf
fa2ddca57e v0.3.1 2018-01-15 02:23:14 +02:00
Chen Asraf
686b0bf227 Update README.md 2018-01-15 02:23:05 +02:00
Chen Asraf
0be29dd89e v0.3.0 2018-01-15 02:19:23 +02:00
Chen Asraf
4f29a612a3 cleanups 2018-01-15 02:18:39 +02:00
Chen Asraf
14b60ffc79 output is optional 2018-01-15 02:16:51 +02:00
Chen Asraf
1275743764 Update README.md 2018-01-07 02:25:27 +02:00
Chen Asraf
b09299b432 Uodate README.md 2018-01-05 04:53:41 +02:00
Chen Asraf
45e8de3034 Rename 2018-01-05 04:52:52 +02:00
Chen Asraf
a3a77e2ab5 Update README.md 2018-01-05 04:52:26 +02:00
Chen Asraf
f36015962d v0.2.0 2018-01-05 04:45:31 +02:00
Chen Asraf
e391f8f68f Improve cmd script, add readme 2018-01-05 04:44:11 +02:00
Chen Asraf
4ca7c6acb3 Fixed cmd 2018-01-05 04:04:33 +02:00
Chen Asraf
0fd996413b Improve build 2018-01-05 03:53:43 +02:00
Chen Asraf
e64c0e4a45 Use handlebars, add cmd script in package.json 2018-01-05 03:35:00 +02:00
Chen Asraf
3e42ac5a95 Move all scripts to webpack, add wip cmd script for bin 2018-01-05 03:06:25 +02:00
Chen Asraf
eecec82aaa v0.1.5 2018-01-01 23:27:10 +02:00
Chen Asraf
6ec19fc4cd v0.1.4 2018-01-01 23:22:20 +02:00
Chen Asraf
a5776d6108 v0.1.3 2018-01-01 23:20:36 +02:00
32 changed files with 5472 additions and 2082 deletions

3
.gitignore vendored
View File

@@ -57,4 +57,5 @@ typings/
# dotenv environment variables file
.env
examples/test-output
examples/test-output/**/*
!examples/test-output/.gitkeep

3
.prettierrc Normal file
View File

@@ -0,0 +1,3 @@
{
"semi": false
}

24
.vscode/launch.json vendored
View File

@@ -4,18 +4,26 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Scaffold",
"type": "node",
"request": "launch",
"protocol": "inspector",
"cwd": "${workspaceFolder}",
"program": "${workspaceFolder}/test.ts",
"outFiles": [
"${workspaceRoot}/dist/test.js"
],
"env": {
"NODE_ENV": "develop"
},
"sourceMaps": true,
},
{
"type": "node",
"request": "attach",
"name": "Attach by Process ID",
"processId": "${command:PickProcess}"
},
{
"type": "node",
"request": "launch",
"name": "Debug Scaffold",
"protocol": "inspector",
"program": "${workspaceFolder}/dist/scaffold.js"
}
}
]
}

4
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"typescript.tsdk": "./node_modules/typescript/lib",
"npm.packageManager": "yarn"
}

47
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,47 @@
{
"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": [],
},
{
"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": [],
},
],
}

166
README.md
View File

@@ -1,12 +1,164 @@
# scaffolder
Scaffolder allows you to create your structured files based on templates.
# simple-scaffold
### How to use
Simple Scaffold allows you to create your structured files based on templates.
#### Install
You Scaffolder by using `npm`. Global flag is useful if you want easy cli access to it.
## Install
```
npm install -g scaffolder
You can either use it as a command line tool or import into your own code and run from there.
```bash
# npm
npm install [-g] simple-scaffold
# yarn
yarn [global] add simple-scaffold
```
## Use as a command line tool
### Command Line Options
```plaintext
Scaffold Generator
Generate scaffolds for your project based on file templates.
Usage: simple-scaffold scaffold-name [options]
Options
-n, --name string Component output name
-t, --templates File[] A glob pattern of template files to load.
A template file may be of any type and extension, and supports Handlebars as
a parsing engine for the file names and contents, so you may customize both
with variables from your configuration.
-o, --output File The output directory to put the new files in. They will attempt to maintain
their regular structure as they are found, if possible.
-l, --locals JSON string A JSON string for the template to use in parsing.
-w, --overwrite Boolean Whether to overwrite files when they are found to already exist. Default=true
-q, --quiet Boolean When set to true, logs will not output (including warnings and errors).
Default=false
-S, --create-sub-folder Boolean Whether to create a subdirectory with {{Name}} in the output directory.
Default=true
-h, --help Display this help message
```
You can add this as a script in your `package.json`:
```json
{
"scripts": {
"scaffold": "yarn simple-scaffold --template scaffolds/component/**/* --output src/components --locals 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}}>`, as per the Handlebars formatting rules.
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.
### Use in Node.js
You can also build the scaffold yourself, if you want to create more complex arguments or scaffold groups.
Simply pass a config object to the constructor, and invoke `run()` when you are ready to start.
The config takes similar arguments to the command line:
```javascript
const SimpleScaffold = require("simple-scaffold").default
const scaffold = new SimpleScaffold({
name: "component",
templates: [path.join(__dirname, "scaffolds", "component")],
output: path.join(__dirname, "src", "components"),
createSubFolder: true,
locals: {
property: "value",
},
}).run()
```
The exception in the config is that `output`, when used in Node directly, may also be passed a
function for each input file to output into a dynamic path:
```javascript
config.output = (fullPath, baseDir, baseName) => {
console.log({ fullPath, baseDir, baseName })
return [baseDir, baseName].join(path.sep)
}
```
## Example Scaffold Input
### Input directory structure
```plaintext
- project
- scaffold
- {{Name}}.js
- src
- components
- ...
```
#### project/scaffold/{{Name}}.js
```js
const React = require('react')
module.exports = class {{Name}} extends React.Component {
render() {
<div className="{{className}}">{{Name}} Component</div>
}
}
```
### Run Example
```bash
simple-scaffold MyComponent \
-t project/scaffold/**/* \
-o src/components \
-l className=my-component
```
## Example Scaffold Output
### Output directory structure
```plaintext
- project
- src
- components
- MyComponent
- MyComponent.js
- ...
```
With `createSubfolder = false`:
```plaintext
- project
- src
- components
- MyComponent.js
- ...
```
#### 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>
}
}
```

135
cmd.ts Normal file
View File

@@ -0,0 +1,135 @@
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
typeLabel?: string
}
function localsParser(content: string) {
return JSON.parse(content)
}
function filePathParser(content: string) {
if (content.startsWith("/")) {
return content
}
return [process.cwd(), content].join(path.sep)
}
function booleanParser(text: string) {
return text && text.trim().length
? ["true", "1", "on"].includes(text.trim())
: true
}
const defs: Def[] = [
{
name: "name",
alias: "n",
type: String,
description: "Component output name",
defaultOption: true,
},
{
name: "templates",
alias: "t",
type: filePathParser,
typeLabel: "{underline File}[]",
description: `A glob pattern of template files to load.\nA template file may be of any type and extension, and supports Handlebars as a parsing engine for the file names and contents, so you may customize both with variables from your configuration.`,
multiple: true,
},
{
name: "output",
alias: "o",
type: filePathParser,
typeLabel: "{underline File}",
description: `The output directory to put the new files in. They will attempt to maintain their regular structure as they are found, if possible.`,
},
{
name: "locals",
alias: "l",
description: `A JSON string for the template to use in parsing.`,
typeLabel: "{underline JSON string}",
type: localsParser,
},
{
name: "overwrite",
alias: "w",
description: `Whether to overwrite files when they are found to already exist. {bold Default=true}`,
type: booleanParser,
typeLabel: "{underline Boolean}",
defaultValue: true,
},
{
name: "quiet",
alias: "q",
description:
"When set to {bold true}, logs will not output (including warnings and errors). {bold Default=false}",
type: booleanParser,
typeLabel: "{underline Boolean}",
defaultValue: false,
},
{
name: "create-sub-folder",
alias: "S",
typeLabel: "{underline Boolean}",
description:
"Whether to create a subdirectory with \\{\\{Name\\}\\} in the {underline output} directory. {bold Default=true}",
type: booleanParser,
defaultValue: true,
},
{
name: "help",
alias: "h",
type: Boolean,
description: "Display this help message",
},
]
const args = cliArgs(defs, { camelCase: true }) as Omit<
IScaffold.Config,
"createSubFolder"
> & {
help: boolean
createSubFolder: boolean
}
const help = [
{
header: "Scaffold Generator",
content: `Generate scaffolds for your project based on file templates.\nUsage: {bold simple-scaffold} {underline scaffold-name} {underline [options]}`,
},
{ header: "Options", optionList: defs },
]
if (args.createSubFolder === null) {
args.createSubFolder = true
}
if (args.quiet === null) {
args.quiet = true
}
if (args.help || !args.name) {
console.log(cliUsage(help))
process.exit(0)
}
if (!args.quiet) {
console.info("Config:", args)
}
new SimpleScaffold({
name: args.name,
templates: args.templates,
output: args.output,
locals: args.locals,
createSubfolder: args.createSubFolder,
overwrite: args.overwrite,
quiet: args.quiet,
}).run()

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

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

3
dist/cmd.js vendored Executable file

File diff suppressed because one or more lines are too long

1
dist/cmd.js.map vendored Executable file

File diff suppressed because one or more lines are too long

30
dist/index.d.ts vendored Executable file
View File

@@ -0,0 +1,30 @@
declare namespace IScaffold {
class SimpleScaffold {
constructor(config: Config)
run(): void
}
export interface Config {
name?: string
templates: string[]
output:
| string
| ((fullPath: string, basedir: string, basename: string) => string)
locals?: Locals
createSubfolder?: boolean
overwrite?: boolean | ((path: string) => boolean)
quiet?: boolean
}
export interface Locals {
[k: string]: string
}
export interface FileRepr {
base: string
file: string
}
}
export default IScaffold.SimpleScaffold
export { IScaffold }

2
dist/index.js vendored Executable file
View File

@@ -0,0 +1,2 @@
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.library=e():t.library=e()}(global,(function(){return(()=>{"use strict";var t={493:function(t,e,o){var r=this&&this.__assign||function(){return(r=Object.assign||function(t){for(var e,o=1,r=arguments.length;o<r;o++)for(var i in e=arguments[o])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)},i=this&&this.__spreadArrays||function(){for(var t=0,e=0,o=arguments.length;e<o;e++)t+=arguments[e].length;var r=Array(t),i=0;for(e=0;e<o;e++)for(var n=arguments[e],s=0,a=n.length;s<a;s++,i++)r[i]=n[s];return r};Object.defineProperty(e,"__esModule",{value:!0});var n=o(747),s=o(622),a=o(878),l=o(778),f=function(){function t(t){this.locals={};var e={name:"scaffold",templates:[],output:process.cwd(),createSubfolder:!0,overwrite:!0,quiet:!1};this.config=r(r({},e),t);var o={Name:this.config.name[0].toUpperCase()+this.config.name.slice(1),name:this.config.name[0].toLowerCase()+this.config.name.slice(1)};this.locals=r(r({},o),t.locals)}return t.prototype.parseLocals=function(t){try{return l.compile(t,{noEscape:!0})(this.locals)}catch(e){return this.warn("Problem using Handlebars, returning unmodified content"),t}},t.prototype.fileList=function(t){for(var e=[],o=0,r=t;o<r.length;o++){var i=r[o],n=a.sync(i,{dot:!0}).map((function(t){return"/"==t[0]?t:s.join(process.cwd(),t)})),l=i.indexOf("*"),f=i;l>=0&&(f=i.slice(0,l-1));for(var c=0,p=n;c<p.length;c++){var u=p[c];e.push({base:f,file:u})}}return e},t.prototype.getFileContents=function(t){return this.log(n.readFileSync(t)),n.readFileSync(t).toString()},t.prototype.getOutputPath=function(t,e){var o;if("function"==typeof this.config.output)o=this.config.output(t,e,s.basename(t));else{var r=this.config.output+(this.config.createSubfolder?"/"+this.config.name+"/":"/"),i=t.indexOf(e),n=t;i>=0&&(n=t!==e?t.slice(i+e.length+1):s.basename(t)),o=r+n}return this.parseLocals(o)},t.prototype.writeFile=function(t,e){var o=s.dirname(t);this.writeDirectory(o,t),n.writeFile(t,e,{encoding:"utf-8"},(function(t){if(t)throw t}))},t.prototype.shouldWriteFile=function(t){var e,o,r="boolean"==typeof this.config.overwrite?this.config.overwrite:null===(o=(e=this.config).overwrite)||void 0===o?void 0:o.call(e,t);return!n.existsSync(t)||!1!==r},t.prototype.run=function(){this.log("Generating scaffold: "+this.config.name+"...");var t,e=this.fileList(this.config.templates),o=0;this.log("Template files:",e);for(var r=0,i=e;r<i.length;r++){t=i[r];var s=void 0,a=void 0,l=void 0,f=void 0,c=void 0;try{if(o++,f=t.file,c=t.base,s=this.getOutputPath(f,c),n.lstatSync(f).isDirectory()){this.writeDirectory(s,f);continue}a=this.getFileContents(f),l=this.parseLocals(a),this.shouldWriteFile(s)?(this.info("Writing:",{file:f,base:c,outputPath:s,outputContents:l.replace("\n","\\n")}),this.writeFile(s,l)):this.log("Skipping file "+s)}catch(t){throw this.error("Error while processing file:",{file:f,base:c,contents:a,outputPath:s,outputContents:l}),t}}if(!o)throw new Error("No files to scaffold!");this.log("Done")},t.prototype.writeDirectory=function(t,e){var o=s.dirname(t);n.existsSync(o)||this.writeDirectory(o,t),n.existsSync(t)||(this.info("Creating directory:",{file:e,outputPath:t}),n.mkdirSync(t))},t.prototype._log=function(t){for(var e=[],o=1;o<arguments.length;o++)e[o-1]=arguments[o];if(!this.config.quiet){var r=console[t];r.apply(void 0,e)}},t.prototype.log=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];this._log.apply(this,i(["log"],t))},t.prototype.info=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];this._log.apply(this,i(["info"],t))},t.prototype.warn=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];this._log.apply(this,i(["warn"],t))},t.prototype.error=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];this._log.apply(this,i(["error"],t))},t}();e.default=f},747:t=>{t.exports=require("fs")},878:t=>{t.exports=require("glob")},778:t=>{t.exports=require("handlebars")},622:t=>{t.exports=require("path")}},e={};return function o(r){if(e[r])return e[r].exports;var i=e[r]={exports:{}};return t[r].call(i.exports,i,i.exports,o),i.exports}(493)})()}));
//# sourceMappingURL=index.js.map

1
dist/index.js.map vendored Executable file

File diff suppressed because one or more lines are too long

21
dist/scaffold.d.ts vendored Executable file
View File

@@ -0,0 +1,21 @@
/// <reference types="node" />
import { IScaffold } from "./index.d";
declare class SimpleScaffold {
config: IScaffold.Config;
locals: IScaffold.Config["locals"];
constructor(config: IScaffold.Config);
private parseLocals;
private fileList;
private getFileContents;
private getOutputPath;
private writeFile;
private shouldWriteFile;
run(): void;
private writeDirectory;
_log(method: keyof typeof console, ...args: any[]): void;
log(...args: any[]): void;
info(...args: any[]): void;
warn(...args: any[]): void;
error(...args: any[]): void;
}
export default SimpleScaffold;

1
dist/scaffold.js vendored
View File

@@ -1 +0,0 @@
!function(t){function e(n){if(o[n])return o[n].exports;var i=o[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var o={};e.m=t,e.c=o,e.d=function(t,o,n){e.o(t,o)||Object.defineProperty(t,o,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var o=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(o,"a",o),o},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=0)}([function(t,e,o){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var n=o(1),i=(o.n(n),o(2)),s=(o.n(i),function(){function t(t){if(this.locals={},this.scaffoldName=process.argv[2],this.DefaultConfig={templates:[],output:i.resolve(process.cwd()),locals:{Name:this.scaffoldName[0].toUpperCase()+this.scaffoldName.slice(1),name:this.scaffoldName[0].toLowerCase()+this.scaffoldName.slice(1)}},!this.scaffoldName)throw new Error("Must provide scaffold name");this.config=Object.assign({},this.DefaultConfig,t),this.locals=Object.assign({},this.DefaultConfig.locals,t.locals),console.info("Config loaded:",this.config),console.info("Locals:",this.locals)}return t.prototype.parseLocals=function(t){var e=this,o=t.toString(),n=/{[%]\s*([^%{}]+)\s*[%]}/gi;return o.replace(n,function(t,o){return e.locals[o]})},t.prototype.getFileList=function(t){var e=this,o=[];return t.forEach(function(t){var s=n.lstatSync(t);if(s.isFile())console.info("pushing",t),o.push(t);else if(s.isDirectory()){console.info("going into dir",t);var r=n.readdirSync(t).map(function(e){return i.join(t,e)});o=o.concat(e.getFileList(r))}}),o},t.prototype.getFileContents=function(t){return n.readFileSync(t).toString()},t.prototype.getOutputPath=function(t){var e;return e="function"==typeof this.config.output?this.config.output(t):this.config.output+"/"+this.scaffoldName+"/"+i.basename(t),this.parseLocals(e)},t.prototype.writeFile=function(t,e){n.existsSync(i.dirname(t))||n.mkdirSync(i.dirname(t)),console.info("Writing file:",t),n.writeFileSync(t,e,{encoding:"utf-8"})},t.prototype.run=function(){var t=this,e=this.getFileList(this.config.templates);console.info(e),e.forEach(function(e){var o=t.getOutputPath(e),n=t.getFileContents(e),i=t.parseLocals(n);t.writeFile(o,i)})},t}());exports.default=s},function(t,e){t.exports=require("fs")},function(t,e){t.exports=require("path")}]);

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

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

2
dist/test.js vendored Executable file
View File

@@ -0,0 +1,2 @@
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.library=e():t.library=e()}(global,(function(){return(()=>{"use strict";var t={493:function(t,e,o){var r=this&&this.__assign||function(){return(r=Object.assign||function(t){for(var e,o=1,r=arguments.length;o<r;o++)for(var i in e=arguments[o])Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i]);return t}).apply(this,arguments)},i=this&&this.__spreadArrays||function(){for(var t=0,e=0,o=arguments.length;e<o;e++)t+=arguments[e].length;var r=Array(t),i=0;for(e=0;e<o;e++)for(var n=arguments[e],s=0,a=n.length;s<a;s++,i++)r[i]=n[s];return r};Object.defineProperty(e,"__esModule",{value:!0});var n=o(747),s=o(622),a=o(878),l=o(778),p=function(){function t(t){this.locals={};var e={name:"scaffold",templates:[],output:process.cwd(),createSubfolder:!0,overwrite:!0,quiet:!1};this.config=r(r({},e),t);var o={Name:this.config.name[0].toUpperCase()+this.config.name.slice(1),name:this.config.name[0].toLowerCase()+this.config.name.slice(1)};this.locals=r(r({},o),t.locals)}return t.prototype.parseLocals=function(t){try{return l.compile(t,{noEscape:!0})(this.locals)}catch(e){return this.warn("Problem using Handlebars, returning unmodified content"),t}},t.prototype.fileList=function(t){for(var e=[],o=0,r=t;o<r.length;o++){var i=r[o],n=a.sync(i,{dot:!0}).map((function(t){return"/"==t[0]?t:s.join(process.cwd(),t)})),l=i.indexOf("*"),p=i;l>=0&&(p=i.slice(0,l-1));for(var u=0,c=n;u<c.length;u++){var f=c[u];e.push({base:p,file:f})}}return e},t.prototype.getFileContents=function(t){return this.log(n.readFileSync(t)),n.readFileSync(t).toString()},t.prototype.getOutputPath=function(t,e){var o;if("function"==typeof this.config.output)o=this.config.output(t,e,s.basename(t));else{var r=this.config.output+(this.config.createSubfolder?"/"+this.config.name+"/":"/"),i=t.indexOf(e),n=t;i>=0&&(n=t!==e?t.slice(i+e.length+1):s.basename(t)),o=r+n}return this.parseLocals(o)},t.prototype.writeFile=function(t,e){var o=s.dirname(t);this.writeDirectory(o,t),n.writeFile(t,e,{encoding:"utf-8"},(function(t){if(t)throw t}))},t.prototype.shouldWriteFile=function(t){var e,o,r="boolean"==typeof this.config.overwrite?this.config.overwrite:null===(o=(e=this.config).overwrite)||void 0===o?void 0:o.call(e,t);return!n.existsSync(t)||!1!==r},t.prototype.run=function(){this.log("Generating scaffold: "+this.config.name+"...");var t,e=this.fileList(this.config.templates),o=0;this.log("Template files:",e);for(var r=0,i=e;r<i.length;r++){t=i[r];var s=void 0,a=void 0,l=void 0,p=void 0,u=void 0;try{if(o++,p=t.file,u=t.base,s=this.getOutputPath(p,u),n.lstatSync(p).isDirectory()){this.writeDirectory(s,p);continue}a=this.getFileContents(p),l=this.parseLocals(a),this.shouldWriteFile(s)?(this.info("Writing:",{file:p,base:u,outputPath:s,outputContents:l.replace("\n","\\n")}),this.writeFile(s,l)):this.log("Skipping file "+s)}catch(t){throw this.error("Error while processing file:",{file:p,base:u,contents:a,outputPath:s,outputContents:l}),t}}if(!o)throw new Error("No files to scaffold!");this.log("Done")},t.prototype.writeDirectory=function(t,e){var o=s.dirname(t);n.existsSync(o)||this.writeDirectory(o,t),n.existsSync(t)||(this.info("Creating directory:",{file:e,outputPath:t}),n.mkdirSync(t))},t.prototype._log=function(t){for(var e=[],o=1;o<arguments.length;o++)e[o-1]=arguments[o];if(!this.config.quiet){var r=console[t];r.apply(void 0,e)}},t.prototype.log=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];this._log.apply(this,i(["log"],t))},t.prototype.info=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];this._log.apply(this,i(["info"],t))},t.prototype.warn=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];this._log.apply(this,i(["warn"],t))},t.prototype.error=function(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];this._log.apply(this,i(["error"],t))},t}();e.default=p},743:(t,e,o)=>{Object.defineProperty(e,"__esModule",{value:!0});var r=o(493),i=o(622).join(process.cwd(),"examples");new r.default({templates:[i+"/test-input/Component/**/*"],output:i+"/test-output/no-create-subpath",createSubfolder:!1,locals:{property:"myProp",value:'"value"'}}).run(),new r.default({templates:[i+"/test-input/Component/**/*"],output:i+"/test-output",locals:{property:"myProp",value:'"value"'}}).run(),new r.default({templates:[i+"/test-input/Component/**/*"],output:function(t,e,o){return console.log({file:t,basedir:e,basename:o}),t},locals:{property:"myProp",value:'"value"'}}).run()},747:t=>{t.exports=require("fs")},878:t=>{t.exports=require("glob")},778:t=>{t.exports=require("handlebars")},622:t=>{t.exports=require("path")}},e={};return function o(r){if(e[r])return e[r].exports;var i=e[r]={exports:{}};return t[r].call(i.exports,i,i.exports,o),i.exports}(743)})()}));
//# sourceMappingURL=test.js.map

1
dist/test.js.map vendored Executable file

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,13 +0,0 @@
import * as React from 'react'
class {%Name%} extends React.Component {
private {%property%}
constructor() {
this.{%property%} = {%value%}
}
<div className={css.{%Name%}} />
}
export default {%Name%}

View File

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

View File

31
index.d.ts vendored
View File

@@ -1,15 +1,30 @@
export namespace IScaffold {
declare namespace IScaffold {
class SimpleScaffold {
constructor(config: Config)
run(): void
}
export interface IConfig {
export interface Config {
name?: string
templates: string[]
output: string | ((path: string) => string)
locals?: any
output:
| string
| ((fullPath: string, basedir: string, basename: string) => string)
locals?: Locals
createSubfolder?: boolean
overwrite?: boolean | ((path: string) => boolean)
quiet?: boolean
}
export interface IReplacement {
find: string | RegExp
replace(): string
[other: string]: any
export interface Locals {
[k: string]: string
}
export interface FileRepr {
base: string
file: string
}
}
export default IScaffold.SimpleScaffold
export { IScaffold }

2
index.js Normal file
View File

@@ -0,0 +1,2 @@
const SimpleScaffold = require('./dist')
module.exports = SimpleScaffold

View File

@@ -1,26 +1,47 @@
{
"name": "simple-scaffold",
"version": "0.1.2",
"version": "0.7.4",
"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/scaffold.js",
"bin": "./dist/cmd.js",
"types": "index.d.ts",
"scripts": {
"build": "webpack -p && chmod +x ./dist/scaffold.js",
"build": "NODE_ENV=${NODE_ENV:-production} webpack && chmod -R +x ./dist",
"prepublishOnly": "yarn build",
"dev": "webpack --watch",
"start": "node ./dist/scaffold.js",
"test": "yarn build && node ./scripts/test.js"
"start": "node dist/scaffold.js",
"test": "node dist/test.js",
"cmd": "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": "^6.1.1",
"glob": "^7.1.3",
"handlebars": "^4.1.0"
},
"files": [
"./dist/scaffold.js"
],
"devDependencies": {
"@types/node": "^8.0.50",
"ts-loader": "^3.1.1",
"typescript": "^2.6.1",
"webpack": "^3.8.1",
"webpack-dev-server": "^2.9.4"
"@types/command-line-args": "^5.0.0",
"@types/command-line-usage": "^5.0.1",
"@types/glob": "^7.1.1",
"@types/node": "^14.14.22",
"copy-webpack-plugin": "^7.0.0",
"jest": "^26.6.3",
"ts-loader": "^8.0.14",
"typescript": "^4.1.3",
"webpack": "^5.19.0",
"webpack-cli": "^4.4.0",
"webpack-dev-server": "^3.2.1",
"webpack-node-externals": "^2.5.2"
},
"jest": {
"testPathIgnorePatterns": [
"node_modules",
"dist"
]
}
}

View File

@@ -1,88 +1,198 @@
import * as fs from 'fs'
import * as path from 'path'
import { IScaffold } from './index'
import * as fs from "fs"
import * as path from "path"
import { IScaffold } from "./index.d"
import * as glob from "glob"
import * as handlebars from "handlebars"
class SimpleScaffold {
private config: IScaffold.IConfig
private locals = {} as any
public scaffoldName = process.argv[2]
private DefaultConfig: IScaffold.IConfig = {
templates: [],
output: path.resolve(process.cwd()),
locals: {
Name: this.scaffoldName[0].toUpperCase() + this.scaffoldName.slice(1),
name: this.scaffoldName[0].toLowerCase() + this.scaffoldName.slice(1)
}
}
public config: IScaffold.Config
public locals: IScaffold.Config["locals"] = {} as any
constructor(config: IScaffold.IConfig) {
if (!this.scaffoldName) {
throw new Error('Must provide scaffold name')
constructor(config: IScaffold.Config) {
const DefaultConfig: IScaffold.Config = {
name: "scaffold",
templates: [],
output: process.cwd(),
createSubfolder: true,
overwrite: true,
quiet: false,
}
this.config = (Object as any).assign({}, this.DefaultConfig, config)
this.locals = (Object as any).assign({}, this.DefaultConfig.locals, config.locals)
console.info('Config loaded:', this.config)
console.info('Locals:', this.locals)
this.config = { ...DefaultConfig, ...config }
const DefaultLocals = {
// TODO improve
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 {
let out = text.toString()
const pattern = /{[%]\s*([^%{}]+)\s*[%]}/gi
return out.replace(pattern, (match: string, $1: string) => this.locals[$1])
try {
const template = handlebars.compile(text, {
noEscape: true,
})
return template(this.locals)
} catch (e) {
this.warn("Problem using Handlebars, returning unmodified content")
return text
}
}
private getFileList(pathList: string[]): string[] {
let outList: string[] = []
pathList.forEach((checkPath: string) => {
const stat = fs.lstatSync(checkPath)
if (stat.isFile()) {
console.info('pushing', checkPath)
outList.push(checkPath)
} else if (stat.isDirectory()) {
console.info('going into dir', checkPath)
const innerFiles = fs.readdirSync(checkPath).map(p => path.join(checkPath, p))
outList = outList.concat(this.getFileList(innerFiles))
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)
}
})
return outList
for (const file of files) {
output.push({ base: cleanCheckPath, file })
}
}
return output
}
private getFileContents(filePath: string): string {
this.log(fs.readFileSync(filePath))
return fs.readFileSync(filePath).toString()
}
private getOutputPath(file: string): string {
let out
private getOutputPath(file: string, basePath: string): string {
let out: string
if (typeof this.config.output === 'function') {
out = this.config.output(file)
if (typeof this.config.output === "function") {
out = this.config.output(file, basePath, path.basename(file))
} else {
out = this.config.output + `/${this.scaffoldName}/` + path.basename(file)
const outputDir =
this.config.output +
(this.config.createSubfolder ? `/${this.config.name}/` : "/")
const idx = file.indexOf(basePath)
let relativeFilePath = file
if (idx >= 0) {
if (file !== basePath) {
relativeFilePath = file.slice(idx + basePath.length + 1)
} else {
relativeFilePath = path.basename(file)
}
}
out = outputDir + relativeFilePath
}
return this.parseLocals(out)
}
private writeFile(filePath: string, fileContents: string): void {
if (!fs.existsSync(path.dirname(filePath))) {
fs.mkdirSync(path.dirname(filePath))
}
console.info('Writing file:', filePath)
fs.writeFileSync(filePath, fileContents, { encoding: 'utf-8' })
const baseDir = path.dirname(filePath)
this.writeDirectory(baseDir, filePath)
fs.writeFile(filePath, fileContents, { encoding: "utf-8" }, (err) => {
if (err) {
throw err
}
})
}
public run() {
const inputFiles = this.getFileList(this.config.templates)
console.info(inputFiles)
inputFiles.forEach((file: string) => {
const outputPath = this.getOutputPath(file)
const contents = this.getFileContents(file)
const outputContents = this.parseLocals(contents)
this.writeFile(outputPath, outputContents)
})
private shouldWriteFile(filePath: string) {
const overwrite =
typeof this.config.overwrite === "boolean"
? this.config.overwrite
: this.config.overwrite?.(filePath)
const exists = fs.existsSync(filePath)
return !exists || overwrite !== false
}
public run(): void {
this.log(`Generating scaffold: ${this.config.name}...`)
const templates = this.fileList(this.config.templates)
let fileConf,
count = 0
this.log("Template files:", templates)
for (fileConf of templates) {
let outputPath, contents, outputContents, file, base
try {
count++
file = fileConf.file
base = fileConf.base
outputPath = this.getOutputPath(file, base)
if (fs.lstatSync(file).isDirectory()) {
this.writeDirectory(outputPath, file)
continue
}
contents = this.getFileContents(file)
outputContents = this.parseLocals(contents)
if (this.shouldWriteFile(outputPath)) {
this.info("Writing:", {
file,
base,
outputPath,
outputContents: outputContents.replace("\n", "\\n"),
})
this.writeFile(outputPath, outputContents)
} else {
this.log(`Skipping file ${outputPath}`)
}
} catch (e) {
this.error("Error while processing file:", {
file,
base,
contents,
outputPath,
outputContents,
})
throw e
}
}
if (!count) {
throw new Error("No files to scaffold!")
}
this.log("Done")
}
private writeDirectory(outputPath: string, file: any): void {
const parent = path.dirname(outputPath)
if (!fs.existsSync(parent)) {
this.writeDirectory(parent, outputPath)
}
if (!fs.existsSync(outputPath)) {
this.info("Creating directory:", {
file,
outputPath,
})
fs.mkdirSync(outputPath)
}
}
_log(method: keyof typeof console, ...args: any[]): void {
if (this.config.quiet) {
return
}
const fn = console[method] as (...a: any[]) => void
fn(...args)
}
log(...args: any[]): void {
this._log("log", ...args)
}
info(...args: any[]): void {
this._log("info", ...args)
}
warn(...args: any[]): void {
this._log("warn", ...args)
}
error(...args: any[]): void {
this._log("error", ...args)
}
}
exports.default = SimpleScaffold
export default SimpleScaffold

View File

@@ -1,12 +0,0 @@
const Scaffolder = require('../dist/scaffold').default
const templateDir = process.cwd() + '/examples'
const scf = new Scaffolder({
templates: [templateDir + '/test-input/Component'],
output: templateDir + '/test-output',
locals: {
property: 'myProp',
value: '"value"'
}
}).run()

35
test.ts Normal file
View File

@@ -0,0 +1,35 @@
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()
new SimpleScaffold({
templates: [templateDir + "/test-input/Component/**/*"],
output: (file, basedir, basename) => {
console.log({ file, basedir, basename })
return file
},
locals: {
property: "myProp",
value: '"value"',
},
}).run()

View File

@@ -1,9 +1,10 @@
{
"compilerOptions": {
"target": "es5",
"module": "es2015",
"module": "commonjs",
"lib": ["es2017", "es6"],
"declaration": true,
"outDir": "./dist",
"outDir": "dist",
"strict": true,
"sourceMap": true
},

View File

@@ -1,22 +1,46 @@
const path = require('path')
const path = require("path")
const webpack = require("webpack")
const nodeExternals = require("webpack-node-externals")
const CopyPlugin = require("copy-webpack-plugin")
module.exports = {
target: 'node',
entry: './scaffold.ts',
devtool:
process.env.NODE_ENV === "develop" ? "inline-source-map" : "source-map",
target: "node",
entry: {
index: "./scaffold.ts",
test: "./test.ts",
cmd: "./cmd.ts",
},
output: {
filename: 'scaffold.js',
path: path.resolve(__dirname, 'dist')
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
devtoolModuleFilenameTemplate: "[absolute-resource-path]",
library: "library",
libraryTarget: "umd",
},
resolve: {
extensions: ['.ts']
extensions: [".ts"],
},
externals: [nodeExternals()],
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: ['./examples']
}
]
}
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/],
}),
new CopyPlugin({ patterns: ["index.d.ts"] }),
],
}

6649
yarn.lock

File diff suppressed because it is too large Load Diff