├── .gitignore ├── src ├── generator │ ├── templates │ │ ├── README.md │ │ ├── default │ │ │ ├── post.md │ │ │ ├── product.md │ │ │ └── doc.md │ │ ├── basics.js │ │ └── list.js │ └── index.js ├── index.js └── inquirer │ └── index.js ├── .badge.json ├── package.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /src/generator/templates/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.badge.json: -------------------------------------------------------------------------------- 1 | {"data":{"name":"create-content"},"installed":["npm-version","npm-download-total","npm-license"]} -------------------------------------------------------------------------------- /src/generator/templates/default/post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: <%= options.title %> 3 | description: <%= options.description || '' %> 4 | image: <%= options.image || '' %> 5 | tags: <%= options.tags || [] %> 6 | category: <%= options.category || '' %> 7 | publishedAt: <%= options.publishedAt || '' %> 8 | --- 9 | 10 | 11 | 12 | ## Block Heading 1 13 | -------------------------------------------------------------------------------- /src/generator/templates/default/product.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: <%= options.title || '' %> 3 | description: <%= options.description || '' %> 4 | image: <%= options.image || '' %> 5 | price: <%= options.price || '' %> 6 | tags: <%= options.tags || '' %> 7 | category: <%= options.category || '' %> 8 | --- 9 | 10 | 11 | 12 | ## Block Heading 1 13 | -------------------------------------------------------------------------------- /src/generator/templates/default/doc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: <%= options.title %> 3 | description: <%= options.description || '' %> 4 | position: <%= options.position %> 5 | version: <%= options.version %> 6 | category: <%= options.category || '' %> 7 | fullscreen: <%= options.fullscreen || '' %> 8 | menuTitle: <%= options.menuTitle || '' %> 9 | subtitle: <%= options.subtitle || '' %> 10 | badge: <%= options.badge || '' %> 11 | --- 12 | 13 | 14 | 15 | ## Page Sectopm 1 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const chalk = require('chalk') 4 | const { log, clear } = require('console') 5 | const figlet = require('figlet') 6 | const ContentInquirer = require('./inquirer') 7 | const Generator = require('./generator') 8 | 9 | clear() 10 | 11 | console.log(chalk.green( 12 | figlet.textSync('Content', { horizontalLayout: 'full' }) 13 | )) 14 | 15 | const run = async () => { 16 | const args = process.argv.slice(2); 17 | 18 | if (!args || !args.length) { 19 | log(chalk.red('🛑\ You need to provide a file name!!! Try run again "create-content you-file-name"')) 20 | return 21 | } 22 | 23 | log(chalk.yellow(`Tell me your needs and I will create your content file accordingly\ 😉`)) 24 | log('') 25 | 26 | const filename = args[0] 27 | 28 | //Prompt 29 | const inquirer = new ContentInquirer() 30 | const answers = await inquirer.prompt({ filename }) 31 | 32 | //Generate 33 | const generator = new Generator() 34 | generator.create(filename, answers) 35 | } 36 | 37 | run() -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-content", 3 | "version": "0.0.4", 4 | "description": "Script to create easy docs from templates for blog post, documentation page, or from your own template.", 5 | "main": "src/index.js", 6 | "bin": "src/index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "content", 12 | "nuxt", 13 | "docs", 14 | "markdown", 15 | "csv", 16 | "nodejs", 17 | "post", 18 | "frontmatter", 19 | "yaml", 20 | "article", 21 | "template" 22 | ], 23 | "author": "Maya Shavin", 24 | "license": "MIT", 25 | "dependencies": { 26 | "chalk": "^4.1.0", 27 | "clear": "^0.1.0", 28 | "clui": "^0.3.6", 29 | "ejs": "^3.1.5", 30 | "figlet": "^1.5.0", 31 | "inquirer": "^7.3.3", 32 | "inquirer-datepicker-prompt": "^0.4.2", 33 | "inquirer-file-tree-selection-prompt": "^1.0.6", 34 | "inquirer-fuzzy-path": "^2.3.0" 35 | }, 36 | "devDependencies": { 37 | "@types/ejs": "^3.0.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Maya Shavin 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 | -------------------------------------------------------------------------------- /src/inquirer/index.js: -------------------------------------------------------------------------------- 1 | const inquirer = require('inquirer') 2 | const chalk = require('chalk') 3 | const { log, clear } = require('console') 4 | const templateDatas = require('../generator/templates/list') 5 | const basics = require('../generator/templates/basics') 6 | 7 | inquirer.registerPrompt('datetime', require('inquirer-datepicker-prompt')) 8 | inquirer.registerPrompt('fuzzypath', require('inquirer-fuzzy-path')) 9 | 10 | class ContentInquirer { 11 | constructor() {} 12 | /** 13 | * Inquire questions for setting up the file 14 | * @param {Object} options - contains filename given from the console 15 | * @returns {Object} 16 | */ 17 | async prompt({ filename }) { 18 | const starters = await inquirer.prompt(basics) 19 | 20 | if (starters.template === 'manual') return starters 21 | 22 | const next = templateDatas.find(sect => sect.type === starters.template) 23 | 24 | clear() 25 | log(chalk.yellow(`🗒️\ Setup basic content for ${next.title.toLowerCase()} "${filename}":`)) 26 | const answers = await inquirer.prompt(next.fields) 27 | 28 | return { 29 | ...starters, 30 | ...answers, 31 | template: next.template 32 | } 33 | } 34 | } 35 | 36 | module.exports = ContentInquirer -------------------------------------------------------------------------------- /src/generator/templates/basics.js: -------------------------------------------------------------------------------- 1 | const excludePath = path => ['node_modules', '.git', '.nuxt', '.vercel'].reduce((isExclude, folder) => isExclude || path.startsWith(folder), false) 2 | 3 | module.exports = [ 4 | { 5 | name: 'directory', 6 | type: 'fuzzypath', 7 | message: 'Save to directory:', 8 | excludePath, 9 | itemType: 'directory', 10 | default: '', 11 | suggestOnly: true, 12 | }, 13 | { 14 | name: 'template', 15 | type: 'list', 16 | message: 'Please pick from a template:', 17 | default: 'post', 18 | choices: [{ 19 | name: 'Blog post/article', 20 | value: 'post' 21 | }, { 22 | name: 'Product page', 23 | value: 'product' 24 | }, { 25 | name: 'Documentation page', 26 | value: 'doc' 27 | }, 28 | { 29 | name: 'Manual', 30 | value: 'manual' 31 | }], 32 | }, 33 | { 34 | name: 'type', 35 | type: 'list', 36 | message: 'Choose the file type:', 37 | choices: [{ 38 | name: 'Markdown', 39 | value: '.md' 40 | }, { 41 | name: 'CSV', 42 | value: '.csv' 43 | }, { 44 | name: 'YAML', 45 | value: '.yaml' 46 | }], 47 | default: '.md', 48 | when: (response) => response.template === 'manual' 49 | }, 50 | ] -------------------------------------------------------------------------------- /src/generator/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const chalk = require('chalk') 3 | const { log } = require('console') 4 | const { join } = require('path') 5 | const { Spinner } = require('clui') 6 | const ejs = require('ejs') 7 | // const ContentInquirer = require('../inquirer') 8 | 9 | /** 10 | * @typedef {Object} Options 11 | * @property {String} directory 12 | * @property {String} template 13 | */ 14 | 15 | class Generator { 16 | constructor(){} 17 | /** 18 | * Create file based on given file name and options 19 | * @param {String} file 20 | * @param {Options} options 21 | */ 22 | async create(file, options) { 23 | if (!file) return; 24 | 25 | const isDirExist = fs.existsSync(join(process.cwd(), options.directory)) 26 | 27 | // if (isDirExist) { 28 | // const inquirer = new ContentInquirer() 29 | // const response = await inquirer.prompt({ 30 | // type: 'confirm', 31 | // default: true, 32 | // name: 'overwrite', 33 | // message: 'A file with same name exists. Do you want to overwrite it?', 34 | // }) 35 | 36 | // if (!response.overwrite) { 37 | // console.log('👋\ Aborted! Bye bye!') 38 | // console.log('') 39 | // return; 40 | // } 41 | // } 42 | 43 | const spinner = new Spinner(`${isDirExist ? 'Merging' : 'Creating'} content file...`, ['⣾','⣽','⣻','⢿','⡿','⣟','⣯','⣷']) 44 | 45 | spinner.start() 46 | 47 | const dirPath = join(process.cwd(), options.directory) 48 | 49 | if (!isDirExist) { 50 | fs.mkdirSync(dirPath, { recursive: true }) 51 | } 52 | 53 | const extension = options.template === 'manual' ? options.type : options.template.substring(options.template.lastIndexOf('.')) 54 | let contents = '' 55 | 56 | if (options.template !== 'manual') { 57 | const template = fs.readFileSync(join(__dirname, 'templates', options.template), 'utf8') 58 | contents = ejs.render(template, { 59 | options 60 | }) 61 | } 62 | 63 | fs.writeFileSync(join(dirPath, `${file}${extension}`), contents) 64 | 65 | spinner.stop() 66 | 67 | log('') 68 | log(chalk.yellow(`🚀\ File "${chalk.green(file)}" created successfully! Enjoy\ ❤️\!`)) 69 | log('') 70 | } 71 | // TODO - Save a manual created template 72 | /** 73 | * Create and save a new template 74 | * @param {Options} options - confirgurations 75 | */ 76 | template(options) {} 77 | } 78 | 79 | module.exports = Generator -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-content 2 | 3 | [![npm version](https://img.shields.io/npm/v/create-content.svg)](https://www.npmjs.com/package/create-content) 4 | [![npm download](https://img.shields.io/npm/dt/create-content.svg)](https://www.npmjs.com/package/create-content) 5 | [![npm license](https://img.shields.io/npm/l/create-content.svg)](https://www.npmjs.com/package/create-content) 6 | 7 | 8 | ![Create a content file automatically](https://res.cloudinary.com/mayashavin/image/upload/v1602193382/create-content.jpg) 9 | 10 | A CLI tool to auto generate a content file (Markdown, CSV, YAML) based on available templates or customized templates. 11 | 12 | > Designed especially for working with [Nuxtjs](https://nuxtjs.org) and [Content module](https://content.nuxtjs.org). 13 | 14 | ## Install 15 | 16 | ```bash 17 | npm i create-content 18 | ``` 19 | 20 | ## 👩‍💻 How to run 21 | 22 | ```bash 23 | create-content your-file-name 24 | ``` 25 | 26 | And then select from the options to create you file. 27 | 28 | ## Features 29 | 30 | * Create a content file based on an **existing** templates: 31 | 32 | * Blog post in [Markdown format](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) (.md), with YAML formatter blocks to indicate some basic information about the post 33 | 34 | * `title` 35 | * `description` 36 | * `image` 37 | * `tags` 38 | * `category` 39 | * `publishedAt` 40 | 41 | Inspired by [Nuxt Content's layout](https://content.nuxtjs.org/writing#front-matter) 42 | 43 | * Documentation page followed [Theme Docs standards](https://content.nuxtjs.org/themes/docs) in [Markdown format](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) (.md), with YAML formatter blocks consisting the following fields: 44 | * `title` 45 | * `description` 46 | * `position` 47 | * `version` 48 | * `category` 49 | * `fullscreen` 50 | * `menuTitle` 51 | * `subtitle` 52 | * `badge` 53 | 54 | * Product info in [Markdown format](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet) (.md), with YAML formatter blocks to indicate some basic information about the product: 55 | 56 | * `title` 57 | * `description` 58 | * `image` 59 | * `price` 60 | * `tags` 61 | * `category` 62 | 63 | * Create new content file manually with the following format: 64 | * `.md` - Markdown 65 | * `.csv` - CSV 66 | * `.yaml` - YAML 67 | 68 | ## Coming soon 69 | 70 | 1. Create and save new template per project (or globally) 71 | 2. Create new content file based on the new template. 72 | 73 | ## Contributing 74 | 75 | 1. Clone this repository 76 | 2. Install dependencies `npm install` 77 | 3. Develop 78 | 4. Test in local project using `npm link` 79 | 80 | * Run `npm link` 81 | * Go to the target project, run `npm link create-content` 82 | * You now can test if the tool works locally 83 | 84 | Maintained by [Maya Shavin](https://twitter.com/MayaShavin) 85 | -------------------------------------------------------------------------------- /src/generator/templates/list.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | type: 'post', 3 | title: 'Blog post/article', 4 | template: 'default/post.md', 5 | fields: [{ 6 | message: 'Title:', 7 | name: 'title', 8 | type: 'input', 9 | required: true, 10 | },{ 11 | message: 'Short description:', 12 | name: 'description', 13 | type: 'input', 14 | required: true, 15 | }, { 16 | message: "Path to post's cover image (URL or local path):", 17 | name: 'image', 18 | type: 'input', 19 | }, { 20 | message: 'Tags (separate tag by comma): ', 21 | name: 'tags', 22 | type: 'input', 23 | }, { 24 | message: 'Category:', 25 | name: 'category', 26 | type: 'input', 27 | }, { 28 | message: 'When to publish:', 29 | name: 'publishedAt', 30 | type: 'datetime', 31 | format: ['mm', '/', 'dd', '/', 'yyyy', ' ', 'hh', ':', 'MM', ' ', 'TT'], 32 | }] 33 | }, { 34 | type: 'product', 35 | title: 'Product page', 36 | template: 'default/product.md', 37 | fields:[ 38 | { 39 | message: 'Title:', 40 | name: 'title', 41 | type: 'input', 42 | required: true, 43 | }, 44 | { 45 | message: 'Short description:', 46 | name: 'description', 47 | type: 'input', 48 | required: true, 49 | }, 50 | { 51 | message: "Path to post's cover image (URL or local path):", 52 | name: 'image', 53 | type: 'input', 54 | }, 55 | { 56 | message: 'Product price:', 57 | name: 'price', 58 | type: 'input', 59 | required: true, 60 | }, 61 | { 62 | message: 'Tags (separate tag by comma):', 63 | name: 'tags', 64 | type: 'input', 65 | }, 66 | { 67 | message: 'Category:', 68 | name: 'category', 69 | type: 'input', 70 | } 71 | ] 72 | }, { 73 | type: 'doc', 74 | title: 'Documentation page', 75 | template: 'default/doc.md', 76 | fields:[ 77 | { 78 | message: 'Title:', 79 | name: 'title', 80 | type: 'input', 81 | required: true, 82 | }, 83 | { 84 | message: 'Short description:', 85 | name: 'description', 86 | type: 'input', 87 | required: true, 88 | }, 89 | { 90 | name: 'position', 91 | type: 'input', 92 | message: 'Position of page on sidebar:', 93 | }, 94 | { 95 | name: 'version', 96 | type: 'input', 97 | message: 'Version of page:', 98 | }, 99 | { 100 | name: 'category', 101 | type: 'input', 102 | message: 'Category:', 103 | }, 104 | { 105 | name: 'fullscreen', 106 | type: 'boolean', 107 | message: 'Expand to fullscreen:', 108 | default: false, 109 | }, 110 | { 111 | name: 'menuTitle', 112 | type: 'input', 113 | message: 'Title on the side menu for page:', 114 | }, 115 | { 116 | name: 'subtitle', 117 | type: 'input', 118 | message: 'Subtitle of page:', 119 | }, 120 | { 121 | name: 'badge', 122 | type: 'input', 123 | message: 'Badge:', 124 | }] 125 | }] 126 | --------------------------------------------------------------------------------