├── .gitignore ├── LICENSE ├── README.md ├── build ├── build.js ├── package.json ├── test.js └── webpack.config.js ├── package.json ├── src ├── declarations.d.ts ├── e2e-tabs.ts ├── e2e.ts ├── generator-options.ts ├── generator.spec.ts ├── generator.ts ├── index.spec.ts ├── index.ts ├── project-structure-options.ts ├── tab-generator.ts ├── utils.spec.ts └── utils.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | e2e 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # various 41 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ionic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ionic-app-generators 2 | This project is a small library used for generating content types (page, component, directive, tabs, etc) within an Ionic project. This utility is included as a dependency to ionic-cli. 3 | 4 | The project is written in typescript. It is then converted to ES5, and bundled with WebPack to result in a dependency free experience. 5 | 6 | ## To Build 7 | Run `npm run build` to build the project. It builds an `index.js` and a `package.json` file to `dist/ionic-generators`. 8 | 9 | ## To Publish 10 | Manually increment the package.json version in the `build` directory. Do a build, and then navigate to the `dist/ionic-generators` directory and run `npm publish`. 11 | 12 | Before publishing, please run the tests and make sure they pass. 13 | 14 | ## To Test 15 | Unit tests - `npm run test` 16 | E2E - `npm run e2e` 17 | E2E for tabs - `npm run e2e` -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function clean() { 4 | var del = require('del'); 5 | del.sync('dist/*'); 6 | } 7 | 8 | function deleteCompiled() { 9 | // var del = require('del'); 10 | // del.sync('dist/compiled'); 11 | } 12 | 13 | function runTsc(testBuild) { 14 | return new Promise(function(resolve, reject) { 15 | var exec = require('child_process').exec; 16 | var command = './node_modules/.bin/tsc -p .'; 17 | if (testBuild) { 18 | command = command + ' --module commonjs' 19 | } 20 | exec(command, function(err, stdout, stderr) { 21 | if (err) { 22 | console.log(stdout); 23 | console.log(stderr); 24 | reject(err); 25 | } else { 26 | resolve(); 27 | } 28 | }); 29 | }); 30 | } 31 | 32 | function customResolver() { 33 | return { 34 | resolveId(id) { 35 | if (id.startsWith('lodash')) { 36 | return process.cwd() + '/node_modules/lodash-es/' + id.split('lodash/').pop() + '.js'; 37 | } 38 | } 39 | }; 40 | } 41 | 42 | function bundle() { 43 | 44 | /*var rollup = require('rollup'); 45 | var nodeResolve = require('rollup-plugin-node-resolve'); 46 | var commonjs = require('@danbucholtz/rollup-commonjs-unambiguous-fork'); 47 | 48 | return rollup.rollup({ 49 | entry: './dist/compiled/index.js', 50 | sourceMap: true, 51 | useStrict: false, 52 | plugins: [ 53 | customResolver(), 54 | nodeResolve({ 55 | module: true, 56 | jsnext: true, 57 | main: true 58 | }), 59 | commonjs({}) 60 | ] 61 | }).then((bundle) => { 62 | return bundle.write({ 63 | format: 'cjs', 64 | useStrict: false, 65 | dest: './dist/ionic-generators/index.js' 66 | }); 67 | }); 68 | */ 69 | return new Promise(function(resolve, reject) { 70 | var webpack = require('webpack'); 71 | var config = require('./webpack.config'); 72 | var compiler = webpack(config); 73 | compiler.run(function(err, stats) { 74 | if (err) { 75 | reject(err); 76 | return; 77 | } 78 | resolve(); 79 | }); 80 | }); 81 | } 82 | 83 | function copyTypeDefinitions() { 84 | var destinationBase = './dist/ionic-generators'; 85 | return copyFiles('./dist/compiled/*.d.ts', destinationBase); 86 | } 87 | 88 | function copyTemplates(isTestBuild) { 89 | var destinationBase = './dist/ionic-generators'; 90 | if (isTestBuild) { 91 | destinationBase = './dist/compiled'; 92 | } 93 | var promises = []; 94 | promises.push(copyFiles('./src/component/*', destinationBase + '/component')); 95 | promises.push(copyFiles('./src/directive/*', destinationBase + '/directive')); 96 | promises.push(copyFiles('./src/page/*', destinationBase + '/page')); 97 | promises.push(copyFiles('./src/pipe/*', destinationBase + '/pipe')); 98 | promises.push(copyFiles('./src/provider/*', destinationBase + '/provider')); 99 | 100 | return Promise.all(promises); 101 | } 102 | 103 | function copyFiles(srcGlob, destDir) { 104 | return new Promise(function(resolve, reject) { 105 | var vinyl = require('vinyl-fs'); 106 | var stream = vinyl.src(srcGlob).pipe(vinyl.dest(destDir)); 107 | stream.on('end', function() { 108 | resolve(); 109 | }) 110 | stream.on('error', function() { 111 | reject(); 112 | }); 113 | }); 114 | } 115 | 116 | function copyPackageJsonTemplate() { 117 | return copyFiles('./build/package.json', './dist/ionic-generators'); 118 | } 119 | 120 | function doBuild(isTestBuild) { 121 | clean() 122 | runTsc(isTestBuild).then( function() { 123 | return bundle(); 124 | }).then(function() { 125 | return copyTemplates(isTestBuild); 126 | }).then(function() { 127 | return copyPackageJsonTemplate(); 128 | }).then(function() { 129 | return copyTypeDefinitions(); 130 | }).then(function() { 131 | if (!isTestBuild) { 132 | return deleteCompiled(); 133 | } 134 | }).catch(function(err){ 135 | console.log("ERROR: ", err.message); 136 | process.exit(1); 137 | }); 138 | } 139 | 140 | var isTestBuild = process.argv.length > 2 && process.argv[2] === '--test'; 141 | 142 | doBuild(isTestBuild); -------------------------------------------------------------------------------- /build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ionic/app-generators", 3 | "version": "0.0.4", 4 | "description": "easily generate components, pages, directives, etc.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "ionic" 11 | ], 12 | "author": "Ionic Team (http://ionic.io)", 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /build/test.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function runTests() { 4 | var vinyl = require('vinyl-fs'); 5 | var jasmine = require('gulp-jasmine'); 6 | vinyl.src('./dist/compiled/*.spec.js').pipe(jasmine()); 7 | } 8 | 9 | runTests(); -------------------------------------------------------------------------------- /build/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | 5 | /*var nodeModules = {}; 6 | fs.readdirSync('node_modules') 7 | .filter(function(x) { 8 | return ['.bin'].indexOf(x) === -1; 9 | }) 10 | .forEach(function(mod) { 11 | nodeModules[mod] = 'commonjs ' + mod; 12 | }); 13 | */ 14 | module.exports = { 15 | entry: './dist/compiled/index.js', 16 | target: 'node', 17 | output: { 18 | path: './dist/ionic-generators', 19 | filename: 'index.js', 20 | libraryTarget: 'commonjs2' 21 | } 22 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-generators", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "node ./build/build.js", 9 | "test": "node ./build/build.js --test && node ./build/test.js", 10 | "e2e": "node ./build/build.js --test && node ./dist/compiled/e2e.js", 11 | "e2e-tabs": "node ./build/build.js --test && node ./dist/compiled/e2e-tabs.js" 12 | }, 13 | "author": "Ionic Team (http://ionic.io)", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "@types/chalk": "^0.4.31", 17 | "@types/fs-extra": "0.0.31", 18 | "@types/inquirer": "0.0.28", 19 | "@types/jasmine": "2.2.33", 20 | "@types/node": "6.0.38", 21 | "@types/proxyquire": "1.3.26", 22 | "chalk": "^1.1.3", 23 | "colors": "1.1.2", 24 | "del": "2.2.2", 25 | "fs-extra": "0.30.0", 26 | "gulp-jasmine": "2.4.1", 27 | "inquirer": "1.1.3", 28 | "lodash-es": "4.15.0", 29 | "param-case": "2.1.0", 30 | "proxyquire": "1.7.10", 31 | "typescript": "2.0.2", 32 | "vinyl-fs": "2.4.3", 33 | "webpack": "2.1.0-beta.22" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'param-case'; 2 | declare module 'inquirer'; -------------------------------------------------------------------------------- /src/e2e-tabs.ts: -------------------------------------------------------------------------------- 1 | import { generate } from './index'; 2 | 3 | 4 | function getOptions() { 5 | return { 6 | generatorType: 'tabs', 7 | suppliedName: 'my tabs', 8 | includeSpec: true, 9 | includeSass: true 10 | }; 11 | } 12 | 13 | function getProjectStructureObject() { 14 | return { 15 | absoluteComponentDirPath: `${process.cwd()}/e2e/components`, 16 | absoluteDirectiveDirPath: `${process.cwd()}/e2e/components`, 17 | absolutePagesDirPath: `${process.cwd()}/e2e/pages`, 18 | absolutePipeDirPath: `${process.cwd()}/e2e/pipes`, 19 | absoluteProviderDirPath: `${process.cwd()}/e2e/providers`, 20 | absolutePathTemplateBaseDir: `${process.env.HOME}/Dev/ionic2-build/scripts/templates` 21 | } 22 | } 23 | 24 | generate(getOptions(), getProjectStructureObject()); -------------------------------------------------------------------------------- /src/e2e.ts: -------------------------------------------------------------------------------- 1 | import { generate } from './index'; 2 | 3 | 4 | function getOptions() { 5 | return { 6 | generatorType: 'page', 7 | suppliedName: 'my page of doom', 8 | includeSpec: true, 9 | includeSass: true 10 | }; 11 | } 12 | 13 | function getProjectStructureObject() { 14 | return { 15 | absoluteComponentDirPath: `${process.cwd()}/e2e/components`, 16 | absoluteDirectiveDirPath: `${process.cwd()}/e2e/components`, 17 | absolutePagesDirPath: `${process.cwd()}/e2e/pages`, 18 | absolutePipeDirPath: `${process.cwd()}/e2e/pipes`, 19 | absoluteProviderDirPath: `${process.cwd()}/e2e/providers`, 20 | absolutePathTemplateBaseDir: `${process.env.HOME}/Dev/ionic2-build/scripts/templates` 21 | } 22 | } 23 | 24 | generate(getOptions(), getProjectStructureObject()); -------------------------------------------------------------------------------- /src/generator-options.ts: -------------------------------------------------------------------------------- 1 | export interface GeneratorOptions { 2 | generatorType: string; 3 | suppliedName: string; 4 | includeSpec: boolean; 5 | includeSass: boolean; 6 | } -------------------------------------------------------------------------------- /src/generator.spec.ts: -------------------------------------------------------------------------------- 1 | /*import * as proxyquire from 'proxyquire'; 2 | proxyquire.noCallThru(); 3 | 4 | let mockLodash; 5 | let mockMkDirp; 6 | let mockFs; 7 | let mockPath; 8 | 9 | function getTestSubject() { 10 | let testSubject = proxyquire('./generator', { 11 | 'lodash': mockLodash, 12 | 'mkdirp-no-bin': mockMkDirp, 13 | 'fs': mockFs, 14 | 'path': mockPath 15 | }); 16 | return testSubject; 17 | } 18 | 19 | function getGeneratorOptions() { 20 | return { 21 | generatorType: 'generatorType', 22 | suppliedName: 'suppliedName', 23 | includeSpec: true, 24 | includeSass: true 25 | } 26 | } 27 | 28 | function getProjectStructureOptions() { 29 | return { 30 | absoluteComponentDirPath: 'absoluteComponentDirPath', 31 | absoluteDirectiveDirPath: 'absoluteDirectiveDirPath', 32 | absolutePagesDirPath: 'absolutePagesDirPath', 33 | absolutePipeDirPath: 'absolutePipeDirPath', 34 | absoluteProviderDirPath: 'absoluteProviderDirPath', 35 | } 36 | } 37 | 38 | describe('generator', () => { 39 | beforeEach(() => { 40 | mockLodash = { 41 | camelCase: () => {}, 42 | capitalize: () => {}, 43 | kebabCase: () => {}, 44 | template: () => {}, 45 | }; 46 | 47 | mockMkDirp = () => {}; 48 | 49 | mockFs = { 50 | access: () => {}, 51 | readdirSync: () => {}, 52 | readFileSync: () => {}, 53 | writeFileSync: () => {}, 54 | }; 55 | 56 | mockPath = { 57 | basename: () => {}, 58 | extname: () => {}, 59 | join: () => {} 60 | }; 61 | }); 62 | describe('constructor', () => { 63 | it('should store options and create the fileName and className', () => { 64 | // arrange 65 | let testSubject = getTestSubject(); 66 | let generatorOptions = getGeneratorOptions(); 67 | let pso = getProjectStructureOptions(); 68 | spyOn(mockLodash, 'kebabCase').and.returnValue('fileName'); 69 | spyOn(mockLodash, 'camelCase').and.returnValue('className'); 70 | spyOn(mockLodash, 'capitalize').and.returnValue('ClassName'); 71 | 72 | // act 73 | let generator = new testSubject.Generator(generatorOptions, pso); 74 | 75 | // assert 76 | expect(generator.fileName).toEqual('fileName'); 77 | expect(mockLodash.kebabCase).toHaveBeenCalledWith(generatorOptions.suppliedName); 78 | expect(generator.className).toEqual('ClassName'); 79 | expect(mockLodash.capitalize).toHaveBeenCalledWith('className'); 80 | expect(mockLodash.camelCase).toHaveBeenCalledWith('suppliedName'); 81 | expect(generator.options).toEqual(generatorOptions); 82 | expect(generator.projectStructureOptions).toEqual(pso); 83 | }); 84 | }); 85 | }); 86 | */ 87 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync, readFileSync, writeFileSync } from 'fs'; 2 | import { join } from 'path'; 3 | 4 | import { camelCase, capitalize, filterAndCleanUpTemplates, generateFileName, getPathsBasedOnGeneratorType, getTemplatesInDir, kebabCase, makeDirectoryIfNeeded, renderTemplate, stringToClassCase } from './utils'; 5 | import { GeneratorOptions } from './generator-options'; 6 | import { ProjectStructureOptions } from './project-structure-options'; 7 | 8 | export class Generator { 9 | 10 | suppliedName: string; 11 | fileName: string; 12 | className: string; 13 | tabContent: string; 14 | tabImports: string; 15 | tabVariables: string; 16 | 17 | constructor(public options: GeneratorOptions, public projectStructureOptions: ProjectStructureOptions){ 18 | this.suppliedName = options.suppliedName; 19 | this.fileName = kebabCase(options.suppliedName); 20 | this.className = stringToClassCase(options.suppliedName); 21 | this.tabContent = ''; 22 | this.tabImports = ''; 23 | this.tabVariables = ''; 24 | } 25 | 26 | generate(): Promise{ 27 | let pathsObj; 28 | try{ 29 | pathsObj = getPathsBasedOnGeneratorType(this.options.generatorType, this.fileName, this.projectStructureOptions); 30 | } catch (ex) { 31 | return Promise.reject(ex); 32 | } 33 | 34 | return makeDirectoryIfNeeded(pathsObj.outputDir).then( () => { 35 | return this.renderAndWriteTemplates(pathsObj.sourceTemplateDir, pathsObj.outputDir); 36 | }); 37 | } 38 | 39 | renderAndWriteTemplates(templateSourceDir: string, destinationDir: string) { 40 | const allTemplates = getTemplatesInDir(templateSourceDir); 41 | const filteredTemplatePaths = filterAndCleanUpTemplates(allTemplates, templateSourceDir, this.options); 42 | filteredTemplatePaths.forEach(templatePath => { 43 | const templateContent = readFileSync(templatePath).toString(); 44 | const renderedTemplate = renderTemplate(templateContent, this.suppliedName, this.fileName, this.className, this.tabContent, this.tabImports, this.tabVariables); 45 | const fileName = generateFileName(templatePath, this.fileName); 46 | const fileToWrite = join(destinationDir, fileName); 47 | writeFileSync(fileToWrite, renderedTemplate); 48 | }); 49 | } 50 | } -------------------------------------------------------------------------------- /src/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as proxyquire from 'proxyquire'; 2 | proxyquire.noCallThru(); 3 | 4 | let mockGeneratorInstance; 5 | let mockGeneratorImport; 6 | 7 | function getTestSubject() { 8 | let testSubject = proxyquire('./index', { 9 | './generator': mockGeneratorImport 10 | }); 11 | return testSubject; 12 | } 13 | 14 | function getGeneratorOptions() { 15 | return { 16 | generatorType: "generatorType", 17 | suppliedName: "suppliesName", 18 | includeSpec: true, 19 | includeSass: true 20 | } 21 | } 22 | 23 | function getProjectStructureOptions() { 24 | return { 25 | absoluteComponentDirPath: "absoluteComponentDirPath", 26 | absoluteDirectiveDirPath: "absoluteDirectiveDirPath", 27 | absolutePagesDirPath: "absolutePagesDirPath", 28 | absolutePipeDirPath: "absolutePipeDirPath", 29 | absoluteProviderDirPath: "absoluteProviderDirPath", 30 | } 31 | } 32 | 33 | describe('index', () => { 34 | beforeEach(() => { 35 | mockGeneratorInstance = { 36 | generate: () => {} 37 | }; 38 | 39 | mockGeneratorImport = { 40 | Generator: () => { 41 | return mockGeneratorInstance; 42 | } 43 | } 44 | }); 45 | 46 | describe('generate', () => { 47 | it('should throw an exception when no options are provided', () => { 48 | try{ 49 | // arrange 50 | let testSubject = getTestSubject(); 51 | // act 52 | testSubject.generate(null, null); 53 | 54 | // fail the test if it doesn't throw 55 | expect(true).toEqual(false); 56 | } catch(ex) { 57 | // assert 58 | expect(ex.message).toBeDefined; 59 | } 60 | }); 61 | 62 | it('should throw when missing generatorType', () => { 63 | try{ 64 | // arrange 65 | let testSubject = getTestSubject(); 66 | let options = getGeneratorOptions(); 67 | options.generatorType = null; 68 | 69 | // act 70 | testSubject.generate(options, null); 71 | // fail the test if it doesn't throw 72 | expect(true).toEqual(false); 73 | } catch(ex) { 74 | // assert 75 | expect(ex.message).toBeDefined; 76 | } 77 | }); 78 | 79 | it('should throw when missing suppliedName', () => { 80 | try{ 81 | // arrange 82 | let testSubject = getTestSubject(); 83 | let options = getGeneratorOptions(); 84 | options.suppliedName = null; 85 | 86 | // act 87 | testSubject.generate(options, null); 88 | // fail the test if it doesn't throw 89 | expect(true).toEqual(false); 90 | } catch(ex) { 91 | // assert 92 | expect(ex.message).toBeDefined; 93 | } 94 | }); 95 | 96 | it('should throw when missing projectStructureOptions', () => { 97 | try{ 98 | // arrange 99 | let testSubject = getTestSubject(); 100 | let options = getGeneratorOptions(); 101 | 102 | // act 103 | testSubject.generate(options, null); 104 | // fail the test if it doesn't throw 105 | expect(true).toEqual(false); 106 | } catch(ex) { 107 | // assert 108 | expect(ex.message).toBeDefined; 109 | } 110 | }); 111 | 112 | it('should throw when missing absoluteComponentDirPath', () => { 113 | try{ 114 | // arrange 115 | let testSubject = getTestSubject(); 116 | let options = getGeneratorOptions(); 117 | let pso = getProjectStructureOptions(); 118 | pso.absoluteComponentDirPath = null; 119 | // act 120 | testSubject.generate(options, pso); 121 | // fail the test if it doesn't throw 122 | expect(true).toEqual(false); 123 | } catch(ex) { 124 | // assert 125 | expect(ex.message).toBeDefined; 126 | } 127 | }); 128 | 129 | it('should throw when missing absoluteDirectiveDirPath', () => { 130 | try{ 131 | // arrange 132 | let testSubject = getTestSubject(); 133 | let options = getGeneratorOptions(); 134 | let pso = getProjectStructureOptions(); 135 | pso.absoluteDirectiveDirPath = null; 136 | // act 137 | testSubject.generate(options, pso); 138 | // fail the test if it doesn't throw 139 | expect(true).toEqual(false); 140 | } catch(ex) { 141 | // assert 142 | expect(ex.message).toBeDefined; 143 | } 144 | }); 145 | 146 | it('should throw when missing absolutePagesDirPath', () => { 147 | try{ 148 | // arrange 149 | let testSubject = getTestSubject(); 150 | let options = getGeneratorOptions(); 151 | let pso = getProjectStructureOptions(); 152 | pso.absolutePagesDirPath = null; 153 | // act 154 | testSubject.generate(options, pso); 155 | // fail the test if it doesn't throw 156 | expect(true).toEqual(false); 157 | } catch(ex) { 158 | // assert 159 | expect(ex.message).toBeDefined; 160 | } 161 | }); 162 | 163 | it('should throw when missing absolutePipeDirPath', () => { 164 | try{ 165 | // arrange 166 | let testSubject = getTestSubject(); 167 | let options = getGeneratorOptions(); 168 | let pso = getProjectStructureOptions(); 169 | pso.absolutePipeDirPath = null; 170 | // act 171 | testSubject.generate(options, pso); 172 | // fail the test if it doesn't throw 173 | expect(true).toEqual(false); 174 | } catch(ex) { 175 | // assert 176 | expect(ex.message).toBeDefined; 177 | } 178 | }); 179 | 180 | it('should throw when missing absoluteProviderDirPath', () => { 181 | try{ 182 | // arrange 183 | let testSubject = getTestSubject(); 184 | let options = getGeneratorOptions(); 185 | let pso = getProjectStructureOptions(); 186 | pso.absoluteProviderDirPath = null; 187 | // act 188 | testSubject.generate(options, pso); 189 | // fail the test if it doesn't throw 190 | expect(true).toEqual(false); 191 | } catch(ex) { 192 | // assert 193 | expect(ex.message).toBeDefined; 194 | } 195 | }); 196 | 197 | it('should call generate on new instance', () => { 198 | // arrange 199 | let testSubject = getTestSubject(); 200 | let options = getGeneratorOptions(); 201 | let pso = getProjectStructureOptions(); 202 | spyOn(mockGeneratorImport, 'Generator').and.callThrough(); 203 | spyOn(mockGeneratorInstance, 'generate').and.callThrough(); 204 | 205 | // act 206 | testSubject.generate(options, pso); 207 | 208 | // assert 209 | 210 | expect(mockGeneratorImport.Generator).toHaveBeenCalledWith(options, pso); 211 | expect(mockGeneratorInstance.generate).toHaveBeenCalled(); 212 | }); 213 | }); 214 | }); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as chalk from 'chalk'; 2 | 3 | import { Generator } from './generator'; 4 | import { GeneratorOptions } from './generator-options'; 5 | import { ProjectStructureOptions } from './project-structure-options'; 6 | import { TabGenerator } from './tab-generator'; 7 | import { TABS_TYPE } from './utils'; 8 | 9 | 10 | export function printAvailableGenerators() { 11 | console.log(chalk.blue('Available generators:'as any)); 12 | console.log(chalk.blue(' *' as any), 'Component'); 13 | console.log(chalk.blue(' *' as any), 'Directive'); 14 | console.log(chalk.blue(' *' as any), 'Page'); 15 | console.log(chalk.blue(' *' as any), 'Pipe'); 16 | console.log(chalk.blue(' *' as any), 'Provider'); 17 | console.log(chalk.blue(' *' as any), 'Tabs'); 18 | } 19 | 20 | export function generate(options: GeneratorOptions, projectStructureOptions: ProjectStructureOptions): Promise{ 21 | const error = validateOptions(options, projectStructureOptions); 22 | if (error) { 23 | return Promise.reject(error); 24 | } 25 | 26 | const generator = getGenerator(options, projectStructureOptions); 27 | return generator.generate(); 28 | } 29 | 30 | function getGenerator(options: GeneratorOptions, projectStructureOptions: ProjectStructureOptions) { 31 | if (options.generatorType === TABS_TYPE) { 32 | return new TabGenerator(options, projectStructureOptions); 33 | } 34 | return new Generator(options, projectStructureOptions); 35 | } 36 | 37 | function validateOptions(options: GeneratorOptions, projectStructureOptions: ProjectStructureOptions) { 38 | if (!options) { 39 | return new Error('No options passed to generator'); 40 | } 41 | 42 | if (!options.generatorType || options.generatorType.length === 0) { 43 | return new Error('No generator type passed'); 44 | } 45 | 46 | if (!options.suppliedName || options.suppliedName.length === 0) { 47 | return new Error('No supplied name provided'); 48 | } 49 | 50 | if (!projectStructureOptions) { 51 | return new Error('No projectStructureOptions passed to generator'); 52 | } 53 | 54 | if (!projectStructureOptions.absolutePathTemplateBaseDir || projectStructureOptions.absolutePathTemplateBaseDir.length === 0) { 55 | return new Error('projectStructureOptions.absolutePathTemplateBaseDir is a required field'); 56 | } 57 | 58 | if (!projectStructureOptions.absoluteComponentDirPath || projectStructureOptions.absoluteComponentDirPath.length === 0) { 59 | return new Error('projectStructureOptions.absoluteComponentDirPath is a required field'); 60 | } 61 | 62 | if (!projectStructureOptions.absoluteDirectiveDirPath || projectStructureOptions.absoluteDirectiveDirPath.length === 0) { 63 | return new Error('projectStructureOptions.absoluteDirectiveDirPath is a required field'); 64 | } 65 | 66 | if (!projectStructureOptions.absolutePagesDirPath || projectStructureOptions.absolutePagesDirPath.length === 0) { 67 | return new Error('projectStructureOptions.absolutePagesDirPath is a required field'); 68 | } 69 | 70 | if (!projectStructureOptions.absolutePipeDirPath || projectStructureOptions.absolutePipeDirPath.length === 0) { 71 | return new Error('projectStructureOptions.absolutePipeDirPath is a required field'); 72 | } 73 | 74 | if (!projectStructureOptions.absoluteProviderDirPath || projectStructureOptions.absoluteProviderDirPath.length === 0) { 75 | return new Error('projectStructureOptions.absoluteProviderDirPath is a required field'); 76 | } 77 | } -------------------------------------------------------------------------------- /src/project-structure-options.ts: -------------------------------------------------------------------------------- 1 | export interface ProjectStructureOptions { 2 | absoluteComponentDirPath: string; 3 | absoluteDirectiveDirPath: string; 4 | absolutePagesDirPath: string; 5 | absolutePipeDirPath: string; 6 | absoluteProviderDirPath: string; 7 | absolutePathTemplateBaseDir: string; 8 | } -------------------------------------------------------------------------------- /src/tab-generator.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync, readFileSync, writeFileSync } from 'fs'; 2 | import { join } from 'path'; 3 | 4 | import * as inquirer from 'inquirer'; 5 | 6 | import { Generator } from './generator'; 7 | import { GeneratorOptions } from './generator-options'; 8 | import { filterAndCleanUpTemplates, getPathsBasedOnGeneratorType, getTemplatesInDir, kebabCase, makeDirectoryIfNeeded, mergeObjects, stringToClassCase } from './utils'; 9 | 10 | export class TabGenerator extends Generator { 11 | 12 | tabNames: string[]; 13 | 14 | generate():Promise{ 15 | this.tabNames = []; 16 | return this.tabCountPrompt() 17 | .then( (numTabs: number) => { 18 | return this.promptForTabNames(0, numTabs); 19 | }) 20 | .then(() => { 21 | return this.createPages(this.tabNames); 22 | }) 23 | .then(() => { 24 | this.tabContent = this.createTabContent(this.tabNames); 25 | this.tabImports = this.createTabImports(this.tabNames); 26 | this.tabVariables = this.createTabVariables(this.tabNames); 27 | return super.generate(); 28 | }).catch((err) => { 29 | console.log("ERROR: An unexpected error occurred"); 30 | console.log(err.msg); 31 | process.exit(1); 32 | }); 33 | } 34 | 35 | tabCountPrompt() { 36 | return inquirer.prompt({ 37 | choices: ['1', '2', '3', '4', '5'], 38 | message: 'How many tabs would you like?', 39 | name: 'count', 40 | type: 'list' 41 | }).then(result => { 42 | return parseInt(result.count) 43 | }); 44 | } 45 | 46 | validate(input: string): any{ 47 | if (isNaN(parseInt(input))) { 48 | // Pass the return value in the done callback 49 | return 'You need to provide a number'; 50 | } 51 | return true; 52 | } 53 | 54 | tabNamePromptInternal(index: number) { 55 | const numberNames = ['first', 'second', 'third', 'fourth', 'fifth']; 56 | return inquirer.prompt({ 57 | message: 'Enter the ' + numberNames[index] + ' tab name:', 58 | name: 'name', 59 | type: 'input' 60 | }).then(result => { 61 | return result.name; 62 | }); 63 | 64 | } 65 | 66 | promptForTabNames(numTabsCompleted: number, numTabsNeeded: number) { 67 | 68 | if ( numTabsNeeded === numTabsCompleted ) { 69 | // we're done 70 | return Promise.resolve(); 71 | } else { 72 | // we need to prompt for a tab 73 | return this.tabNamePromptInternal(numTabsCompleted).then( (tabName: string) => { 74 | this.tabNames.push(tabName); 75 | numTabsCompleted++; 76 | return this.promptForTabNames(numTabsCompleted, numTabsNeeded); 77 | }); 78 | } 79 | } 80 | 81 | createPages(pageNames) { 82 | 83 | let promises = []; 84 | for ( let pageName of pageNames ) { 85 | const compontentOptions = (mergeObjects(this.options, {generatorType: 'page', suppliedName: pageName}) as GeneratorOptions); 86 | const pageGenerator = new Generator(compontentOptions, this.projectStructureOptions); 87 | promises.push(pageGenerator.generate()); 88 | } 89 | return Promise.all(promises); 90 | } 91 | 92 | createTabContent(tabs: string[]) { 93 | let templateString = ''; 94 | for (let i = 0; i < tabs.length; i++) { 95 | templateString = `${templateString} ` 96 | if (i !== tabs.length - 1) { 97 | // add a newline character 98 | templateString = `${templateString}\n` 99 | } 100 | } 101 | return templateString; 102 | } 103 | 104 | getTabIconForIndex(index: number) { 105 | if (index === 0) { 106 | return 'home'; 107 | } else if (index === 1) { 108 | return 'text'; 109 | } else if (index === 2) { 110 | return 'stats'; 111 | } else if (index === 3) { 112 | return 'image'; 113 | } else if (index === 4) { 114 | return 'star'; 115 | } 116 | } 117 | 118 | createTabImports(tabs: string[]) { 119 | let importString = ''; 120 | for (let i = 0; i < tabs.length; i++) { 121 | importString = `${importString}import { ${stringToClassCase(tabs[i])} } from '../${kebabCase(tabs[i])}/${kebabCase(tabs[i])}'` 122 | if (i !== tabs.length - 1) { 123 | // add a newline character 124 | importString = `${importString}\n`; 125 | } 126 | } 127 | return importString; 128 | } 129 | 130 | createTabVariables(tabs: string[]) { 131 | let tabVariableString = ''; 132 | for (let i = 0; i < tabs.length; i++) { 133 | tabVariableString = `${tabVariableString} tab${i + 1}Root: any = ${stringToClassCase(tabs[i])};` 134 | if (i !== tabs.length - 1) { 135 | // add a newline character 136 | tabVariableString = `${tabVariableString}\n`; 137 | } 138 | } 139 | return tabVariableString; 140 | } 141 | } -------------------------------------------------------------------------------- /src/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import * as proxyquire from 'proxyquire'; 2 | proxyquire.noCallThru(); 3 | 4 | let mockFs; 5 | 6 | function getTestSubject() { 7 | const testSubject = proxyquire('./utils', { 8 | 'fs-extra': mockFs 9 | }); 10 | return testSubject; 11 | } 12 | 13 | function getProjectStructureOptions() { 14 | return { 15 | absoluteComponentDirPath: 'absoluteComponentDirPath', 16 | absoluteDirectiveDirPath: 'absoluteDirectiveDirPath', 17 | absolutePagesDirPath: 'absolutePagesDirPath', 18 | absolutePipeDirPath: 'absolutePipeDirPath', 19 | absoluteProviderDirPath: 'absoluteProviderDirPath', 20 | } 21 | } 22 | 23 | describe('utils', () => { 24 | beforeEach(() => { 25 | mockFs = { 26 | access: () => { console.log("mock access")}, 27 | mkdirp: () => {}, 28 | readFileSync: () => {}, 29 | readdirSync: () => {} 30 | }; 31 | }); 32 | describe('getPathsBasedOnGeneratorType', () => { 33 | it('should use component/fileName for components', () => { 34 | // arrange 35 | const testSubject = getTestSubject(); 36 | let pso = getProjectStructureOptions(); 37 | // act 38 | const result = testSubject.getPathsBasedOnGeneratorType('component', 'fileName', pso); 39 | 40 | // assert 41 | expect(result.outputDir).toEqual(`${pso.absoluteComponentDirPath}/fileName`); 42 | expect(result.sourceTemplateDir).toEqual(`${__dirname}/component`); 43 | }); 44 | 45 | it('should use provided directive dir as dir', () => { 46 | // arrange 47 | const testSubject = getTestSubject(); 48 | let pso = getProjectStructureOptions(); 49 | // act 50 | const result = testSubject.getPathsBasedOnGeneratorType('directive', 'fileName', pso); 51 | 52 | // assert 53 | expect(result.outputDir).toEqual(`${pso.absoluteDirectiveDirPath}/fileName`); 54 | expect(result.sourceTemplateDir).toEqual(`${__dirname}/directive`); 55 | }); 56 | 57 | it('should use pages/fileName for pages', () => { 58 | // arrange 59 | const testSubject = getTestSubject(); 60 | let pso = getProjectStructureOptions(); 61 | // act 62 | const result = testSubject.getPathsBasedOnGeneratorType('page', 'fileName', pso); 63 | 64 | // assert 65 | expect(result.outputDir).toEqual(`${pso.absolutePagesDirPath}/fileName`); 66 | expect(result.sourceTemplateDir).toEqual(`${__dirname}/page`); 67 | }); 68 | 69 | it('should return pipe directory info', () => { 70 | // arrange 71 | const testSubject = getTestSubject(); 72 | let pso = getProjectStructureOptions(); 73 | // act 74 | const result = testSubject.getPathsBasedOnGeneratorType('pipe', 'fileName', pso); 75 | 76 | // assert 77 | expect(result.outputDir).toEqual(`${pso.absolutePipeDirPath}`); 78 | expect(result.sourceTemplateDir).toEqual(`${__dirname}/pipe`); 79 | }); 80 | 81 | it('should return provider directory info', () => { 82 | // arrange 83 | const testSubject = getTestSubject(); 84 | let pso = getProjectStructureOptions(); 85 | // act 86 | const result = testSubject.getPathsBasedOnGeneratorType('provider', 'fileName', pso); 87 | 88 | // assert 89 | expect(result.outputDir).toEqual(`${pso.absoluteProviderDirPath}`); 90 | expect(result.sourceTemplateDir).toEqual(`${__dirname}/provider`); 91 | }); 92 | }); 93 | 94 | describe('renderTemplate', () => { 95 | it('should replace all instance of filename const', () => { 96 | const testSubject = getTestSubject(); 97 | const result = testSubject.renderTemplate(`My sample string: $FILENAME $FILENAME`, '', 'taco', ''); 98 | expect(result).toEqual(`My sample string: taco taco`); 99 | }); 100 | 101 | it('should replace all instance of supplied name const', () => { 102 | const testSubject = getTestSubject(); 103 | const result = testSubject.renderTemplate(`My sample string: $SUPPLIEDNAME $SUPPLIEDNAME`, 'taco', '', ''); 104 | expect(result).toEqual(`My sample string: taco taco`); 105 | }); 106 | 107 | it('should replace all instance of supplied name const', () => { 108 | const testSubject = getTestSubject(); 109 | const result = testSubject.renderTemplate(`My sample string: $CLASSNAME $CLASSNAME`, '', '', 'taco'); 110 | expect(result).toEqual(`My sample string: taco taco`); 111 | }); 112 | 113 | it('should replace all instance of supplied name const', () => { 114 | const testSubject = getTestSubject(); 115 | const result = testSubject.renderTemplate(`My sample string: $FILENAME $SUPPLIEDNAME $CLASSNAME $FILENAME $SUPPLIEDNAME $CLASSNAME`, 116 | 'mySuppliedName', 'myFileName', 'myClassName'); 117 | expect(result).toEqual(`My sample string: myFileName mySuppliedName myClassName myFileName mySuppliedName myClassName`); 118 | }); 119 | }); 120 | 121 | describe('capitalize', () => { 122 | it('should capitalize the first letter', () => { 123 | const testSubject = getTestSubject(); 124 | let result = testSubject.capitalize('myTest'); 125 | expect(result).toEqual('MyTest'); 126 | }); 127 | }); 128 | 129 | describe('camelCase', () => { 130 | it('should not change all lowercase input', () => { 131 | const testSubject = getTestSubject(); 132 | let result = testSubject.camelCase('test'); 133 | expect(result).toEqual('test'); 134 | }); 135 | 136 | it('should change capital T to lowercase', () => { 137 | const testSubject = getTestSubject(); 138 | let result = testSubject.camelCase('Test'); 139 | expect(result).toEqual('test'); 140 | }); 141 | 142 | it('should handle hyphen', () => { 143 | const testSubject = getTestSubject(); 144 | let result = testSubject.camelCase('Test-test'); 145 | expect(result).toEqual('testTest'); 146 | }); 147 | 148 | it('should handle hyphen and capital', () => { 149 | const testSubject = getTestSubject(); 150 | let result = testSubject.camelCase('Test-Test'); 151 | expect(result).toEqual('testTest'); 152 | }); 153 | 154 | it('should handle space', () => { 155 | const testSubject = getTestSubject(); 156 | let result = testSubject.camelCase('Test test'); 157 | expect(result).toEqual('testTest'); 158 | }); 159 | 160 | it('should handle space and Cap', () => { 161 | const testSubject = getTestSubject(); 162 | let result = testSubject.camelCase('Test Test'); 163 | expect(result).toEqual('testTest'); 164 | }); 165 | 166 | it('should handle capitalized word separation', () => { 167 | const testSubject = getTestSubject(); 168 | let result = testSubject.camelCase('MyTestProject'); 169 | expect(result).toEqual('myTestProject'); 170 | }); 171 | 172 | it('should handle double word, space and cap', () => { 173 | const testSubject = getTestSubject(); 174 | let result = testSubject.camelCase('MyTest Project'); 175 | expect(result).toEqual('myTestProject'); 176 | }); 177 | 178 | it('should handle double word, space and cap', () => { 179 | const testSubject = getTestSubject(); 180 | let result = testSubject.camelCase('MyTest-Project'); 181 | expect(result).toEqual('myTestProject'); 182 | }); 183 | }); 184 | 185 | describe("kebabCase", () => { 186 | it('should separate camelCase', () => { 187 | const testSubject = getTestSubject(); 188 | let result = testSubject.kebabCase('mySampleProject'); 189 | expect(result).toEqual('my-sample-project'); 190 | }); 191 | 192 | it('should handle titleCase', () => { 193 | const testSubject = getTestSubject(); 194 | let result = testSubject.kebabCase('MySampleProject'); 195 | expect(result).toEqual('my-sample-project'); 196 | }); 197 | 198 | it('should handle spaces', () => { 199 | const testSubject = getTestSubject(); 200 | let result = testSubject.kebabCase('My Sample project'); 201 | expect(result).toEqual('my-sample-project'); 202 | }); 203 | 204 | it('should handle spaces and hyphens', () => { 205 | const testSubject = getTestSubject(); 206 | let result = testSubject.kebabCase('My-Sample project'); 207 | expect(result).toEqual('my-sample-project'); 208 | }); 209 | }); 210 | 211 | describe('makeDirectoryIfNeeded', () => { 212 | it('should return a resolved promise when path exists', (done) => { 213 | // arrange 214 | const testSubject = getTestSubject(); 215 | spyOn(mockFs, 'access').and.callThrough; 216 | 217 | // act 218 | const promise = testSubject.makeDirectoryIfNeeded('myPath'); 219 | const callback = mockFs.access.calls.mostRecent().args[2]; 220 | callback(); 221 | 222 | // assert 223 | promise.then(() => { 224 | expect(true).toEqual(true); 225 | done(); 226 | }); 227 | }); 228 | 229 | it('should create dir when it doesnt exist', (done) => { 230 | // arrange 231 | const testSubject = getTestSubject(); 232 | spyOn(mockFs, 'access').and.callThrough; 233 | spyOn(mockFs, 'mkdirp').and.callThrough; 234 | // act 235 | const promise = testSubject.makeDirectoryIfNeeded('myPath'); 236 | const accessCallback = mockFs.access.calls.mostRecent().args[2]; 237 | accessCallback(new Error('file not found')); 238 | const mkdirpCallback = mockFs.mkdirp.calls.mostRecent().args[1]; 239 | mkdirpCallback(); 240 | 241 | // assert 242 | promise.then(() => { 243 | expect(true).toEqual(true); 244 | done(); 245 | }); 246 | }); 247 | 248 | it('should reject when cant create dir', (done) => { 249 | // arrange 250 | const testSubject = getTestSubject(); 251 | spyOn(mockFs, 'access').and.callThrough; 252 | spyOn(mockFs, 'mkdirp').and.callThrough; 253 | // act 254 | const promise = testSubject.makeDirectoryIfNeeded('myPath'); 255 | const accessCallback = mockFs.access.calls.mostRecent().args[2]; 256 | accessCallback(new Error('file not found')); 257 | const mkdirpCallback = mockFs.mkdirp.calls.mostRecent().args[1]; 258 | mkdirpCallback(new Error('failed to create dir')); 259 | 260 | // assert 261 | promise.catch(err => { 262 | expect(err.message).toEqual('failed to create dir'); 263 | done(); 264 | }); 265 | }); 266 | }); 267 | 268 | describe('generateFileName', () => { 269 | it('should generate the file name', () => { 270 | const testSubject = getTestSubject(); 271 | const result = testSubject.generateFileName('/Users/dan/Dev/something/ts.tmpl', 'taco'); 272 | expect(result).toEqual('taco.ts'); 273 | }); 274 | }); 275 | 276 | describe('getTemplatesInDir', () => { 277 | it('should call readdirSync on provided path', () => { 278 | // arrange 279 | const testSubject = getTestSubject(); 280 | spyOn(mockFs, 'readdirSync').and.returnValue('returnValue'); 281 | // act 282 | const result = testSubject.getTemplatesInDir('./mypath/of/doom'); 283 | expect(result).toEqual('returnValue'); 284 | expect(mockFs.readdirSync.calls.mostRecent().args[0]).toEqual('./mypath/of/doom'); 285 | }); 286 | }); 287 | 288 | }); 289 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs-extra'; 2 | import { basename, extname, join } from 'path'; 3 | 4 | import * as paramCase from 'param-case'; 5 | 6 | import { GeneratorOptions } from './generator-options'; 7 | import { ProjectStructureOptions } from './project-structure-options'; 8 | 9 | export function getPathsBasedOnGeneratorType(generatorType: string, fileName: String, projectStructureOptions: ProjectStructureOptions) { 10 | if (generatorType === COMPONENT_TYPE) { 11 | return { 12 | outputDir: join(projectStructureOptions.absoluteComponentDirPath, fileName), 13 | sourceTemplateDir: join(projectStructureOptions.absolutePathTemplateBaseDir, COMPONENT_TYPE) 14 | }; 15 | } else if (generatorType === DIRECTIVE_TYPE) { 16 | return { 17 | outputDir: join(projectStructureOptions.absoluteDirectiveDirPath, fileName), 18 | sourceTemplateDir: join(projectStructureOptions.absolutePathTemplateBaseDir, DIRECTIVE_TYPE) 19 | }; 20 | } else if (generatorType === PAGE_TYPE) { 21 | return { 22 | outputDir: join(projectStructureOptions.absolutePagesDirPath, fileName), 23 | sourceTemplateDir: join(projectStructureOptions.absolutePathTemplateBaseDir, PAGE_TYPE) 24 | }; 25 | } else if (generatorType === PIPE_TYPE) { 26 | return { 27 | outputDir: join(projectStructureOptions.absolutePipeDirPath), 28 | sourceTemplateDir: join(projectStructureOptions.absolutePathTemplateBaseDir, PIPE_TYPE) 29 | }; 30 | } else if (generatorType === PROVIDER_TYPE) { 31 | return { 32 | outputDir: join(projectStructureOptions.absoluteProviderDirPath), 33 | sourceTemplateDir: join(projectStructureOptions.absolutePathTemplateBaseDir, PROVIDER_TYPE) 34 | }; 35 | } else if (generatorType === TABS_TYPE) { 36 | return { 37 | outputDir: join(projectStructureOptions.absolutePagesDirPath, fileName), 38 | sourceTemplateDir: join(projectStructureOptions.absolutePathTemplateBaseDir, TABS_TYPE) 39 | }; 40 | } 41 | else { 42 | throw new Error("Unknown Generator Type"); 43 | } 44 | } 45 | 46 | export function renderTemplate(templateContent: string, suppliedName: string, fileName: string, className: string, tabContent: string, tabImports: string, tabVariables: string) { 47 | let processedTemplate = replaceAll(templateContent, FILE_NAME, fileName); 48 | processedTemplate = replaceAll(processedTemplate, SUPPLIED_NAME, suppliedName); 49 | processedTemplate = replaceAll(processedTemplate, CLASS_NAME, className); 50 | processedTemplate = replaceAll(processedTemplate, TAB_CONTENT, tabContent); 51 | processedTemplate = replaceAll(processedTemplate, TAB_IMPORTS, tabImports); 52 | processedTemplate = replaceAll(processedTemplate, TAB_VARIABLES, tabVariables); 53 | return processedTemplate; 54 | } 55 | 56 | export function capitalize(input: string) { 57 | return input[0].toUpperCase() + input.substr(1); 58 | } 59 | 60 | export function camelCase(input: string) { 61 | return input.replace(/[^A-Za-z0-9]/g, ' ').replace(/^\w|[A-Z]|\b\w|\s+/g, function (match, index) { 62 | if (+match === 0 || match === '-' || match === '.' ) { 63 | return ""; // or if (/\s+/.test(match)) for white spaces 64 | } 65 | return index === 0 ? match.toLowerCase() : match.toUpperCase(); 66 | }); 67 | } 68 | 69 | export function stringToClassCase(input: string) { 70 | return capitalize(camelCase(input)); 71 | } 72 | 73 | export function kebabCase(input: string) { 74 | return paramCase(input); 75 | } 76 | 77 | export function replaceAll(input: string, toReplace: string, replacement: string) { 78 | return input.split(toReplace).join(replacement); 79 | } 80 | 81 | export function makeDirectoryIfNeeded(absolutePath: string): Promise { 82 | return new Promise( (resolve, reject) => { 83 | //console.log(fs); 84 | fs.access(absolutePath, fs.F_OK, err => { 85 | if (err) { 86 | // the directory does not exist, so create it 87 | fs.mkdirp(absolutePath, err => { 88 | if (err) { 89 | // we failed to create the directory 90 | reject(err); 91 | } else { 92 | resolve(); 93 | } 94 | }); 95 | } else { 96 | // the directory exists already, sweet 97 | resolve(); 98 | } 99 | }); 100 | }); 101 | } 102 | 103 | export function getTemplatesInDir(templateDirectoryPath: string) { 104 | return fs.readdirSync(templateDirectoryPath); 105 | } 106 | 107 | export function filterAndCleanUpTemplates(templates: string[], templateDirectoryPath: string, options: GeneratorOptions) { 108 | // filter the templates out 109 | const filteredTemplates = templates.filter( filePath => { 110 | // make sure it's a valid template file 111 | return extname(filePath) === TEMPLATE_EXTENSION; 112 | }).map(fileName => { 113 | return `${templateDirectoryPath}/${fileName}` 114 | }).filter(filePath => { 115 | // filter out sass if the option is passed to do so 116 | if ( filePath.includes('scss.tmpl') && ! options.includeSass) { 117 | return false; 118 | } 119 | return true; 120 | }).filter(filePath => { 121 | // filter out spec if the option is passed to do so 122 | if (filePath.includes('spec.ts.tmpl') && ! options.includeSpec) { 123 | return false; 124 | } 125 | return true; 126 | }); 127 | 128 | return filteredTemplates; 129 | } 130 | 131 | export function generateFileName(templatePath, fileName) { 132 | // the files are named such realExtension${TEMPLATE_EXTENSION}, 133 | // so removing ${TEMPLATE_EXTENSION} allows us to prefix the 134 | // filename and call it a day 135 | const realFileExtension = basename(templatePath, TEMPLATE_EXTENSION) 136 | const result = `${fileName}.${realFileExtension}`; 137 | return result; 138 | } 139 | 140 | export function mergeObjects(obj1: any, obj2: any ) { 141 | if (! obj1) { 142 | obj1 = {}; 143 | } 144 | if (! obj2) { 145 | obj2 = {}; 146 | } 147 | var obj3 = {}; 148 | for (var attrname in obj1) { 149 | (obj3)[attrname] = obj1[attrname]; 150 | } 151 | for (var attrname in obj2) { 152 | (obj3)[attrname] = obj2[attrname]; 153 | } 154 | return obj3; 155 | } 156 | 157 | export const COMPONENT_TYPE = 'component'; 158 | export const DIRECTIVE_TYPE = 'directive'; 159 | export const PAGE_TYPE = 'page'; 160 | export const PIPE_TYPE = 'pipe'; 161 | export const PROVIDER_TYPE = 'provider'; 162 | export const TABS_TYPE = 'tabs'; 163 | 164 | const FILE_NAME = '$FILENAME'; 165 | const CLASS_NAME = '$CLASSNAME'; 166 | const SUPPLIED_NAME = '$SUPPLIEDNAME'; 167 | const TAB_CONTENT = '$TAB_CONTENT'; 168 | const TAB_IMPORTS = '$TAB_IMPORTS'; 169 | const TAB_VARIABLES = '$TAB_VARIABLES'; 170 | 171 | const TEMPLATE_EXTENSION = '.tmpl'; 172 | 173 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "declaration": true, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": ["es2015"], 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "noImplicitAny": false, 11 | "outDir": "./dist/compiled", 12 | "removeComments": true, 13 | "target": "es5", 14 | "types": ["jasmine", "node"] 15 | }, 16 | "include": [ 17 | "./src/**/*.ts" 18 | ], 19 | "compileOnSave": false, 20 | "buildOnSave": false, 21 | "atom": { 22 | "rewriteTsconfig": false 23 | } 24 | } --------------------------------------------------------------------------------