├── .gitignore ├── .npmignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.ts └── promisify.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | tabWidth: 4 3 | trailingComma: es5 4 | semi: true 5 | arrowParens: always 6 | quoteProps: consistent -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alex Young 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 | # plop-prettier 2 | Use prettier to format plop templates with a custom plop action type. 3 | 4 | ## Installation 5 | ```` 6 | npm i --save plop-prettier 7 | ```` 8 | 9 | ## Usage 10 | In your base plopfile, use `plop.load` to add the "pretty-add" action:: 11 | ```` 12 | const aGenerator = require("./path/to/a/generator"); 13 | 14 | module.exports = function(plop) { 15 | plop.load("plop-prettier"); 16 | 17 | plop.setGenerator("Generator Name", aGenerator); 18 | }; 19 | ```` 20 | You can now use the "pretty-add" action type within your generators: 21 | ```` 22 | const anAction = { 23 | type: "pretty-add", 24 | path: "path/to/generated/file", 25 | template: "path/to/template" 26 | }; 27 | ```` 28 | 29 | ## Options 30 | Prettier options can be provided - for information on available prettier options see the [prettier docs](https://github.com/prettier/prettier#options). 31 | ```` 32 | const aGenerator = require("./path/to/a/generator"); 33 | 34 | module.exports = function(plop) { 35 | plop.load("plop-prettier", { 36 | tabWidth: 4 37 | }); 38 | 39 | plop.setGenerator("Generator Name", aGenerator); 40 | }; 41 | ```` 42 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-prettier", 3 | "version": "2.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "8.10.54", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.54.tgz", 10 | "integrity": "sha512-kaYyLYf6ICn6/isAyD4K1MyWWd5Q3JgH6bnMN089LUx88+s4W8GvK9Q6JMBVu5vsFFp7pMdSxdKmlBXwH/VFRg==", 11 | "dev": true 12 | }, 13 | "@types/prettier": { 14 | "version": "1.18.3", 15 | "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.18.3.tgz", 16 | "integrity": "sha512-48rnerQdcZ26odp+HOvDGX8IcUkYOCuMc2BodWYTe956MqkHlOGAG4oFQ83cjZ0a4GAgj7mb4GUClxYd2Hlodg==", 17 | "dev": true 18 | }, 19 | "prettier": { 20 | "version": "1.18.2", 21 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", 22 | "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", 23 | "dev": true 24 | }, 25 | "typescript": { 26 | "version": "3.6.4", 27 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", 28 | "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", 29 | "dev": true 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plop-prettier", 3 | "version": "3.0.0", 4 | "description": "Use prettier to format plop templates", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "directories": { 8 | "lib": "lib" 9 | }, 10 | "scripts": { 11 | "build": "tsc", 12 | "prepublishOnly": "npm run build" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/alsiola/plop-prettier.git" 17 | }, 18 | "keywords": [ 19 | "plop", 20 | "prettier" 21 | ], 22 | "author": "Alex Young", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/alsiola/plop-prettier/issues" 26 | }, 27 | "homepage": "https://github.com/alsiola/plop-prettier#readme", 28 | "devDependencies": { 29 | "@types/node": "^8.10.54", 30 | "@types/prettier": "^1.18.3", 31 | "prettier": "^1.17.0", 32 | "typescript": "^3.6.4" 33 | }, 34 | "peerDependencies": { 35 | "@types/prettier": "^1.18.3", 36 | "prettier": "^1.17.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from "fs"; 3 | import * as prettier from "prettier"; 4 | import promisify from "./promisify"; 5 | 6 | const readFileP = promisify(fs.readFile); 7 | const access = promisify(fs.access); 8 | const writeFileP = promisify(fs.writeFile); 9 | const mkdir = promisify(fs.mkdir); 10 | 11 | const writeFile = (pathToWrite: any, data: any) => writeFileP(pathToWrite, data, "utf-8"); 12 | const readFile = (pathToWrite: string) => readFileP(pathToWrite, "utf-8"); 13 | const fileExists = (pathToWrite: string) => access(pathToWrite).then(() => true, () => false); 14 | 15 | const interfaceCheck = (action: { path: any }) => { 16 | if (typeof action !== "object") { 17 | return `Invalid action object: ${JSON.stringify(action)}`; 18 | } 19 | 20 | const { path } = action; 21 | 22 | if (typeof path !== "string" || path.length === 0) { 23 | return `Invalid path "${path}"`; 24 | } 25 | 26 | return true; 27 | }; 28 | 29 | const prettyAdd = async ( 30 | data: any, 31 | cfg: { template?: any; path?: any; templateFile?: any }, 32 | plop: { 33 | getPlopfilePath: () => string; 34 | getDestBasePath: { (): string; (): string }; 35 | renderString: { (arg0: any, arg1: any): string; (arg0: any, arg1: any): string }; 36 | }, 37 | prettierOpts: prettier.Options 38 | ) => { 39 | // if not already an absolute path, make an absolute path from the basePath (plopfile location) 40 | const makeTmplPath = (p: any) => path.resolve(plop.getPlopfilePath(), p); 41 | const makeDestPath = (p: any) => path.resolve(plop.getDestBasePath(), p); 42 | 43 | var { template } = cfg; 44 | const fileDestPath = makeDestPath(plop.renderString(cfg.path || "", data)); 45 | 46 | try { 47 | if (cfg.templateFile) { 48 | template = await readFile(makeTmplPath(cfg.templateFile)); 49 | } 50 | if (template == null) { 51 | template = ""; 52 | } 53 | 54 | // check path 55 | const pathExists = await fileExists(fileDestPath); 56 | 57 | if (pathExists) { 58 | throw `File already exists\n -> ${fileDestPath}`; 59 | } else { 60 | const dirExists = await fileExists(path.dirname(fileDestPath)); 61 | if (!dirExists) { 62 | await mkdir(path.dirname(fileDestPath)); 63 | } 64 | await writeFile( 65 | fileDestPath, 66 | prettier.format(plop.renderString(template, data), prettierOpts) 67 | ); 68 | } 69 | 70 | // return the added file path (relative to the destination path) 71 | return fileDestPath.replace(path.resolve(plop.getDestBasePath()), ""); 72 | } catch (err) { 73 | if (typeof err === "string") { 74 | throw err; 75 | } else { 76 | throw err.message || JSON.stringify(err); 77 | } 78 | } 79 | }; 80 | 81 | function plopPrettier( 82 | plop: { 83 | setDefaultInclude: (arg0: { actionTypes: boolean }) => void; 84 | setActionType: ( 85 | arg0: string, 86 | arg1: (data: any, config: any, plop: any) => Promise 87 | ) => void; 88 | }, 89 | config?: prettier.Options 90 | ) { 91 | // Destructure prettier options out of config otherwise unrecognised properties 92 | // are passed to prettier and cause a console warning 93 | const { 94 | arrowParens, 95 | quoteProps, 96 | printWidth, 97 | tabWidth, 98 | useTabs, 99 | semi, 100 | singleQuote, 101 | trailingComma, 102 | bracketSpacing, 103 | jsxBracketSameLine, 104 | rangeStart, 105 | rangeEnd, 106 | parser, 107 | filepath, 108 | } = config; 109 | const prettierOpts = { 110 | arrowParens, 111 | quoteProps, 112 | printWidth, 113 | tabWidth, 114 | useTabs, 115 | semi, 116 | singleQuote, 117 | trailingComma, 118 | bracketSpacing, 119 | jsxBracketSameLine, 120 | rangeStart, 121 | rangeEnd, 122 | parser, 123 | filepath, 124 | }; 125 | plop.setDefaultInclude({ actionTypes: true }); 126 | plop.setActionType("pretty-add", async (data: any, config: any, plop: any) => { 127 | const validInterface = interfaceCheck(config); 128 | 129 | if (!validInterface) { 130 | throw validInterface; 131 | } 132 | 133 | return await prettyAdd(data, config, plop, prettierOpts); 134 | }); 135 | return plop; 136 | } 137 | 138 | module.exports = plopPrettier; 139 | -------------------------------------------------------------------------------- /src/promisify.ts: -------------------------------------------------------------------------------- 1 | export type AnyFunction = (...args: any[]) => T; 2 | 3 | export type Promisify = ( 4 | fnOrObj: object | AnyFunction, 5 | fnName?: string 6 | ) => (...args: any[]) => Promise; 7 | 8 | const promisify = (fnOrObj: object | AnyFunction, fnName?: string) => ( 9 | ...args: any[] 10 | ): Promise => { 11 | return new Promise((resolve, reject) => { 12 | const callback = (err, results) => { 13 | if (err) { 14 | return reject(err); 15 | } 16 | resolve(results); 17 | }; 18 | 19 | if (fnName) { 20 | fnOrObj[fnName].apply(fnOrObj, [...args, callback]); 21 | } else if (typeof fnOrObj === "function") { 22 | fnOrObj.apply(this, [...args, callback]); 23 | } else { 24 | throw new Error( 25 | "If no object property is passed, first argument must be a function." 26 | ); 27 | } 28 | }); 29 | }; 30 | 31 | export default promisify; 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "lib": ["es6"], 8 | "rootDir": "./src", 9 | "outDir": "./lib", 10 | "declaration": true 11 | } 12 | } 13 | --------------------------------------------------------------------------------