├── .editorconfig ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── bin └── clarion ├── lib ├── clarion.js ├── commands │ ├── add.js │ ├── config.js │ ├── new.js │ └── remove.js ├── data │ ├── newProject.js │ ├── newProjectQuestions.js │ ├── projectData.js │ └── styleContentData.js ├── services │ ├── configService.js │ ├── directoryService.js │ ├── fileService.js │ ├── logService.js │ ├── projectService.js │ └── shellService.js └── taskManager.js ├── package-lock.json ├── package.json ├── src ├── clarion.ts ├── commands │ ├── add.ts │ ├── new.ts │ └── remove.ts ├── data │ ├── newProject.ts │ ├── projectData.ts │ └── styleContentData.ts └── services │ ├── configService.ts │ ├── directoryService.ts │ ├── fileService.ts │ ├── logService.ts │ ├── projectService.ts │ └── shellService.ts ├── tests ├── commands │ ├── add.spec.ts │ └── new.spec.ts └── services │ ├── configService.spec.ts │ └── projectDataService.spec.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at burton@breakstuff.io. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 break-stuff 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clarion 2 | 3 |

4 | product clarion logo 5 |

6 | 7 | ## A CSS and Design System Framework for well crafted applications. 8 | 9 | _Check out the official Documentation here: [projectclarion.com](http://projectclarion.com)_ 10 | 11 | ## Installation 12 | 13 | Install [Node.js](https://nodejs.org/), if you don't already have it installed. 14 | 15 | In your terminal or command prompt type: 16 | 17 | ```bash 18 | npm install -g clarion 19 | - or - 20 | yarn global add clarion 21 | ``` 22 | 23 | ## Start a New Project 24 | 25 | ```bash 26 | clarion new 27 | ``` 28 | 29 | Answer a few questions about you would like to configure your project and it will be scaffolded out for you. 30 | 31 | Your dependencies will also automatically be installed! 32 | 33 | ## Run Your New Project 34 | 35 | After your dependencies are installed you can run your project. 36 | 37 | ```bash 38 | cd ProjectName 39 | npm run dev 40 | ``` 41 | 42 | ## About Your New Project 43 | 44 | The default project is configured with [SASS](http://sass-lang.com/) using the `.scss` syntax and [webpack](https://webpack.js.org/) as the compiler and module manager. 45 | 46 | The project architecture implements the Clarion Style Architecture. 47 | 48 | ```bash 49 | MyProject/ 50 | | 51 | |--build/ 52 | | 53 | |--src/ 54 | | |--sass/ 55 | | | |--00_Abstracts/ # Variables, Functions, Mixins, and Placeholders 56 | | | | 57 | | | |--01_Base/ # Resets/Normalize, Typography Rules, Etc. 58 | | | | |--index.scss # Manifest File 59 | | | | 60 | | | |--02_Vendors/ # Style sheets provided by a third party such as themes or plug-ins 61 | | | | |--index.scss # Manifest File 62 | | | | 63 | | | |--03_Elements/ # Styles for HTML tags, such as a form label, an input or a button 64 | | | | |--index.scss # Manifest File 65 | | | | 66 | | | |--04_Components/ # Cards, Carousels, and Navbars 67 | | | | |--index.scss # Manifest File 68 | | | | 69 | | | |--05_Layouts/ # Grid System, Header, Footer, and Sidebars 70 | | | | |--index.scss # Manifest File 71 | | | | 72 | | | |--06_Pages/ # Page specific styles 73 | | | | |--index.scss # Manifest File 74 | | | | 75 | | | |--07_Utilities/ # Utilities and Helper Classes 76 | | | | |--index.scss # Manifest File 77 | | | | 78 | | | |--styles.scss/ # Main Sass Manifest 79 | | | 80 | | |--scripts/ 81 | | |--components/ # Component-Specific Scripts 82 | | |--services/ # Reusable Functionality 83 | | |--main.js 84 | | 85 | |--index.html 86 | |--package.json 87 | |--postcss.config.js 88 | |--webpack.config.js 89 | ``` 90 | 91 | ## Adding a New File 92 | 93 | Additional style files can easily be manged through the CLI as well. 94 | 95 | ### Usage 96 | 97 | ```bash 98 | clarion add 99 | ``` 100 | 101 | #### Example 102 | 103 | ```bash 104 | clarion add element headings 105 | ``` 106 | 107 | This will create the file _headings.scss in the 03_Elements directory as well as add "@import '_headings.scss'" import statement to the directory manifest file so it can be included in your final CSS file. 108 | 109 | ## Removing a File 110 | 111 | Similar to adding a file, removing files can also be done through the CLI. 112 | 113 | ### Usage 114 | 115 | ```bash 116 | clarion remove 117 | ``` 118 | 119 | #### Example 120 | 121 | ```bash 122 | clarion remove element headings 123 | ``` 124 | 125 | This will remove the file _headings.scss in the 03_Elements directory as well as remove "@import '_headings.scss'" import statement from the directory manifest file. 126 | 127 | ## Building Your Project 128 | 129 | To build your application for final use, run the build command. 130 | 131 | ```bash 132 | npm run build 133 | ``` 134 | 135 | The final compiled JavaScript and CSS file are in build directory in the root of your project. 136 | 137 | ## Options 138 | 139 | These are options you can run when initializing you project. 140 | 141 | ## Project Content 142 | 143 | | Option | Description | 144 | | --- | --- | 145 | | Starter Project | generate the style architecture, the style framework, as well as any task runners/bundlers and optimizers needed to begin developing a web application. 146 | | Styles Only | generate the style architecture only (great for integrating into frameworks) | 147 | | Architecture Only | generate a the style architecture without any of the start-up files | 148 | 149 | ## Style Format 150 | 151 | | Option | Description | 152 | | --- | --- | 153 | | [SCSS](https://sass-lang.com/) | files are in .scss format | 154 | | [SASS](https://sass-lang.com/) | files are in .sass format | 155 | | [LESS](http://lesscss.org/) | files are in .less format | 156 | 157 | ## Task Runners and Bundlers 158 | 159 | | Option | Description | 160 | | --- | --- | 161 | | [Webpack](https://webpack.js.org/) | configure project for WebPack bundler | 162 | | [Parcel](https://parceljs.org/) | configure project for Parcel bundler | 163 | | [Gulp](https://gulpjs.com/) | configure project for Gulp task runner | 164 | | [Grunt](https://gruntjs.com/) | configure project for Grunt task runner | 165 | 166 | ## Changelog 167 | 168 | 3.8.4 - Fix color palette. 169 | 170 | 3.8.3 - Updated pipeline config files to handle 'less' and 'sass' configurations better. 171 | 172 | 3.8.1 - Fix Grunt build 173 | 174 | 3.8.0 - Miscellaneous bug fixes and added mixins such as: `z-index` mixin, updated default color pallet for accessibility, `$content-width` variable, font families variables are now a map and use the `font` mixin, media queries now use `rem`s instead of `px`s, and spacing mixins now follow standard CSS patterns. 175 | 176 | 3.7.1 - Fixed bug in `Pow` function for some bundlers/task runners for decimals. 177 | 178 | 3.7.0 - Added [display mixins](https://projectclarion.com/framework/documentation/mixins/display.html) - `full-width`, `full-height`, `full-screen`, and `screen-reader-only` 179 | 180 | 3.6.0 - Added `important` parameter on mixins 181 | 182 | 3.5.1 - Updated spacing variable to be more inline with naming convention 183 | 184 | 3.5.0 - Add [border mixin](https://projectclarion.com/framework/documentation/mixins/border.html) 185 | 186 | 3.4.0 - Fix [padding and margin mixin](https://projectclarion.com/framework/documentation/mixins/spacing.html) parameter order 187 | 188 | 3.4.0 - Fix [directory creation](https://projectclarion.com/cli/documentation/add.html#adding-directories) logic 189 | 190 | 3.2.2 - Fix CleanWebpackPlugin error for WebPack projects. 191 | 192 | 3.2.1 - Fix color functions to handle "black" and "white" values 193 | 194 | 3.2.0 - Streamlined new project setup. 195 | 196 | 3.1.0 - Updated variables, added new color contrast logic, and fixed SASS file references. 197 | 198 | 3.0.2 - Fixed file reference error in SCSS. 199 | 200 | 3.0.1 - Added additional border radius mixins and fixed some error messages. 201 | 202 | 3.0.0 - Added style framework for SASS and SCSS. 203 | 204 | 2.1.0 - Added a default configuration option and some bug fixes. 205 | 206 | 2.0.1 - Updated documentation. 207 | 208 | 2.0.0 - Added new CLI interface. 209 | 210 | 1.1.2 - Fixed hot reloading for Webpack. 211 | 212 | 1.1.1 - Added ability to add new Directories [via the CLI](https://projectclarion.com/documentation/architecture/). 213 | 214 | 1.0.3 - Replaced failing 'extract-text-webpack-plugin' with 'mini-css-extract-plugin' for Webpack 4. 215 | 216 | 1.0.1 - Fixed an type-o in the Grunt project. 217 | 218 | 1.0.0 - Final testing and added documentation via markdown files in each directory. 219 | 220 | 0.9.2 - Fixed bugs in SASS projects. 221 | 222 | 0.9.1 - Fixed Webpack build error. 223 | 224 | 0.9.0 - Refactored to use a better templating system and added unit tests. 225 | 226 | 0.8.7 - Updated documentation to include new site URL and install instructions. 227 | 228 | 0.8.6 - Temporarily removed "extract-text-webpack-plugin" as it is currently incompatible with Webpack v4. 229 | 230 | 0.8.5 - Updated Webpack project for changes in version 4. 231 | 232 | 0.8.1 - Added option for Parcel project creation. 233 | 234 | 0.7.1 - Added missing dependency for Gulp project. 235 | 236 | 0.7.0 - Revised dependency management so the latest packages are always installed, added Grunt, and added 'pixrem' to postcss. 237 | 238 | 0.6.1 - Fixed error in gulpfile.js. 239 | 240 | 0.6.0 - Modified add feature to find any directory name rather than only those in the Clarion Style Architecture. 241 | 242 | 0.5.0 - Renamed 02_Themes to 02_Vendors. 243 | 244 | 0.4.1 - Added link to documentation site. 245 | -------------------------------------------------------------------------------- /bin/clarion: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/clarion.js'); -------------------------------------------------------------------------------- /lib/clarion.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const commander = require("commander"); 12 | const inquirer_1 = require("inquirer"); 13 | const newProject_1 = require("./data/newProject"); 14 | const new_1 = require("./commands/new"); 15 | const add_1 = require("./commands/add"); 16 | const remove_1 = require("./commands/remove"); 17 | const configService_1 = require("./services/configService"); 18 | commander 19 | .version("3.8.4") 20 | .usage(""); 21 | commander 22 | .command("new") 23 | .description('Create a new project') 24 | .action(() => __awaiter(this, void 0, void 0, function* () { 25 | let projectName = ''; 26 | let pipeline = ''; 27 | const projectStartResponse = yield inquirer_1.prompt(newProject_1.starterQuestions.projectStart); 28 | const newProject = new new_1.NewProject(); 29 | if (projectStartResponse.projectStart === 'Manual Configuration') { 30 | const projectTypeResponse = yield inquirer_1.prompt(newProject_1.starterQuestions.projectType); 31 | if (projectTypeResponse.projectType === 'Starter Project') { 32 | const projectNameResponse = yield inquirer_1.prompt(newProject_1.starterQuestions.projectName); 33 | const pipelineResponse = yield inquirer_1.prompt(newProject_1.starterQuestions.pipeline); 34 | projectName = projectNameResponse.projectName; 35 | pipeline = pipelineResponse.pipeline; 36 | } 37 | const styleFormatResponses = yield inquirer_1.prompt(newProject_1.starterQuestions.styleFormat); 38 | newProject.init(projectTypeResponse.projectType, projectName, styleFormatResponses.styleFormat, pipeline); 39 | } 40 | else { 41 | const projectNameResponse = yield inquirer_1.prompt(newProject_1.starterQuestions.projectName); 42 | newProject.init('default', projectNameResponse.projectName, 'SCSS', 'Webpack'); 43 | } 44 | })); 45 | commander 46 | .command('add [filename]') 47 | .description('Use "add " to add a style sheet to a specific directory || Use "add " to add a style sheet to current directory') 48 | .action((dir, filename) => { 49 | let add = new add_1.Add(); 50 | if (dir === 'directory') { 51 | add.addNewDirectory(filename); 52 | } 53 | else { 54 | if (filename) 55 | add.addFileToSpecifiedDirectory(dir, filename); 56 | else 57 | add.addFileToCurrentDirectory(dir); 58 | } 59 | }); 60 | commander 61 | .command('mkdir ') 62 | .description('Add new style directory to architecture') 63 | .action((foldername) => { 64 | let add = new add_1.Add(); 65 | add.addNewDirectory(foldername); 66 | }); 67 | commander 68 | .command('remove [filename]') 69 | .description('Use "add " to add a style sheet to a specific directory || Use "add " to add a style sheet to current directory') 70 | .action((dir, filename) => { 71 | let remove = new remove_1.Remove(); 72 | if (filename) 73 | remove.removeFileFromSpecifiedDirectory(dir, filename); 74 | else 75 | remove.removeFileFromCurrentDirectory(dir); 76 | }); 77 | commander 78 | .command('config') 79 | .description('Configure Clarion to your development environment') 80 | .action(() => { 81 | let configService = new configService_1.ConfigService(); 82 | configService.updateConfigInfo(); 83 | }); 84 | commander.parse(process.argv); 85 | -------------------------------------------------------------------------------- /lib/commands/add.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const fileService_1 = require("../services/fileService"); 4 | const logService_1 = require("../services/logService"); 5 | const directoryService_1 = require("../services/directoryService"); 6 | const configService_1 = require("../services/configService"); 7 | class Add { 8 | constructor() { 9 | this._fileService = new fileService_1.FileService(); 10 | this._logService = new logService_1.LogService(); 11 | this._directoryService = new directoryService_1.DirectoryService(); 12 | this._configService = new configService_1.ConfigService(); 13 | this._config = this._configService.getConfigData(); 14 | } 15 | addFileToCurrentDirectory(fileName) { 16 | if (!this._fileService.getManifestFile("./")) { 17 | this._logService.warning("Sorry, this is not a directory you can add styles to or you may be missing parameters."); 18 | } 19 | else { 20 | this.processNewFile(".", fileName); 21 | } 22 | } 23 | addNewDirectory(folderName) { 24 | let extension = this._fileService.getFileExtension("/"); 25 | let rootDirectory = this._directoryService.findStyleRootDirectory(); 26 | this._directoryService.createDirectory(`${rootDirectory}/${folderName}`); 27 | this._fileService.saveFile(`${rootDirectory}/${folderName}/index.${extension}`, ""); 28 | this._fileService.addFileToManifest(`@import './${folderName}/index${this._fileService.getImportExtension(extension)}`, `${rootDirectory}/styles.${extension}`, true); 29 | } 30 | addFileToSpecifiedDirectory(dir, fileName) { 31 | let directoryName = this._directoryService.findDirectoryByName(dir); 32 | if (directoryName) { 33 | let pathToDirectory = this._directoryService.findDirectory(directoryName); 34 | if (pathToDirectory) { 35 | this.processNewFile(pathToDirectory, fileName); 36 | } 37 | else { 38 | this._logService.warning("Sorry, the directory you specified was not found."); 39 | } 40 | } 41 | else { 42 | this._directoryService.promptForMissingDirectory() 43 | .then(directory => this.addFileToSpecifiedDirectory(directory, fileName)); 44 | } 45 | } 46 | processNewFile(pathToDirectory, fileName) { 47 | let extension = this._fileService.getFileExtension(pathToDirectory); 48 | let newFile = this.getNewFile(fileName, extension); 49 | let manifestFile = `${pathToDirectory}/${this._fileService.getManifestFile(pathToDirectory)}`; 50 | if (!this._fileService.fileExists(`${pathToDirectory}/${newFile}`)) { 51 | let pathToRoot = this.getPathToRoot(fileName); 52 | let importStatement = this._config.importAbstracts && 53 | !pathToDirectory.includes("00_Abstracts") 54 | ? `@import '${pathToRoot}00_Abstracts/index${this._fileService.getImportExtension(extension)}` 55 | : ""; 56 | this._fileService.saveFile(`${pathToDirectory}/${newFile}`, importStatement); 57 | if (this._config.addToManifest) 58 | this._fileService.addFileToManifest(extension === 'sass' || extension === 'scss' ? fileName : newFile, manifestFile, false); 59 | } 60 | else { 61 | this._logService.warning(newFile + " already exists."); 62 | } 63 | } 64 | getNewFile(fileName, extension) { 65 | let directories = fileName.split("/"); 66 | if (directories.length > 1) { 67 | directories[directories.length - 1] = `_${directories[directories.length - 1]}.${extension}`; 68 | return directories.join("/"); 69 | } 70 | return `_${fileName}.${extension}`; 71 | } 72 | getPathToRoot(fileName) { 73 | let pathDepth = fileName.split("/").length; 74 | let pathToRoot = "../"; 75 | for (let i = 1; i < pathDepth; i++) { 76 | pathToRoot += "../"; 77 | } 78 | return pathToRoot; 79 | } 80 | } 81 | exports.Add = Add; 82 | -------------------------------------------------------------------------------- /lib/commands/config.js: -------------------------------------------------------------------------------- 1 | // import { ILogService, LogService } from "../services/logService"; 2 | // import { IConfigService, ConfigService } from "../services/configService"; 3 | // export interface IConfig { 4 | // updateConfig(): void; 5 | // } 6 | // export class Config implements IConfig { 7 | // _logger: ILogService = new LogService(); 8 | // _configService: IConfigService = new ConfigService(); 9 | // updateConfig(): void { 10 | // let config = { 11 | // paths: { 12 | // styles: "./src", 13 | // }, 14 | // format: { 15 | // styles: "scss", 16 | // }, 17 | // addToManifest: true, 18 | // importAbstracts: true 19 | // }; 20 | // // switch (program.args.length) { 21 | // // case 1: 22 | // // this._logger.warning('Please be sure to include the configuration property and value or initialize a config file using the "init" keyword.') 23 | // // break; 24 | // // case 2: 25 | // // this.initializeConfig(); 26 | // // break; 27 | // // case 3: 28 | // // this._configService.saveConfig(program.args[1], program.args[2]); 29 | // // break; 30 | // // default: 31 | // // this._logger.warning('Sorry, we were not able to process your request.'); 32 | // // break; 33 | // // } 34 | // } 35 | // initializeConfig(): void { 36 | // if (program.args[1].toLowerCase() === 'init') { 37 | // this._configService.saveConfig(); 38 | // } else { 39 | // this._logger.warning('Please be sure to include the configuration property and value'); 40 | // } 41 | // } 42 | // } 43 | -------------------------------------------------------------------------------- /lib/commands/new.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const newProject_1 = require("../data/newProject"); 4 | const fileService_1 = require("../services/fileService"); 5 | const projectService_1 = require("../services/projectService"); 6 | const logService_1 = require("../services/logService"); 7 | const directoryService_1 = require("../services/directoryService"); 8 | const shellService_1 = require("../services/shellService"); 9 | const projectData_1 = require("../data/projectData"); 10 | const styleContentData_1 = require("../data/styleContentData"); 11 | class NewProject { 12 | constructor() { 13 | this._fileService = new fileService_1.FileService(); 14 | this._logService = new logService_1.LogService(); 15 | this._directoryService = new directoryService_1.DirectoryService(); 16 | this._shellService = new shellService_1.ShellService(); 17 | this._styleFileRootPath = ''; 18 | } 19 | init(projectType, projectName, styleFormat, pipeline) { 20 | this._projectType = projectType; 21 | this._projectName = projectName; 22 | this._styleFormat = styleFormat; 23 | this._pipeline = pipeline; 24 | this._projectService = new projectService_1.ProjectDataService(pipeline, styleFormat); 25 | this.createNewProject(); 26 | } 27 | createNewProject() { 28 | if (this._projectType !== newProject_1.newProject.options.projectType.architectureOnly 29 | && this._projectType !== newProject_1.newProject.options.projectType.stylesOnly) { 30 | this.createScriptScaffolding(); 31 | this.createTaskRunner(); 32 | this.createPackageJson(); 33 | this.createIndexHtml(); 34 | } 35 | this.createStyleScaffolding(); 36 | this.addStyleFramework(); 37 | this.displayStartupInstructions(); 38 | } 39 | createScriptScaffolding() { 40 | let extension = this._styleFormat.toLowerCase(); 41 | this._styleFileRootPath = `./src/${extension}/styles.${extension}`; 42 | let mainContents = this._pipeline === newProject_1.newProject.options.pipeline.webpack ? `import '../${extension}/styles.${extension}';` : ''; 43 | if (this._projectName) { 44 | this._directoryService.createDirectory(this._projectName); 45 | } 46 | projectData_1.projectData.projectDirectories.forEach(x => { 47 | this._directoryService.createDirectory(x.replace('%%projectName%%', this._projectName)); 48 | }); 49 | this._fileService.saveFile(`./${this._projectName}/src/scripts/main.js`, mainContents); 50 | } 51 | createStyleScaffolding() { 52 | let extension = this._styleFormat.toLowerCase(); 53 | this.createStyleRootDirectory(extension); 54 | let importStatements = this.createStyleDirectories(extension); 55 | this.createRootManifest(extension, importStatements); 56 | } 57 | createStyleRootDirectory(extension) { 58 | this._styleRootPath = `./${extension}`; 59 | if (this._projectType !== newProject_1.newProject.options.projectType.architectureOnly 60 | && this._projectType !== newProject_1.newProject.options.projectType.stylesOnly) { 61 | this._styleRootPath = `./${this._projectName}/src/${extension}`; 62 | } 63 | this._directoryService.createDirectory(this._styleRootPath); 64 | } 65 | createStyleDirectories(extension) { 66 | let importStatements = ''; 67 | projectData_1.projectData.styleDirectories.forEach(styleDirectory => { 68 | this._directoryService.createDirectory(`${this._styleRootPath}/${styleDirectory.name}`); 69 | this._fileService.saveFile(`${this._styleRootPath}/${styleDirectory.name}/index.${extension}`, ''); 70 | this._fileService.saveFile(`${this._styleRootPath}/${styleDirectory.name}/README.md`, styleDirectory.readMe); 71 | importStatements += `@import './${styleDirectory.name}/index${this._fileService.getImportExtension(extension)}\n`; 72 | }); 73 | return importStatements; 74 | } 75 | createRootManifest(extension, importStatements) { 76 | this._fileService.saveFile(`${this._styleRootPath}/styles.${extension}`, importStatements); 77 | } 78 | createPackageJson() { 79 | projectData_1.projectData.packageJson.name = this.convertToKebabCase(this._projectName); 80 | projectData_1.projectData.packageJson.scripts = this._projectData.projectCommands; 81 | this._fileService.saveFile(`./${this._projectName}/package.json`, JSON.stringify(projectData_1.projectData.packageJson, null, '\t')); 82 | this._shellService.installNPMDependencies(this._projectName, this._projectData.devDependencies, true); 83 | } 84 | convertToKebabCase(text) { 85 | return text.replace(/\s+/g, '-') 86 | .replace(/([a-z0-9])([A-Z])/g, '$1-$2') 87 | .toLowerCase(); 88 | } 89 | createTaskRunner() { 90 | this._projectData = this._projectService.getProjectData(this._projectName); 91 | } 92 | createIndexHtml() { 93 | let isParcel = this._pipeline === newProject_1.newProject.options.pipeline.parcel; 94 | let jsDir = isParcel ? './src/scripts/main.js' : './dist/scripts.js'; 95 | let cssDir = isParcel ? this._styleFileRootPath : './dist/styles.css'; 96 | let contents = this._projectService.getHtmlTemplate(cssDir, jsDir); 97 | this._fileService.saveFile(`./${this._projectName}/index.html`, contents); 98 | } 99 | displayStartupInstructions() { 100 | if (this._projectType !== newProject_1.newProject.options.projectType.architectureOnly 101 | && this._projectType !== newProject_1.newProject.options.projectType.stylesOnly) { 102 | this._logService.info('\nTo get started run the following command:'); 103 | if (this._projectName) 104 | this._logService.info(`cd ${this._projectName}`); 105 | this._logService.info('npm run dev'); 106 | } 107 | } 108 | addStyleFramework() { 109 | if (this._projectType !== newProject_1.newProject.options.projectType.architectureOnly) { 110 | this._directoryService.createDirectory(`${this._styleRootPath}/00_Abstracts/mixins`); 111 | this._directoryService.createDirectory(`${this._styleRootPath}/00_Abstracts/functions`); 112 | styleContentData_1.styleContent 113 | .filter(x => x.format === this._styleFormat.toLowerCase()) 114 | .forEach(x => x.styles 115 | .forEach(y => this._fileService.saveFile(`${this._styleRootPath}/${y.file}`, y.content))); 116 | } 117 | } 118 | } 119 | exports.NewProject = NewProject; 120 | -------------------------------------------------------------------------------- /lib/commands/remove.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const fileService_1 = require("../services/fileService"); 4 | const logService_1 = require("../services/logService"); 5 | const directoryService_1 = require("../services/directoryService"); 6 | class Remove { 7 | constructor() { 8 | this._fileService = new fileService_1.FileService(); 9 | this._logService = new logService_1.LogService(); 10 | this._directoryService = new directoryService_1.DirectoryService(); 11 | } 12 | removeFileFromCurrentDirectory(fileName) { 13 | if (!this._fileService.getManifestFile('./')) { 14 | this._logService.warning('Sorry, this is not a directory you can remove styles from or you may be missing parameters.'); 15 | } 16 | else { 17 | this.processFileRemoval('.', fileName); 18 | } 19 | } 20 | removeFileFromSpecifiedDirectory(dir, fileName) { 21 | let directoryName = this._directoryService.findDirectoryByName(dir); 22 | if (directoryName) { 23 | let pathToDirectory = this._directoryService.findDirectory(directoryName); 24 | if (pathToDirectory) { 25 | this.processFileRemoval(pathToDirectory, fileName); 26 | } 27 | else { 28 | this._logService.warning("Sorry, the directory you specified was not found."); 29 | } 30 | } 31 | else { 32 | this._directoryService.promptForMissingDirectory() 33 | .then(directory => this.removeFileFromSpecifiedDirectory(directory, fileName)); 34 | } 35 | } 36 | processFileRemoval(pathToDirectory, fileName) { 37 | let extension = this._fileService.getFileExtension(pathToDirectory); 38 | let fileToRemove = `_${fileName}.${extension}`; 39 | let manifestFile = `${pathToDirectory}/${this._fileService.getManifestFile(pathToDirectory)}`; 40 | this._fileService.removeFile(`${pathToDirectory}/${fileToRemove}`); 41 | this._fileService.removeFileFromManifest(extension === 'sass' || extension === 'scss' ? fileName : fileToRemove, manifestFile); 42 | } 43 | } 44 | exports.Remove = Remove; 45 | -------------------------------------------------------------------------------- /lib/data/newProject.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.starterQuestions = { 4 | projectStart: [ 5 | { 6 | type: "list", 7 | name: "projectStart", 8 | message: "Start with default settings or would you like to customize your setup?", 9 | choices: ["Default (Starter Project, SCSS, Webpack)", "Manual Configuration"] 10 | } 11 | ], 12 | projectType: [ 13 | { 14 | type: "list", 15 | name: "projectType", 16 | message: "What can we get you started with?", 17 | choices: ["Starter Project", "Styles Only", "Architecture Only"] 18 | } 19 | ], 20 | projectName: [ 21 | { 22 | type: "input", 23 | name: "projectName", 24 | message: "Project Name:", 25 | validate: (value) => value.length > 0 || "Project Name is required." 26 | } 27 | ], 28 | styleFormat: [ 29 | { 30 | type: "list", 31 | name: "styleFormat", 32 | message: "What style format would you like to use?", 33 | choices: ["SCSS", "SASS", "LESS"] 34 | } 35 | ], 36 | pipeline: [ 37 | { 38 | type: "list", 39 | name: "pipeline", 40 | message: "What bundler or task-runner would you like to use?", 41 | choices: ["Webpack", "Parcel", "Gulp", "Grunt"] 42 | } 43 | ] 44 | }; 45 | exports.newProject = { 46 | options: { 47 | projectType: { 48 | starter: "Starter Project", 49 | stylesOnly: "Styles Only", 50 | architectureOnly: "Architecture Only" 51 | }, 52 | styleFormat: { 53 | scss: "SCSS", 54 | sass: "SASS", 55 | less: "LESS" 56 | }, 57 | pipeline: { 58 | webpack: "Webpack", 59 | parcel: "Parcel", 60 | gulp: "Gulp", 61 | grunt: "Grunt" 62 | } 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /lib/data/newProjectQuestions.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.newProjectQuestions = [ 4 | { 5 | type: 'input', 6 | name: 'projectName', 7 | message: 'Project Name:', 8 | validate: value => value.length > 0 || 'Project Name is required.' 9 | }, 10 | { 11 | type: 'list', 12 | name: 'projectType', 13 | message: 'What can we get you started with?', 14 | choices: ['Starter Project', 'Styles Only', 'Architecture Only'] 15 | }, 16 | { 17 | type: 'list', 18 | name: 'styleFormat', 19 | message: 'What style format would you like to use?', 20 | choices: ['SCSS', 'SASS', 'LESS'] 21 | }, 22 | { 23 | type: 'list', 24 | name: 'pipeline', 25 | message: 'What bundler or task-runner would you like to use?', 26 | choices: ['Webpack', 'Parcel', 'Gulp', 'Grunt'] 27 | } 28 | ]; 29 | -------------------------------------------------------------------------------- /lib/data/projectData.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.projectData = { 4 | styleDirectories: [ 5 | { 6 | name: "00_Abstracts", 7 | readMe: '# Abstracts\n\nThis folder is for your style tools and helpers used across the project. Your reusable code such as global variables, functions, mixins and placeholders should be put in here.\n\nThe code contained in this folder should not be code that outputs CSS directly.\n\nWith larger projects you may want to break this into subdirectories like variables, functions, and mixins. Subdirectories are supported in the Clarion CLI.' 8 | }, 9 | { 10 | name: "01_Base", 11 | readMe: '# Base\n\nThis folder holds the boilerplate code for the project. You would add your reset files and typographic rules. Keep in mind that most front-end frameworks like Bootstrap and Foundation already have their own CSS resets built into their styles, so additional resets may not be necessary.' 12 | }, 13 | { 14 | name: "02_Vendors", 15 | readMe: '# Vendors\n\nThe Themes folder contains third-party themes for your website and for things like styles for plugins. This folder has been added higher in the architecture so that if you would like to create customizations, you can do so in the subsequent folders like Components, Layouts, and Pages. This will allow you to override the theme\'s rules without modifying the theme directly and ruining the integrity of the code.' 16 | }, 17 | { 18 | name: "03_Elements", 19 | readMe: '# Elements\n\nThe Elements folder is for styling specific elements such as links, buttons, form inputs, and header tags. These are the smallest building blocks in the web and cannot be broken up into smaller parts. Browsers, by default, have their own styling in place for these elements, but tend to be inconsistent. Because of this, adding a reset to the 01_Base directory or adding one to the build pipeline is recommended.' 20 | }, 21 | { 22 | name: "04_Components", 23 | readMe: '# Components\n\nComponents are groups of elements. This can be things like a search box, navbar, or carousel. These groups of elements together have a specific purpose.' 24 | }, 25 | { 26 | name: "05_Layouts", 27 | readMe: '# Layouts\n\nThis folder contains everything that takes part in laying out the web site or application. These could be containers used for organizing elements and components within them like like a header, footer, or sidebar. This could also be used for your grid-system.' 28 | }, 29 | { 30 | name: "06_Pages", 31 | readMe: '# Pages\n\nThis folder is for any page-specific styles, if you have any. Some sites may have custom styling for the Home page or the Contact Us page.' 32 | }, 33 | { 34 | name: "07_Utilities", 35 | readMe: '# Utilities\n\nThe Utilities are used to create overrides or call out specific rules for an elements or components. For example, you could have a class for making text bold or aligning it to the right. The rule of thumb is to make it specific and make it simple.' 36 | } 37 | ], 38 | styleTypes: [ 39 | "less", 40 | "sass", 41 | "scss" 42 | ], 43 | projectDirectories: [ 44 | './%%projectName%%/dist', 45 | './%%projectName%%/src', 46 | './%%projectName%%/src/scripts', 47 | './%%projectName%%/src/scripts/components', 48 | './%%projectName%%/src/scripts/services' 49 | ], 50 | packageJson: { 51 | name: '', 52 | version: "0.1.0", 53 | description: "", 54 | main: "index.js", 55 | scripts: {}, 56 | keywords: [], 57 | author: "", 58 | license: "ISC", 59 | }, 60 | clarionConfig: { 61 | paths: { 62 | styles: './src', 63 | scripts: './src' 64 | }, 65 | format: { 66 | styles: 'scss', 67 | scripts: 'js' 68 | }, 69 | addToManifest: "true", 70 | importAbstracts: "true" 71 | }, 72 | postCssConfig: { 73 | plugins: { 74 | autoprefixer: {}, 75 | cssnano: {} 76 | } 77 | }, 78 | grunt: { 79 | configFile: 'gruntfile.js', 80 | devDependencies: [ 81 | "autoprefixer", 82 | "cssnano", 83 | "grunt", 84 | "grunt-postcss", 85 | "grunt-contrib-watch", 86 | "grunt-contrib-connect", 87 | "cross-env" 88 | ], 89 | lessDependencies: [ 90 | 'grunt-contrib-less' 91 | ], 92 | sassDependencies: [ 93 | 'grunt-sass', 94 | "node-sass" 95 | ], 96 | npmCommands: { 97 | "dev": "cross-env NODE_ENV=development grunt dev", 98 | "build": "cross-env NODE_ENV=production grunt build" 99 | }, 100 | configContents: `module.exports = function(grunt) { 101 | grunt.initConfig({ 102 | pkg: grunt.file.readJSON("package.json"), 103 | %%styleFormat%%: { 104 | dist: { 105 | files: { 106 | "dist/styles.css": "src/%%extension%%/styles.%%extension%%" 107 | } 108 | } 109 | }, 110 | watch: { 111 | css: { 112 | files: "**/*.%%extension%%", 113 | tasks: ["%%styleFormat%%", "postcss"], 114 | options: { 115 | livereload: true 116 | } 117 | } 118 | }, 119 | postcss: { 120 | options: { 121 | map: true, 122 | processors: [ 123 | require("autoprefixer")(), 124 | require("cssnano")() 125 | ] 126 | }, 127 | dist: { 128 | src: "dist/*.css" 129 | } 130 | }, 131 | connect: { 132 | server: { 133 | options: { 134 | port: 8000, 135 | hostname: "*", 136 | open: true, 137 | onCreateServer: function(server, connect, options) {} 138 | } 139 | } 140 | } 141 | }); 142 | grunt.loadNpmTasks("grunt-%%styleFormat%%"); 143 | grunt.loadNpmTasks("grunt-postcss"); 144 | grunt.loadNpmTasks("grunt-contrib-watch"); 145 | grunt.loadNpmTasks("grunt-contrib-connect"); 146 | grunt.registerTask("dev", ["%%styleFormat%%", "postcss", "connect", "watch"]); 147 | grunt.registerTask("build", ["%%styleFormat%%", "postcss"]); 148 | }; 149 | ` 150 | }, 151 | gulp: { 152 | configFile: 'gulpfile.js', 153 | devDependencies: [ 154 | "autoprefixer", 155 | "cssnano", 156 | "cross-env", 157 | "gulp", 158 | "gulp-postcss", 159 | "gulp-webserver", 160 | "gulp-sourcemaps" 161 | ], 162 | lessDependencies: [ 163 | 'gulp-less' 164 | ], 165 | sassDependencies: [ 166 | 'gulp-sass' 167 | ], 168 | npmCommands: { 169 | "dev": "cross-env NODE_ENV=development gulp dev", 170 | "build": "cross-env NODE_ENV=production gulp build" 171 | }, 172 | configContents: `'use strict'; 173 | 174 | const { src, watch, dest, parallel } = require("gulp"); 175 | const %%styleFormat%% = require("gulp-%%styleFormat%%"); 176 | const postcss = require("gulp-postcss"); 177 | const webserver = require("gulp-webserver"); 178 | const sourcemaps = require("gulp-sourcemaps"); 179 | 180 | function localServer() { 181 | return src(".").pipe( 182 | webserver({ 183 | livereload: true, 184 | directoryListing: false, 185 | open: true 186 | }) 187 | ); 188 | } 189 | 190 | function css() { 191 | return src("./src/%%extension%%/styles.%%extension%%") 192 | .pipe(sourcemaps.init()) 193 | .pipe(%%styleFormat%%().on("error", %%styleFormat%%.logError)) 194 | .pipe(postcss()) 195 | .pipe(sourcemaps.write("./")) 196 | .pipe(dest("./dist")); 197 | } 198 | 199 | exports.dev = function() { 200 | localServer(); 201 | css(); 202 | watch("./src/%%extension%%/**/*.%%extension%%", css); 203 | }; 204 | 205 | exports.build = function() { 206 | parallel(css); 207 | };` 208 | }, 209 | parcel: { 210 | configFile: null, 211 | devDependencies: [ 212 | "autoprefixer", 213 | "cssnano", 214 | "cross-env", 215 | ], 216 | lessDependencies: [], 217 | sassDependencies: [], 218 | npmCommands: { 219 | "dev": "cross-env NODE_ENV=development parcel index.html", 220 | "build": "cross-env NODE_ENV=production parcel build index.html -d ./dist" 221 | }, 222 | configContents: null 223 | }, 224 | webpack: { 225 | configFile: 'webpack.config.js', 226 | devDependencies: [ 227 | "autoprefixer", 228 | "css-loader", 229 | "style-loader", 230 | "cross-env", 231 | "mini-css-extract-plugin", 232 | "postcss-loader", 233 | "webpack", 234 | "webpack-cli", 235 | "webpack-dev-server", 236 | "cssnano", 237 | "clean-webpack-plugin", 238 | "html-webpack-plugin", 239 | ], 240 | lessDependencies: [ 241 | 'less-loader' 242 | ], 243 | sassDependencies: [ 244 | 'node-sass', 245 | 'sass-loader' 246 | ], 247 | npmCommands: { 248 | "dev": "cross-env NODE_ENV=development webpack-dev-server --progress --open --hot --mode development", 249 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules -p --mode production" 250 | }, 251 | configContents: `const path = require('path'); 252 | const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 253 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 254 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 255 | const webpack = require('webpack'); 256 | 257 | module.exports = { 258 | entry: './src/scripts/main.js', 259 | output: { 260 | filename: 'scripts.js', 261 | path: path.resolve(__dirname, 'dist') 262 | }, 263 | devServer: { 264 | contentBase: path.resolve(__dirname, '.'), 265 | inline: true, 266 | hot: true 267 | }, 268 | module: { 269 | rules: [ 270 | { 271 | test: /\.(scss|sass)$/, 272 | use: [ 273 | MiniCssExtractPlugin.loader, 274 | 'css-loader', 275 | 'sass-loader', 276 | 'postcss-loader' 277 | ] 278 | } 279 | ] 280 | }, 281 | plugins: [ 282 | new CleanWebpackPlugin(), 283 | new MiniCssExtractPlugin({ 284 | filename: "styles.css" 285 | }), 286 | new HtmlWebpackPlugin({ 287 | filename: 'index.html', 288 | template: 'index.html' 289 | }), 290 | new webpack.NamedModulesPlugin(), 291 | new webpack.HotModuleReplacementPlugin() 292 | ] 293 | }` 294 | }, 295 | indexHtml: ` 296 | 297 | 298 | 299 | 300 | Clarion 301 | 302 | 303 | 304 | 305 |
306 |

Congratulations! You did it!

307 | tobias huzzah! 308 |

This page is intentionally ugly!

309 |

Create some styles and start having fun!

310 |
    311 |
  • Modify headings by creating a headings file:
    312 | clarion add element headings 313 |
  • 314 |
  • Modify lists by creating a lists file:
    315 | clarion add element lists 316 |
  • 317 |
318 |

Here is some sweet, sweet dummy text to play with

319 |

Bacon ipsum dolor amet ball tip hamburger adipisicing chicken prosciutto non. Shoulder venison quis, flank leberkas turducken dolor tenderloin nostrud. Ham strip steak swine boudin tempor. Shoulder doner mollit brisket. Cillum strip steak picanha kevin et culpa commodo lorem pastrami.

320 |

Bacon ipsum

321 |

Elit est ut prosciutto sausage spare ribs tenderloin pork loin cupidatat brisket dolore pancetta occaecat. Elit meatball leberkas burgdoggen ham hock beef ribs ut bresaola voluptate pork belly eu culpa t-bone esse pork loin. Quis corned beef minim eu velit excepteur. Quis consequat bacon corned beef boudin chicken anim sint labore kielbasa do ipsum sed. Frankfurter laborum turkey do brisket elit exercitation adipisicing doner irure jowl leberkas. Culpa flank kevin drumstick sunt porchetta kielbasa pancetta picanha ea dolor ad. Irure tongue in pork belly alcatra sirloin mollit reprehenderit tri-tip dolore.

322 |

Tri-tip Meatball Pork Belly

323 |

Id tri-tip meatball pork belly, mollit burgdoggen leberkas cupim. Do nostrud sirloin jerky capicola ham hock deserunt, spare ribs pork belly boudin culpa salami in duis. Biltong ut aute chuck nostrud drumstick short ribs et ham hock in lorem. Prosciutto exercitation salami in enim. Laborum shoulder ribeye kielbasa exercitation bacon officia ut alcatra rump pork turkey. Excepteur ut voluptate, qui labore est pork chop pastrami hamburger cupim laborum doner shank.

324 |
325 | 326 | 327 | 328 | ` 329 | }; 330 | -------------------------------------------------------------------------------- /lib/data/styleContentData.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.styleContent = [ 4 | { 5 | format: "scss", 6 | styles: [ 7 | { 8 | file: "00_Abstracts/_variables.scss", 9 | content: `// CONTAINERS 10 | $content-width: 600px !default; 11 | 12 | // FONTS 13 | $font-size-base: 16px !default; 14 | 15 | $fonts: ( 16 | accent: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, Noto Sans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"', 17 | base: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, Noto Sans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"', 18 | monospace: 'SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' 19 | ) !default; 20 | 21 | $font-sizes: ( 22 | xxs: 0.75rem, // 12px 23 | xs: 0.875rem, // 14px 24 | sm: 1rem, // 16px 25 | md: 1.25rem, // 30px 26 | lg: 1.5rem, // 24px 27 | xl: 2rem, // 32px 28 | xxl: 3rem // 48px 29 | ) !default; 30 | 31 | $line-height-base: 1.5 !default; 32 | 33 | // SPACING 34 | 35 | $spacing-sizes: ( 36 | auto: auto, 37 | none: 0, 38 | 3xs: 0.0625rem, // 1px 39 | xxs: 0.125rem, // 2px 40 | xs: 0.25rem, // 4px 41 | sm: 0.5rem, // 8px 42 | md: 0.75rem, // 12px 43 | lg: 1rem, // 16px 44 | xl: 1.25rem, // 20px 45 | xxl: 2rem // 32px 46 | ) !default; 47 | 48 | 49 | // COLORS 50 | 51 | $theme-colors: ( 52 | primary: #24598d, 53 | secondary: #117d47, 54 | success: #669c1f, 55 | info: #509BCE, 56 | warning: #B88A00, 57 | danger: #c70000, 58 | light: #ababab, 59 | dark: #545454 60 | ) !default; 61 | 62 | $color-variation: 8% !default; 63 | 64 | 65 | // MEDIA QUERIES 66 | 67 | $media-queries: ( 68 | xl: 90rem, // 1440px 69 | lg: 75rem, // 1200px 70 | md: 62rem, // 992px 71 | sm: 48rem, // 768px 72 | xs: 30rem, // 480px 73 | xxs: 20rem, // 320px 74 | light-mode: light, 75 | dark-mode: dark, 76 | print: print 77 | ) !default; 78 | 79 | 80 | // Z-INDEX 81 | 82 | $z-indexes: ( 83 | sub: -1, 84 | none: 0, 85 | xxs: 1, 86 | xs: 10, 87 | sm: 50, 88 | md: 100, 89 | lg: 200, 90 | xl: 500, 91 | trump: 99999 92 | ) !default; 93 | 94 | 95 | // BORDERS 96 | 97 | $border: ( 98 | style: solid, 99 | size: 3xs, // from $spacing-sizes 100 | color: light lighter 101 | ) !default; 102 | 103 | $border-radiuses: ( 104 | none: 0, 105 | sm: 0.25rem, // 4px 106 | md: 0.5rem, // 8px 107 | lg: 0.75rem, // 12px 108 | pill: 10rem, 109 | circle: 50% 110 | ) !default; 111 | 112 | 113 | // SHADOWS 114 | 115 | $shadows: ( 116 | none: none, 117 | inner: "inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)", 118 | outline: "0 0 0 3px rgba(0, 0, 0, 0.5)", 119 | xs: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)", 120 | sm: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", 121 | md: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)", 122 | lg: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", 123 | xl: "0 25px 50px -12px rgba(0, 0, 0, 0.25)" 124 | ) !default; 125 | 126 | // TRANSITIONS 127 | 128 | $transitions: ( 129 | slow: .5s, 130 | med: .3s, 131 | fast: .1s 132 | ) !default; 133 | ` 134 | }, 135 | { 136 | file: '00_Abstracts/functions/_borders.scss', 137 | content: `@import '../variables'; 138 | @import 'colors'; 139 | 140 | @function default-border($setting) { 141 | @if map-has-key($border, $setting) { 142 | @return map-get($border, $setting); 143 | } 144 | @else { 145 | @error "Invalid default border value: '#{$setting}'."; 146 | } 147 | } 148 | 149 | @function border($style, $size, $color, $variant: base, $opacity: 1, $important: false) { 150 | @if ($important == true) { 151 | @return $style spacing($size) split-color-list($color, $variant, $opacity) !important; 152 | } @else { 153 | @return $style spacing($size) split-color-list($color, $variant, $opacity); 154 | } 155 | } 156 | ` 157 | }, 158 | { 159 | file: "00_Abstracts/functions/_colors.scss", 160 | content: `@import '../variables'; 161 | @import 'color-contrast'; 162 | 163 | @function build-pallet() { 164 | $result: (); 165 | 166 | @each $key, $value in $theme-colors { 167 | $group: ($key: ('base': $value, 168 | 'light': lighten($value, $color-variation), 169 | 'lighter': lighten($value, $color-variation * 2), 170 | 'lightest': lighten($value, $color-variation * 3), 171 | 'dark': darken($value, $color-variation), 172 | 'darker': darken($value, $color-variation * 2), 173 | 'darkest': darken($value, $color-variation * 3))); 174 | 175 | $result: map-merge($result, $group); 176 | } 177 | 178 | @return $result; 179 | } 180 | 181 | $color-pallette: build-pallet(); 182 | 183 | /* 184 | GET COLOR 185 | 186 | usage: 187 | // for the base color 188 | color(primary); 189 | 190 | // for variations of a color 191 | color(primary, lighter); 192 | color(primary, light); 193 | color(primary, dark); 194 | color(primary, darker); 195 | */ 196 | @function color($name: 'primary', $variant: 'base', $opacity: 1) { 197 | $color: null; 198 | 199 | $name: $name + unquote(""); 200 | 201 | @if (type-of($variant) == 'number') { 202 | $opacity: $variant; 203 | $variant: 'base'; 204 | } 205 | 206 | // Get the color name 207 | $color-name: map-get($color-pallette, $name); 208 | 209 | // Get the color variant 210 | @if $color-name or $name == 'black' or $name == 'white' { 211 | @if $name == 'black' { 212 | $color: black; 213 | } @else if $name == 'white' { 214 | $color: white; 215 | } @else { 216 | $color: map-get($color-name, $variant); 217 | } 218 | 219 | @if $color { 220 | @return rgba($color, $opacity); 221 | } @else { 222 | @error "Invalid color variation: '#{$name}', '#{$variant}'." 223 | } 224 | } @else { 225 | @error "Invalid color name: '#{$name}'." 226 | } 227 | 228 | @return $color; 229 | } 230 | 231 | /* 232 | GET CONTRASTING COLOR FOR TEXT ACCESSIBILITY (WCAG 2.0 AA+) 233 | 234 | usage: 235 | // text for the base color 236 | text-color(primary); 237 | 238 | // for variations of a color 239 | text-color(primary, lighter); 240 | text-color(primary, light); 241 | text-color(primary, dark); 242 | text-color(primary, darker); 243 | */ 244 | @function text-color($name: 'primary', $variant: 'base', $opacity: 1) { 245 | $color: color($name, $variant, $opacity); 246 | 247 | @return get-contrast-color($color); 248 | } 249 | 250 | // Used for splitting up color blocks - ex: "primary dark 0.75" 251 | @function split-color-list($color, $variant: base, $opacity: 1) { 252 | @if (length($color) > 1) { 253 | $g-color: nth($color, 1); 254 | $g-variant: nth($color, 2); 255 | $g-opacity: 1; 256 | 257 | @if (length($color) > 2) { 258 | $g-opacity: nth($color, 3); 259 | } 260 | 261 | @return color($g-color, $g-variant, $g-opacity); 262 | } @else { 263 | @return color($color, $variant, $opacity); 264 | } 265 | } 266 | ` 267 | }, 268 | { 269 | file: "00_Abstracts/functions/_spacing.scss", 270 | content: `@import '../variables'; 271 | @import 'strings'; 272 | 273 | @function spacing($name, $important: false) { 274 | $prefix: ""; 275 | 276 | // This enables negative values to be used. 277 | @if (str-contains($name, "-")) { 278 | $prefix: "-"; 279 | $name: str-replace($name, "-", ""); 280 | } 281 | 282 | @if map-has-key($spacing-sizes, $name) { 283 | @if ($important == true) { 284 | @return unquote($prefix + map-get($spacing-sizes, $name)) !important; 285 | } @else { 286 | @return unquote($prefix + map-get($spacing-sizes, $name)); 287 | } 288 | } 289 | @else { 290 | @error "Invalid spacing size: '#{$name}'."; 291 | } 292 | } 293 | ` 294 | }, 295 | { 296 | file: "00_Abstracts/functions/_color-contrast.scss", 297 | content: `@import 'math'; 298 | 299 | /* 300 | Adopted with gratitude from w3.org: 301 | https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests 302 | */ 303 | 304 | @function get-relative-luminance($color) { 305 | $color: $color / 255; 306 | 307 | @if ($color < 0.03928) { 308 | @return $color / 12.92 309 | } 310 | 311 | @return pow(($color + 0.055) / 1.055, 2.4); 312 | } 313 | 314 | @function get-luminance($color) { 315 | $red: get-relative-luminance(red($color)); 316 | $green: get-relative-luminance(green($color)); 317 | $blue: get-relative-luminance(blue($color)); 318 | 319 | @return (.2126 * $red) + (.7152 * $green) + (.0722 * $blue); 320 | } 321 | 322 | @function get-contrast-ratio($backgroundColor, $foregroundColor) { 323 | $backgroundLuminance: get-luminance($backgroundColor) + .05; 324 | $foregroundLuminance: get-luminance($foregroundColor) + .05; 325 | 326 | @return max($backgroundLuminance, $foregroundLuminance) / min($backgroundLuminance, $foregroundLuminance); 327 | } 328 | 329 | @function get-contrast-color($color) { 330 | $lightContrast: get-contrast-ratio($color, white); 331 | $darkContrast: get-contrast-ratio($color, black); 332 | 333 | @if ($lightContrast > 4.5 or $darkContrast < 4.5) { 334 | @return white; 335 | } 336 | 337 | @return black; 338 | } 339 | ` 340 | }, 341 | { 342 | file: "00_Abstracts/functions/_math.scss", 343 | content: `@function pow($base, $exponent, $precision: 12) { 344 | @if (floor($exponent) !=$exponent) { 345 | $precision2: pow(10, $precision); 346 | $exponent: round($exponent * $precision2); 347 | $denominator: greatest-common-divisor($exponent, $precision2); 348 | @return nthRoot(pow($base, $exponent / $denominator), $precision2 / $denominator, $precision); 349 | } 350 | 351 | $value: $base; 352 | 353 | @if $exponent>1 { 354 | @for $i from 2 through $exponent { 355 | $value: $value * $base; 356 | } 357 | } 358 | 359 | @else if $exponent < 1 { 360 | @for $i from 0 through -$exponent { 361 | $value: $value / $base; 362 | } 363 | } 364 | 365 | @return $value; 366 | } 367 | 368 | @function greatest-common-divisor($a, $b) { 369 | @if ($b !=0) { 370 | @return greatest-common-divisor($b, $a % $b); 371 | } 372 | 373 | @else { 374 | @return abs($a); 375 | } 376 | } 377 | 378 | @function nthRoot($num, $n: 2, $precision: 12) { 379 | $x: 1; 380 | 381 | @for $i from 0 through $precision { 382 | $x: 1 / $n * (($n - 1) * $x + ($num / pow($x, $n - 1))); 383 | } 384 | 385 | @return $x; 386 | } 387 | ` 388 | }, 389 | { 390 | file: "00_Abstracts/functions/_strings.scss", 391 | content: `@function str-replace($string, $search, $replace: '') { 392 | $index: str-index($string, $search); 393 | 394 | @if $index { 395 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); 396 | } 397 | 398 | @return $string; 399 | } 400 | 401 | @function str-ends-with($string, $search) { 402 | @return str-slice(quote($string), (str-length($string) - str-length($search) + 1)) == $search; 403 | } 404 | 405 | @function str-contains($string, $search) { 406 | @return str-index(quote($string), $search) != null; 407 | } 408 | ` 409 | }, 410 | { 411 | file: "00_Abstracts/functions/_z-index.scss", 412 | content: `@import '../variables'; 413 | 414 | @function z($name) { 415 | @if map-has-key($z-indexes, $name) { 416 | @return map-get($z-indexes, $name); 417 | } 418 | @else { 419 | @error "Invalid 'z' value: '#{$name}'."; 420 | } 421 | } 422 | ` 423 | }, 424 | { 425 | file: '00_Abstracts/mixins/_borders.scss', 426 | content: `@import '../functions/borders'; 427 | 428 | @mixin border($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) { 429 | border: border($style, $size, $color, $variant, $opacity, $important); 430 | } 431 | 432 | @mixin border-t($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) { 433 | border-top: border($style, $size, $color, $variant, $opacity, $important); 434 | } 435 | 436 | @mixin border-b($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) { 437 | border-bottom: border($style, $size, $color, $variant, $opacity, $important); 438 | } 439 | 440 | @mixin border-l($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) { 441 | border-left: border($style, $size, $color, $variant, $opacity, $important); 442 | } 443 | 444 | @mixin border-r($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) { 445 | border-right: border($style, $size, $color, $variant, $opacity, $important); 446 | } 447 | 448 | @mixin border-x($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) { 449 | @include border-l($style, $size, $color, $variant, $opacity, $important); 450 | @include border-r($style, $size, $color, $variant, $opacity, $important); 451 | } 452 | 453 | @mixin border-y($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) { 454 | @include border-t($style, $size, $color, $variant, $opacity, $important); 455 | @include border-b($style, $size, $color, $variant, $opacity, $important); 456 | } 457 | ` 458 | }, 459 | { 460 | file: "00_Abstracts/mixins/_border-radius.scss", 461 | content: `@import '../variables'; 462 | 463 | @mixin radius($size, $important: false) { 464 | @include radius-t($size, $important); 465 | @include radius-b($size, $important); 466 | } 467 | 468 | @mixin radius-t($size, $important: false) { 469 | @include radius-tl($size, $important); 470 | @include radius-tr($size, $important); 471 | } 472 | 473 | @mixin radius-b($size, $important: false) { 474 | @include radius-bl($size, $important); 475 | @include radius-br($size, $important); 476 | } 477 | 478 | @mixin radius-l($size, $important: false) { 479 | @include radius-tl($size, $important); 480 | @include radius-bl($size, $important); 481 | } 482 | 483 | @mixin radius-r($size, $important: false) { 484 | @include radius-tr($size, $important); 485 | @include radius-br($size, $important); 486 | } 487 | 488 | @mixin radius-tl($size, $important: false) { 489 | @if map-has-key($border-radiuses, $size) { 490 | @if ($important == true) { 491 | border-top-left-radius: map-get($border-radiuses, $size) !important; 492 | } @else { 493 | border-top-left-radius: map-get($border-radiuses, $size); 494 | } 495 | } 496 | @else { 497 | @error "Invalid border-radius size: '#{$size}'."; 498 | } 499 | } 500 | 501 | @mixin radius-tr($size, $important: false) { 502 | @if map-has-key($border-radiuses, $size) { 503 | @if ($important == true) { 504 | border-top-right-radius: map-get($border-radiuses, $size) !important; 505 | } @else { 506 | border-top-right-radius: map-get($border-radiuses, $size); 507 | } 508 | } 509 | @else { 510 | @error "Invalid border-radius size: '#{$size}'."; 511 | } 512 | } 513 | 514 | @mixin radius-bl($size, $important: false) { 515 | @if map-has-key($border-radiuses, $size) { 516 | @if ($important == true) { 517 | border-bottom-left-radius: map-get($border-radiuses, $size) !important; 518 | } @else { 519 | border-bottom-left-radius: map-get($border-radiuses, $size); 520 | } 521 | } 522 | @else { 523 | @error "Invalid border-radius size: '#{$size}'."; 524 | } 525 | } 526 | 527 | @mixin radius-br($size, $important: false) { 528 | @if map-has-key($border-radiuses, $size) { 529 | @if ($important == true) { 530 | border-bottom-right-radius: map-get($border-radiuses, $size) !important; 531 | } @else { 532 | border-bottom-right-radius: map-get($border-radiuses, $size); 533 | } 534 | } 535 | @else { 536 | @error "Invalid border-radius size: '#{$size}'."; 537 | } 538 | } 539 | ` 540 | }, 541 | { 542 | file: "00_Abstracts/mixins/_media-queries.scss", 543 | content: `@import '../variables'; 544 | 545 | /* 546 | MEDIA QUERY 547 | 548 | Usage: @include mq(sm) {} 549 | */ 550 | @mixin mq($mq) { 551 | @if map-has-key($media-queries, $mq) { 552 | $mq-value: map-get($media-queries, $mq); 553 | 554 | @if ($mq-value == light or $mq-value == dark) { 555 | @media screen and (prefers-color-scheme: $mq-value) { 556 | @content; 557 | } 558 | } @else if ($mq-value == print) { 559 | @media print { 560 | @content; 561 | } 562 | } @else { 563 | @media screen and (max-width: ($mq-value - 1)) { 564 | @content; 565 | } 566 | } 567 | } @else { 568 | @error "Invalid media query: '#{$mq}'."; 569 | } 570 | } 571 | ` 572 | }, 573 | { 574 | file: "00_Abstracts/mixins/_display.scss", 575 | content: `@mixin full-width($important: false) { 576 | @if ($important == true) { 577 | position: relative !important; 578 | left: 50% !important; 579 | right: 50% !important; 580 | width: 100vw !important; 581 | margin-left: -50vw !important; 582 | margin-right: -50vw !important; 583 | max-width: 100vw !important; 584 | } @else { 585 | position: relative; 586 | left: 50%; 587 | right: 50%; 588 | width: 100vw; 589 | margin-left: -50vw; 590 | margin-right: -50vw; 591 | max-width: 100vw; 592 | } 593 | } 594 | 595 | @mixin full-height($important: false) { 596 | @if ($important == true) { 597 | position: fixed !important; 598 | top: 50% !important; 599 | bottom: 50% !important; 600 | height: 100vh !important; 601 | margin-top: -50vh !important; 602 | margin-bottom: -50vh !important; 603 | max-height: 100vh !important; 604 | } @else { 605 | position: fixed; 606 | top: 50%; 607 | bottom: 50%; 608 | height: 100vh; 609 | margin-top: -50vh; 610 | margin-bottom: -50vh; 611 | max-height: 100vh; 612 | } 613 | } 614 | 615 | @mixin full-screen($important: false) { 616 | @include full-width($important); 617 | @include full-height($important); 618 | } 619 | 620 | @mixin screen-reader-only { 621 | position: absolute !important; 622 | width: 1px !important; 623 | height: 1px !important; 624 | padding: 0 !important; 625 | margin: -1px !important; 626 | overflow: hidden !important; 627 | clip: rect(0, 0, 0, 0) !important; 628 | white-space: nowrap !important; 629 | border: 0 !important; 630 | } 631 | ` 632 | }, 633 | { 634 | file: "00_Abstracts/mixins/_font-sizes.scss", 635 | content: `@import '../variables'; 636 | 637 | @mixin font-size($size, $important: false) { 638 | @if map-has-key($font-sizes, $size) { 639 | @if ($important == true) { 640 | font-size: map-get($font-sizes, $size) !important; 641 | } @else { 642 | font-size: map-get($font-sizes, $size); 643 | } 644 | } 645 | @else { 646 | @error "Invalid font size: '#{$size}'."; 647 | } 648 | } 649 | ` 650 | }, 651 | { 652 | file: "00_Abstracts/mixins/_font.scss", 653 | content: `@import '../variables'; 654 | 655 | @mixin font($font: base, $important: false) { 656 | @if map-has-key($fonts, $font) { 657 | @if ($important == true) { 658 | font-family: unquote(map-get($fonts, $font)) !important; 659 | } @else { 660 | font-family: unquote(map-get($fonts, $font)); 661 | } 662 | } 663 | @else { 664 | @error "Invalid shadow size: '#{$font}'."; 665 | } 666 | } 667 | ` 668 | }, 669 | { 670 | file: "00_Abstracts/mixins/_colors.scss", 671 | content: `@import '../functions/colors'; 672 | 673 | @mixin bg-color($name: 'primary', $variant: 'base', $opacity: 1, $important: false) { 674 | @if ($important) { 675 | color: text-color($name, $variant, $opacity) !important; 676 | background-color: color($name, $variant, $opacity) !important; 677 | } 678 | 679 | @else { 680 | color: text-color($name, $variant, $opacity); 681 | background-color: color($name, $variant, $opacity); 682 | } 683 | } 684 | ` 685 | }, 686 | { 687 | file: "00_Abstracts/mixins/_hover.scss", 688 | content: `@mixin hover { 689 | &:hover { 690 | @content; 691 | } 692 | } 693 | 694 | @mixin hover-focus { 695 | &:hover, 696 | &:focus { 697 | @content; 698 | } 699 | } 700 | 701 | @mixin hover-focus-active { 702 | &:hover, 703 | &:focus, 704 | &:active { 705 | @content; 706 | } 707 | } 708 | ` 709 | }, 710 | { 711 | file: "00_Abstracts/mixins/_spacing.scss", 712 | content: `@import '../variables'; 713 | @import '../functions/spacing'; 714 | 715 | @mixin m($top, $right: $top, $bottom: $top, $left: $right, $important: false) { 716 | @include mt($top, $important); 717 | @include mb($bottom, $important); 718 | @include ml($left, $important); 719 | @include mr($right, $important); 720 | } 721 | 722 | @mixin mx($left, $right: $left, $important: false) { 723 | @include ml($left, $important); 724 | @include mr($right, $important); 725 | } 726 | 727 | @mixin my($top, $bottom: $top, $important: false) { 728 | @include mt($top, $important); 729 | @include mb($bottom, $important); 730 | } 731 | 732 | @mixin ml($size, $important: false) { 733 | margin-left: spacing($size); 734 | } 735 | 736 | @mixin mt($size, $important: false) { 737 | margin-top: spacing($size); 738 | } 739 | 740 | @mixin mr($size, $important: false) { 741 | margin-right: spacing($size); 742 | } 743 | 744 | @mixin mb($size, $important: false) { 745 | margin-bottom: spacing($size); 746 | } 747 | 748 | @mixin p($top, $right: $top, $bottom: $top, $left: $right, $important: false) { 749 | @include pt($top, $important); 750 | @include pb($bottom, $important); 751 | @include pl($left, $important); 752 | @include pr($right, $important); 753 | } 754 | 755 | @mixin px($left, $right: $left, $important: false) { 756 | @include pl($left, $important); 757 | @include pr($right, $important); 758 | } 759 | 760 | @mixin py($top, $bottom: $top, $important: false) { 761 | @include pt($top, $important); 762 | @include pb($bottom, $important); 763 | } 764 | 765 | @mixin pl($size, $important: false) { 766 | padding-left: spacing($size, $important); 767 | } 768 | 769 | @mixin pt($size, $important: false) { 770 | padding-top: spacing($size, $important); 771 | } 772 | 773 | @mixin pr($size, $important: false) { 774 | padding-right: spacing($size, $important); 775 | } 776 | 777 | @mixin pb($size, $important: false) { 778 | padding-bottom: spacing($size, $important); 779 | } 780 | ` 781 | }, 782 | { 783 | file: "00_Abstracts/mixins/_shadows.scss", 784 | content: `@import '../variables'; 785 | 786 | @mixin shadow($size: sm, $important: false) { 787 | @if map-has-key($shadows, $size) { 788 | @if ($important == true) { 789 | box-shadow: unquote(map-get($shadows, $size)) !important; 790 | } @else { 791 | box-shadow: unquote(map-get($shadows, $size)); 792 | } 793 | } 794 | @else { 795 | @error "Invalid shadow size: '#{$size}'."; 796 | } 797 | } 798 | ` 799 | }, 800 | { 801 | file: "00_Abstracts/mixins/_transitions.scss", 802 | content: `@import '../variables'; 803 | 804 | @mixin transition-ease-in-out($speed: fast, $important: false) { 805 | @if map-has-key($transitions, $speed) { 806 | @if ($important == true) { 807 | transition: all map-get($transitions, $speed) ease-in-out !important; 808 | } @else { 809 | transition: all map-get($transitions, $speed) ease-in-out; 810 | } 811 | } 812 | @else { 813 | @error "Invalid transition speed: '#{$speed}'."; 814 | } 815 | } 816 | ` 817 | }, 818 | { 819 | file: "00_Abstracts/mixins/_z-index.scss", 820 | content: `@import '../variables'; 821 | 822 | @mixin z-index($size, $important: false) { 823 | @if map-has-key($z-indexes, $size) { 824 | @if ($important == true) { 825 | z-index: map-get($z-indexes, $size) !important; 826 | } 827 | @else { 828 | z-index: map-get($z-indexes, $size); 829 | } 830 | } 831 | @else { 832 | @error "Invalid z-index: '#{$size}'."; 833 | } 834 | } 835 | ` 836 | }, 837 | { 838 | file: "00_Abstracts/index.scss", 839 | content: `@import 'variables'; 840 | @import 'functions/colors'; 841 | @import 'functions/math'; 842 | @import 'functions/strings'; 843 | @import 'functions/z-index'; 844 | @import 'mixins/borders'; 845 | @import 'mixins/border-radius'; 846 | @import 'mixins/media-queries'; 847 | @import 'mixins/colors'; 848 | @import 'mixins/display'; 849 | @import 'mixins/font'; 850 | @import 'mixins/font-sizes'; 851 | @import 'mixins/hover'; 852 | @import 'mixins/shadows'; 853 | @import 'mixins/spacing'; 854 | @import 'mixins/transitions'; 855 | @import 'mixins/z-index'; 856 | ` 857 | }, 858 | { 859 | file: "01_Base/_base.scss", 860 | content: `@import '../00_Abstracts/index'; 861 | 862 | html, 863 | body { 864 | font-size: $font-size-base; 865 | line-height: $line-height-base; 866 | @include font(base); 867 | 868 | main { 869 | max-width: $content-width; 870 | @include mx(auto); 871 | } 872 | 873 | code, 874 | pre { 875 | @include font(monospace); 876 | } 877 | } 878 | ` 879 | }, 880 | { 881 | file: "01_Base/index.scss", 882 | content: `@import 'base';` 883 | } 884 | ] 885 | }, 886 | { 887 | format: "sass", 888 | styles: [ 889 | { 890 | file: "00_Abstracts/_variables.sass", 891 | content: `// CONTAINERS 892 | 893 | $content-width: 600px !default 894 | 895 | // FONTS 896 | 897 | $font-size-base: 16px !default 898 | 899 | $fonts: (accent: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, Noto Sans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"', base: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, Noto Sans, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"', monospace: 'SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace') !default 900 | 901 | $font-sizes: ( xxs: 0.75rem, xs: 0.875rem, sm: 1rem, md: 1.25rem, lg: 1.5rem, xl: 2rem, xxl: 3rem) !default 902 | 903 | $line-height-base: 1.5 !default 904 | 905 | // SPACING 906 | 907 | $spacing-sizes: ( auto: auto, none: 0, 3xs: 0.0625rem, xxs: 0.125rem, xs: 0.25rem, sm: 0.5rem, md: 0.75rem, lg: 1rem, xl: 1.25rem, xxl: 2rem) !default 908 | 909 | // COLORS 910 | 911 | $theme-colors: (primary: #24598d, secondary: #117d47, success: #669c1f, info: #509BCE, warning: #B88A00, danger: #c70000, light: #ababab, dark: #545454) !default 912 | 913 | $color-variation: 8% !default 914 | 915 | 916 | // MEDIA-QUERIES 917 | 918 | $media-queries: (xl: 90rem, lg: 75rem, md: 62rem, sm: 48rem, xs: 30rem, xxs: 20rem, light-mode: light, dark-mode: dark, print: print) !default 919 | 920 | // Z-INDEX 921 | 922 | $z-indexes: (sub: -1, none: 0, xxs: 1, xs: 10, sm: 50, md: 100, lg: 200, xl: 500, trump: 99999) !default; 923 | 924 | // BORDERS 925 | 926 | $border: (style: solid, size: 3xs, color: light lighter) !default 927 | 928 | $border-radiuses: (none: 0, sm: 0.25rem, md: 0.5rem, lg: 0.75rem, pill: 10rem, circle: 50%) !default 929 | 930 | // SHADOWS 931 | 932 | $shadows: (none: none, inner: "inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)", outline: "0 0 0 3px rgba(0, 0, 0, 0.5)", xs: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)", sm: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)", md: "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)", lg: "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)", xl: "0 25px 50px -12px rgba(0, 0, 0, 0.25)") !default 933 | 934 | // TRANSITIONS 935 | 936 | $transitions: (slow: .5s, med: .3s, fast: .1s) !default 937 | ` 938 | }, 939 | { 940 | file: '00_Abstracts/functions/_borders.sass', 941 | content: `@import '../variables' 942 | @import 'colors' 943 | 944 | @function default-border($setting) 945 | @if map-has-key($border, $setting) 946 | @return map-get($border, $setting) 947 | @else 948 | @error "Invalid default border value: '#{$setting}'." 949 | 950 | @function border($style, $size, $color, $variant: base, $opacity: 1, $important: false) 951 | @if ($important == true) 952 | @return $style spacing($size) split-color-list($color, $variant, $opacity) !important 953 | @else 954 | @return $style spacing($size) split-color-list($color, $variant, $opacity) 955 | ` 956 | }, 957 | { 958 | file: "00_Abstracts/functions/_colors.sass", 959 | content: `@import '../variables' 960 | 961 | @function build-pallet() 962 | $result: () 963 | 964 | @each $key, $value in $theme-colors 965 | $group: ($key: ('base': $value, 'light': lighten($value, $color-variation), 'lighter': lighten($value, $color-variation * 2), 'dark': darken($value, $color-variation), 'darker': darken($value, $color-variation * 2))) 966 | $result: map-merge($result, $group) 967 | 968 | @return $result 969 | 970 | 971 | $color-pallette: build-pallet() 972 | 973 | // GET COLOR 974 | // 975 | // usage: 976 | // // for the base color 977 | // color(primary) 978 | // 979 | // // for variations of a color 980 | // color(primary, lighter) 981 | // color(primary, light) 982 | // color(primary, dark) 983 | // color(primary, darker) 984 | // 985 | // // opacity can also be adjusted using a third (optional) parameter 986 | // color(primary, lighter, .25) 987 | @function color($name: 'primary', $variant: 'base', $opacity: 1) 988 | $color: null 989 | 990 | $name: $name + unquote("") 991 | 992 | @if (type-of($variant) == 'number') 993 | $opacity: $variant 994 | $variant: 'base' 995 | 996 | // Get the color name 997 | $color-name: map-get($color-pallette, $name) 998 | 999 | // Get the color variant 1000 | @if $color-name or $name == 'black' or $name == 'white' 1001 | @if $name == 'black' 1002 | $color: black 1003 | @else if $name == 'white' 1004 | $color: white 1005 | @else 1006 | $color: map-get($color-name, $variant) 1007 | 1008 | @if $color 1009 | @return rgba($color, $opacity) 1010 | @else 1011 | @error "Invalid color variation: '#{$name}', '#{$variant}'." 1012 | @else 1013 | @error "Invalid color name: '#{$name}'." 1014 | 1015 | @return $color 1016 | 1017 | 1018 | // GET CONTRASTING COLOR FOR TEXT ACCESSIBILITY (WCAG 2.0 AA+) 1019 | // 1020 | // usage: 1021 | // // text for the base color 1022 | // text-color(primary) 1023 | // 1024 | // // for variations of a color 1025 | // text-color(primary, lighter) 1026 | // text-color(primary, light) 1027 | // text-color(primary, dark) 1028 | // text-color(primary, darker) 1029 | @function text-color($name: 'primary', $variant: 'base', $opacity: 1) 1030 | $color: color($name, $variant, $opacity) 1031 | 1032 | @return get-contrast-color($color) 1033 | ` 1034 | }, 1035 | { 1036 | file: "00_Abstracts/functions/_color-contrast.sass", 1037 | content: `@import 'math' 1038 | 1039 | /* 1040 | Adopted with gratitude from w3.org: 1041 | https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests 1042 | */ 1043 | 1044 | @function get-relative-luminance($color) 1045 | $color: $color / 255 1046 | 1047 | @if ($color < 0.03928) 1048 | @return $color / 12.92 1049 | 1050 | @return pow(($color + 0.055) / 1.055, 2.4) 1051 | 1052 | @function get-luminance($color) 1053 | $red: get-relative-luminance(red($color)) 1054 | $green: get-relative-luminance(green($color)) 1055 | $blue: get-relative-luminance(blue($color)) 1056 | 1057 | @return (.2126 * $red) + (.7152 * $green) + (.0722 * $blue) 1058 | 1059 | @function get-contrast-ratio($backgroundColor, $foregroundColor) 1060 | $backgroundLuminance: get-luminance($backgroundColor) + .05 1061 | $foregroundLuminance: get-luminance($foregroundColor) + .05 1062 | 1063 | @return max($backgroundLuminance, $foregroundLuminance) / min($backgroundLuminance, $foregroundLuminance) 1064 | 1065 | @function get-contrast-color($color) 1066 | $lightContrast: get-contrast-ratio($color, white) 1067 | $darkContrast: get-contrast-ratio($color, black) 1068 | 1069 | @if ($lightContrast > 4.5 or $darkContrast < 4.5) 1070 | @return white 1071 | 1072 | @return black 1073 | 1074 | // Used for splitting up color blocks - ex: "primary dark 0.75" 1075 | @function split-color-list($color, $variant: base, $opacity: 1) 1076 | @if (length($color) > 1) 1077 | $g-color: nth($color, 1) 1078 | $g-variant: nth($color, 2) 1079 | $g-opacity: 1 1080 | 1081 | @if (length($color) > 2) 1082 | $g-opacity: nth($color, 3) 1083 | 1084 | @return color($g-color, $g-variant, $g-opacity) 1085 | @else 1086 | @return color($color, $variant, $opacity); 1087 | ` 1088 | }, 1089 | { 1090 | file: "00_Abstracts/functions/_math.sass", 1091 | content: `@function pow($base, $exponent, $precision: 12) 1092 | @if (floor($exponent) !=$exponent) 1093 | $precision2: pow(10, $precision) 1094 | $exponent: round($exponent * $precision2) 1095 | $denominator: greatest-common-divisor($exponent, $precision2) 1096 | @return nthRoot(pow($base, $exponent / $denominator), $precision2 / $denominator, $precision) 1097 | 1098 | $value: $base 1099 | 1100 | @if $exponent>1 1101 | @for $i from 2 through $exponent 1102 | $value: $value * $base; 1103 | @else if $exponent < 1 1104 | @for $i from 0 through -$exponent 1105 | $value: $value / $base; 1106 | 1107 | @return $value 1108 | 1109 | @function greatest-common-divisor($a, $b) 1110 | @if ($b !=0) 1111 | @return greatest-common-divisor($b, $a % $b) 1112 | @else 1113 | @return abs($a) 1114 | 1115 | @function nthRoot($num, $n: 2, $precision: 12) 1116 | $x: 1 1117 | 1118 | @for $i from 0 through $precision 1119 | $x: 1 / $n * (($n - 1) * $x + ($num / pow($x, $n - 1))) 1120 | 1121 | @return $x 1122 | ` 1123 | }, 1124 | { 1125 | file: "00_Abstracts/functions/_strings.sass", 1126 | content: `@function str-replace($string, $search, $replace: '') 1127 | $index: str-index($string, $search) 1128 | 1129 | @if $index 1130 | @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace) 1131 | 1132 | @return $string; 1133 | 1134 | @function str-ends-with($string, $search) 1135 | @return str-slice(quote($string), (str-length($string) - str-length($search) + 1)) == $search 1136 | 1137 | @function str-contains($string, $search) 1138 | @return str-index(quote($string), $search) != null 1139 | ` 1140 | }, 1141 | { 1142 | file: "00_Abstracts/functions/_spacing.sass", 1143 | content: `@import '../variables' 1144 | @import 'strings' 1145 | 1146 | @function spacing($name, $important: flase) 1147 | $prefix: "" 1148 | 1149 | // This enables negative values to be used. 1150 | @if (str-contains($name, "-")) 1151 | $prefix: "-"; 1152 | $name: str-replace($name, "-", "") 1153 | 1154 | @if map-has-key($spacing-sizes, $name) 1155 | @if ($important == true) 1156 | @return unquote($prefix + map-get($spacing-sizes, $name)) !important 1157 | @else 1158 | @return unquote($prefix + map-get($spacing-sizes, $name)) 1159 | @else 1160 | @error "Invalid spacing size: '#{$name}'." 1161 | ` 1162 | }, 1163 | { 1164 | file: "00_Abstracts/functions/_z-index.sass", 1165 | content: `@import '../variables' 1166 | 1167 | @function z($name) 1168 | @if map-has-key($z-indexes, $name) 1169 | @return map-get($z-indexes, $name) 1170 | @else 1171 | @error "Invalid 'z' value: '#{$name}'." 1172 | ` 1173 | }, 1174 | { 1175 | file: "00_Abstracts/mixins/_border-radius.sass", 1176 | content: `@import '../variables'; 1177 | 1178 | @mixin radius($size, $important: false) 1179 | @include radius-t($size, $important) 1180 | @include radius-b($size, $important) 1181 | 1182 | @mixin radius-t($size, $important: false) 1183 | @include radius-tl($size, $important) 1184 | @include radius-tr($size, $important) 1185 | 1186 | @mixin radius-b($size, $important: false) 1187 | @include radius-bl($size, $important) 1188 | @include radius-br($size, $important) 1189 | 1190 | @mixin radius-l($size, $important: false) 1191 | @include radius-tl($size, $important) 1192 | @include radius-bl($size, $important) 1193 | 1194 | @mixin radius-r($size, $important: false) 1195 | @include radius-tr($size, $important) 1196 | @include radius-br($size, $important) 1197 | 1198 | @mixin radius-tl($size, $important: false) 1199 | @if map-has-key($border-radiuses, $size) 1200 | @if ($important == true) 1201 | border-top-left-radius: map-get($border-radiuses, $size) !important 1202 | @else 1203 | border-top-left-radius: map-get($border-radiuses, $size) 1204 | @else 1205 | @error "Invalid border-radius size: '#{$size}'." 1206 | 1207 | @mixin radius-tr($size, $important: false) 1208 | @if map-has-key($border-radiuses, $size) 1209 | @if ($important == true) 1210 | border-top-right-radius: map-get($border-radiuses, $size) !important 1211 | @else 1212 | border-top-right-radius: map-get($border-radiuses, $size) 1213 | @else 1214 | @error "Invalid border-radius size: '#{$size}'." 1215 | 1216 | @mixin radius-bl($size, $important: false) 1217 | @if map-has-key($border-radiuses, $size) 1218 | @if ($important == true) 1219 | border-bottom-left-radius: map-get($border-radiuses, $size) !important 1220 | @else 1221 | border-bottom-left-radius: map-get($border-radiuses, $size) 1222 | @else 1223 | @error "Invalid border-radius size: '#{$size}'." 1224 | 1225 | @mixin radius-br($size, $important: false) 1226 | @if map-has-key($border-radiuses, $size) 1227 | @if ($important == true) 1228 | border-bottom-right-radius: map-get($border-radiuses, $size) !important 1229 | @else 1230 | border-bottom-right-radius: map-get($border-radiuses, $size) 1231 | @else 1232 | @error "Invalid border-radius size: '#{$size}'." 1233 | ` 1234 | }, 1235 | { 1236 | file: "00_Abstracts/mixins/_borders.sass", 1237 | content: `@import '../functions/borders'; 1238 | 1239 | @mixin border($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) 1240 | border: border($style, $size, $color, $variant, $opacity, $important) 1241 | 1242 | @mixin border-t($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) 1243 | border-top: border($style, $size, $color, $variant, $opacity, $important); 1244 | 1245 | @mixin border-b($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) 1246 | border-bottom: border($style, $size, $color, $variant, $opacity, $important) 1247 | 1248 | @mixin border-l($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) 1249 | border-left: border($style, $size, $color, $variant, $opacity, $important) 1250 | 1251 | @mixin border-r($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) 1252 | border-right: border($style, $size, $color, $variant, $opacity, $important) 1253 | 1254 | @mixin border-x($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) 1255 | @include border-l($style, $size, $color, $variant, $opacity, $important) 1256 | @include border-r($style, $size, $color, $variant, $opacity, $important) 1257 | 1258 | @mixin border-y($style: default-border(style), $size: default-border(size), $color: default-border(color), $variant: base, $opacity: 1, $important: false) 1259 | @include border-t($style, $size, $color, $variant, $opacity, $important) 1260 | @include border-b($style, $size, $color, $variant, $opacity, $important) 1261 | ` 1262 | }, 1263 | { 1264 | file: "00_Abstracts/mixins/_media-queries.sass", 1265 | content: `@import '../variables' 1266 | 1267 | /* 1268 | MEDIA QUERY 1269 | 1270 | Usage: @include mq(sm) {} 1271 | */ 1272 | @mixin mq($mq) { 1273 | @if map-has-key($media-queries, $mq) 1274 | $mq-value: map-get($media-queries, $mq) 1275 | 1276 | @if ($mq-value == light or $mq-value == dark) 1277 | @media screen and (prefers-color-scheme: $mq-value) 1278 | @content; 1279 | @else if ($mq-value == print) 1280 | @media print 1281 | @content 1282 | @else 1283 | @media screen and (max-width: ($mq-value - 1)) 1284 | @content 1285 | @else 1286 | @error "Invalid media query: '#{$mq}'." 1287 | ` 1288 | }, 1289 | { 1290 | file: "00_Abstracts/mixins/_display.sass", 1291 | content: `@mixin full-width($important: false) 1292 | @if ($important == true) 1293 | position: relative !important 1294 | left: 50% !important 1295 | right: 50% !important 1296 | width: 100vw !important 1297 | margin-left: -50vw !important 1298 | margin-right: -50vw !important 1299 | max-width: 100vw !important 1300 | @else 1301 | position: relative 1302 | left: 50% 1303 | right: 50% 1304 | width: 100vw 1305 | margin-left: -50vw 1306 | margin-right: -50vw 1307 | max-width: 100vw 1308 | 1309 | @mixin full-height($important: false) 1310 | @if ($important == true) 1311 | position: fixed !important 1312 | top: 50% !important 1313 | bottom: 50% !important 1314 | height: 100vh !important 1315 | margin-top: -50vh !important 1316 | margin-bottom: -50vh !important 1317 | max-height: 100vh !important 1318 | @else 1319 | position: fixed 1320 | top: 50% 1321 | bottom: 50% 1322 | height: 100vh 1323 | margin-top: -50vh 1324 | margin-bottom: -50vh 1325 | max-height: 100vh 1326 | 1327 | @mixin full-screen($important: false) 1328 | @include full-width(true) 1329 | @include full-height(true) 1330 | 1331 | @mixin screen-reader-only 1332 | position: absolute !important 1333 | width: 1px !important 1334 | height: 1px !important 1335 | padding: 0 !important 1336 | margin: -1px !important 1337 | overflow: hidden !important 1338 | clip: rect(0, 0, 0, 0) !important 1339 | white-space: nowrap !important 1340 | border: 0 !important 1341 | ` 1342 | }, 1343 | { 1344 | file: "00_Abstracts/mixins/_font-sizes.sass", 1345 | content: `@import '../variables' 1346 | 1347 | @mixin font-size($size) 1348 | @if map-has-key($font-sizes, $size) 1349 | @if ($important == true) 1350 | font-size: map-get($font-sizes, $size) !important 1351 | @else 1352 | font-size: map-get($font-sizes, $size) 1353 | @else 1354 | @error "Invalid font size: '#{$size}'." 1355 | ` 1356 | }, 1357 | { 1358 | file: "00_Abstracts/mixins/_font.sass", 1359 | content: `@import '../variables'; 1360 | 1361 | @mixin font($font: base, $important: false) 1362 | @if map-has-key($fonts, $font) 1363 | @if ($important == true) 1364 | font-family: unquote(map-get($fonts, $font)) !important; 1365 | @else 1366 | font-family: unquote(map-get($fonts, $font)); 1367 | @else 1368 | @error "Invalid shadow size: '#{$font}'."; 1369 | ` 1370 | }, 1371 | { 1372 | file: "00_Abstracts/mixins/_colors.sass", 1373 | content: `@import '../functions/colors'; 1374 | 1375 | @mixin bg-color($name: 'primary', $variant: 'base', $opacity: 1, $important: false) 1376 | @if ($important) 1377 | color: text-color($name, $variant, $opacity) !important 1378 | background-color: color($name, $variant, $opacity) !important 1379 | 1380 | @else 1381 | color: text-color($name, $variant, $opacity); 1382 | background-color: color($name, $variant, $opacity); 1383 | ` 1384 | }, 1385 | { 1386 | file: "00_Abstracts/mixins/_hover.sass", 1387 | content: `@mixin hover 1388 | &:hover 1389 | @content 1390 | 1391 | @mixin hover-focus 1392 | &:hover, 1393 | &:focus 1394 | @content 1395 | 1396 | @mixin hover-focus-active 1397 | &:hover, 1398 | &:focus, 1399 | &:active 1400 | @content 1401 | ` 1402 | }, 1403 | { 1404 | file: "00_Abstracts/mixins/_spacing.sass", 1405 | content: `@import '../variables'; 1406 | @import '../functions/spacing'; 1407 | 1408 | @mixin m($top, $right: $top, $bottom: $top, $left: $right, $important: false) 1409 | @include mt($top, $important) 1410 | @include mb($bottom, $important) 1411 | @include ml($left, $important) 1412 | @include mr($right, $important) 1413 | 1414 | @mixin mx($left, $right: $left, $important: false) 1415 | @include ml($left, $important) 1416 | @include mr($right, $important) 1417 | 1418 | @mixin my($top, $bottom: $top, $important: false) 1419 | @include mt($top, $important) 1420 | @include mb($bottom, $important) 1421 | 1422 | @mixin ml($size, $important: false) 1423 | margin-left: spacing($size) 1424 | 1425 | @mixin mt($size, $important: false) 1426 | margin-top: spacing($size) 1427 | 1428 | @mixin mr($size, $important: false) 1429 | margin-right: spacing($size) 1430 | 1431 | @mixin mb($size, $important: false) 1432 | margin-bottom: spacing($size) 1433 | 1434 | @mixin p($top, $right: $top, $bottom: $top, $left: $right, $important: false) 1435 | @include pt($top, $important) 1436 | @include pb($bottom, $important) 1437 | @include pl($left, $important) 1438 | @include pr($right, $important) 1439 | 1440 | @mixin px($left, $right: $left, $important: false) 1441 | @include pl($left, $important) 1442 | @include pr($right, $important) 1443 | 1444 | @mixin py($top, $bottom: $top, $important: false) 1445 | @include pt($top, $important) 1446 | @include pb($bottom, $important) 1447 | 1448 | @mixin pl($size, $important: false) 1449 | padding-left: spacing($size, $important) 1450 | 1451 | @mixin pt($size, $important: false) 1452 | padding-top: spacing($size, $important) 1453 | 1454 | @mixin pr($size, $important: false) 1455 | padding-right: spacing($size, $important) 1456 | 1457 | @mixin pb($size, $important: false) 1458 | padding-bottom: spacing($size, $important) 1459 | ` 1460 | }, 1461 | { 1462 | file: "00_Abstracts/mixins/_shadows.sass", 1463 | content: `@import '../variables' 1464 | 1465 | @mixin shadow($size: sm) 1466 | @if map-has-key($shadows, $size) 1467 | @if ($important == true) 1468 | box-shadow: unquote(map-get($shadows, $size)) !important 1469 | @else 1470 | box-shadow: unquote(map-get($shadows, $size)) 1471 | @else 1472 | @error "Invalid shadow size: '#{$size}'." 1473 | ` 1474 | }, 1475 | { 1476 | file: "00_Abstracts/mixins/_transitions.sass", 1477 | content: `@import '../variables' 1478 | 1479 | @mixin transition-ease-in-out($speed: fast) 1480 | @if map-has-key($transitions, $speed) 1481 | @if ($important == true) 1482 | transition: all map-get($transitions, $speed) ease-in-out !important 1483 | @else 1484 | transition: all map-get($transitions, $speed) ease-in-out 1485 | @else 1486 | @error "Invalid transition speed: '#{$speed}'." 1487 | ` 1488 | }, 1489 | { 1490 | file: "00_Abstracts/mixins/_z-index.sass", 1491 | content: `@import '../variables'; 1492 | 1493 | @mixin z-index($size, $important: false) 1494 | @if map-has-key($z-indexes, $size) 1495 | @if ($important == true) 1496 | z-index: map-get($z-indexes, $size) !important 1497 | @else 1498 | z-index: map-get($z-indexes, $size) 1499 | @else 1500 | @error "Invalid z-index: '#{$size}'." 1501 | ` 1502 | }, 1503 | { 1504 | file: "00_Abstracts/index.sass", 1505 | content: `@import 'variables' 1506 | @import 'functions/colors' 1507 | @import 'functions/math' 1508 | @import 'functions/strings' 1509 | @import 'functions/z-index' 1510 | @import 'mixins/border-radius' 1511 | @import 'mixins/borders' 1512 | @import 'mixins/media-queries' 1513 | @import 'mixins/colors' 1514 | @import 'mixins/display' 1515 | @import 'mixins/font' 1516 | @import 'mixins/font-sizes' 1517 | @import 'mixins/hover' 1518 | @import 'mixins/shadows' 1519 | @import 'mixins/spacing' 1520 | @import 'mixins/transitions' 1521 | @import 'mixins/z-index' 1522 | ` 1523 | }, 1524 | { 1525 | file: "01_Base/_base.sass", 1526 | content: `@import '../00_Abstracts/index'; 1527 | 1528 | html, 1529 | body 1530 | font-size: $font-size-base 1531 | line-height: $line-height-base 1532 | @include font(base) 1533 | 1534 | main 1535 | max-width: $content-width 1536 | @include mx(auto) 1537 | 1538 | code, 1539 | pre 1540 | @include font(monospace) 1541 | ` 1542 | }, 1543 | { 1544 | file: "01_Base/_index.sass", 1545 | content: `@import 'base'` 1546 | } 1547 | ] 1548 | }, 1549 | { 1550 | format: "less", 1551 | styles: [] 1552 | } 1553 | ]; 1554 | -------------------------------------------------------------------------------- /lib/services/configService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const inquirer_1 = require("inquirer"); 12 | const projectData_1 = require("../data/projectData"); 13 | const fileService_1 = require("../services/fileService"); 14 | const logService_1 = require("./logService"); 15 | class ConfigService { 16 | constructor() { 17 | this._fileService = new fileService_1.FileService(); 18 | this._logger = new logService_1.LogService(); 19 | this._configFileName = 'clarion-config.json'; 20 | } 21 | getConfigData() { 22 | if (this._fileService.fileExists('./' + this._configFileName)) { 23 | let config = this._fileService.readFile('./' + this._configFileName); 24 | return JSON.parse(config); 25 | } 26 | return projectData_1.projectData.clarionConfig; 27 | } 28 | updateConfigInfo() { 29 | return __awaiter(this, void 0, void 0, function* () { 30 | let responses = yield this.promptUserInput(); 31 | let config = this.updateConfigData(responses); 32 | this._fileService.saveFile(this._configFileName, JSON.stringify(config, null, '\t')); 33 | }); 34 | } 35 | updateConfigData(responses) { 36 | return { 37 | paths: { 38 | styles: responses.stylePath || './src', 39 | }, 40 | format: { 41 | styles: responses.styleFormat || 'scss', 42 | }, 43 | addToManifest: responses.addToManifest === true, 44 | importAbstracts: responses.importAbstracts === true 45 | }; 46 | } 47 | promptUserInput() { 48 | return __awaiter(this, void 0, void 0, function* () { 49 | let questions = [ 50 | { 51 | type: "input", 52 | name: "stylePath", 53 | message: "What is the path to your styles directory?", 54 | default: "./src", 55 | validate: value => value.length > 0 || "This questions is required." 56 | }, 57 | { 58 | type: "input", 59 | name: "styleFormat", 60 | message: "What format are your styles in?", 61 | default: "scss", 62 | validate: value => value.length > 0 || "Project Name is required." 63 | }, 64 | { 65 | type: "confirm", 66 | name: "addToManifest", 67 | message: "Would you like a reference to your new style sheets to automatically be added to the directory manifest (it's very convenient)?", 68 | default: "y" 69 | }, 70 | { 71 | type: "confirm", 72 | name: "importAbstracts", 73 | message: "Would you like a reference to your abstracts to automatically be added to your new style sheets (again, it's very convenient)?", 74 | default: "y" 75 | } 76 | ]; 77 | return yield inquirer_1.prompt(questions); 78 | ; 79 | }); 80 | } 81 | } 82 | exports.ConfigService = ConfigService; 83 | -------------------------------------------------------------------------------- /lib/services/directoryService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const inquirer_1 = require("inquirer"); 4 | const path = require("path"); 5 | const fs = require("fs"); 6 | const projectData_1 = require("../data/projectData"); 7 | const logService_1 = require("./logService"); 8 | const configService_1 = require("./configService"); 9 | class DirectoryService { 10 | constructor() { 11 | this._logger = new logService_1.LogService(); 12 | this._configService = new configService_1.ConfigService(); 13 | this._config = this._configService.getConfigData(); 14 | } 15 | createDirectory(pathName) { 16 | try { 17 | pathName = pathName.replace('//', '/'); 18 | fs.mkdirSync(pathName); 19 | this._logger.success(`Created directory: ${pathName}`); 20 | } 21 | catch (error) { 22 | this._logger.error(`There was an error creating this directory: ${pathName} \n${error}`); 23 | } 24 | } 25 | directoryExists(directoryName) { 26 | try { 27 | return fs.statSync(directoryName).isDirectory(); 28 | } 29 | catch (err) { 30 | return false; 31 | } 32 | } 33 | promptForMissingDirectory() { 34 | let directories = this.getAllStyleDirectories(); 35 | let question = { 36 | type: "list", 37 | name: "directory", 38 | message: "We could not find the directory you entered. Are you looking for one of these?", 39 | choices: directories 40 | }; 41 | if (directories.length === 0) { 42 | this._logger.warning("Please specify a directory."); 43 | } 44 | else { 45 | return inquirer_1.prompt(question).then(answers => answers.directory); 46 | } 47 | } 48 | findDirectoryByName(directoryName) { 49 | let directory; 50 | let directories = this.getAllStyleDirectories(); 51 | if (directoryName) { 52 | directory = directories.find(x => { 53 | return x.toLowerCase().includes(directoryName.toLowerCase()); 54 | }); 55 | } 56 | return directory; 57 | } 58 | findDirectory(directory) { 59 | let pathToDirectory = ''; 60 | if (this.directoryExists(`./${directory}`)) { 61 | pathToDirectory = `./${directory}`; 62 | } 63 | else { 64 | projectData_1.projectData.styleTypes.forEach(x => { 65 | let proposedPath = path.resolve(this.findStyleRootDirectory(), directory); 66 | if (this.directoryExists(proposedPath)) { 67 | pathToDirectory = proposedPath; 68 | } 69 | }); 70 | } 71 | return pathToDirectory; 72 | } 73 | getAllStyleDirectories() { 74 | let config = this._configService.getConfigData(); 75 | let stylePath = this.findStyleRootDirectory(); 76 | let directories = this.getDirectoriesInDirectory(stylePath); 77 | return directories; 78 | } 79 | findStyleRootDirectory() { 80 | let stylesDirectory = ''; 81 | projectData_1.projectData.styleTypes.forEach(type => { 82 | let proposedPath = path.resolve('./', this._config.paths.styles, type); 83 | if (this.directoryExists(proposedPath)) { 84 | stylesDirectory = proposedPath; 85 | } 86 | }); 87 | return stylesDirectory; 88 | } 89 | getDirectoriesInDirectory(path) { 90 | return fs.readdirSync(path).filter(function (file) { 91 | return fs.statSync(path + '/' + file).isDirectory(); 92 | }); 93 | } 94 | } 95 | exports.DirectoryService = DirectoryService; 96 | -------------------------------------------------------------------------------- /lib/services/fileService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const path = require("path"); 4 | const fs = require("fs"); 5 | const program = require("commander"); 6 | const logService_1 = require("./logService"); 7 | class FileService { 8 | constructor() { 9 | this._logger = new logService_1.LogService(); 10 | } 11 | saveFile(destination, content) { 12 | try { 13 | destination = destination.replace('//', '/'); 14 | fs.writeFileSync(destination, content); 15 | this._logger.success(`Saved file: ${destination}`); 16 | } 17 | catch (error) { 18 | this._logger.error(`There was an error saving this file: ${destination} \n${error}`); 19 | } 20 | } 21 | fileExists(fileName) { 22 | try { 23 | return fs.statSync(fileName).isFile(); 24 | } 25 | catch (err) { 26 | return false; 27 | } 28 | } 29 | getManifestFile(filePath) { 30 | if (!filePath) 31 | return undefined; 32 | let manifestFile; 33 | fs.readdirSync(filePath).forEach(file => { 34 | if (file.startsWith('index') || file.startsWith('style')) 35 | manifestFile = file; 36 | }); 37 | return manifestFile; 38 | } 39 | getFileExtension(directory) { 40 | switch (true) { 41 | case program.sass: 42 | return '.sass'; 43 | case program.scss: 44 | return '.scss'; 45 | case program.less: 46 | return '.less'; 47 | default: 48 | return this.getManifestExtension(directory); 49 | } 50 | } 51 | getManifestExtension(directory) { 52 | let manifest = this.getManifestFile(directory); 53 | return manifest ? path.extname(manifest).replace('.', '') : 'scss'; 54 | } 55 | addFileToManifest(fileName, manifestFile, sort) { 56 | if (sort) { 57 | var data = fs.readFileSync(manifestFile, 'utf8'); 58 | let importStatements = data.split('\n').filter(String); 59 | importStatements.push(fileName); 60 | importStatements.sort(); 61 | this.saveFile(manifestFile, importStatements.join('\n')); 62 | } 63 | else { 64 | fs.appendFileSync(manifestFile, `@import '${fileName}';\n`); 65 | } 66 | this._logger.success(`Saved file: ${fileName} was added to the manifest.`); 67 | } 68 | removeFileFromManifest(fileName, manifestFile) { 69 | let importStatements = this.readFile(manifestFile).split('\n'); 70 | let fileIndex = importStatements.indexOf(`@import '${fileName}';`); 71 | if (fileIndex < 0) { 72 | this._logger.warning('File to be removed was not found in your manifest.'); 73 | } 74 | else { 75 | importStatements.splice(fileIndex, 1); 76 | let formattedImportStatements = importStatements.join('\n'); 77 | this.saveFile(manifestFile, formattedImportStatements); 78 | } 79 | } 80 | removeFile(filePath) { 81 | try { 82 | if (this.fileExists(filePath)) { 83 | fs.unlinkSync(filePath); 84 | this._logger.success(`File removed: ${filePath}`); 85 | } 86 | else { 87 | this._logger.warning(filePath + ' was not found'); 88 | } 89 | } 90 | catch (error) { 91 | this._logger.error(`There was an error removing this file: ${filePath} \n${error}`); 92 | } 93 | } 94 | readFile(filePath) { 95 | return this.fileExists(filePath) ? fs.readFileSync(filePath).toString() : ''; 96 | } 97 | getStyleFormat(extension) { 98 | return extension.replace('.', '') === 'less' ? 'less' : 'sass'; 99 | } 100 | getImportExtension(extension) { 101 | return `${extension !== 'sass' && extension !== 'scss' ? '.' + extension : ''}${extension === 'sass' ? '\'' : '\';'}`; 102 | } 103 | } 104 | exports.FileService = FileService; 105 | -------------------------------------------------------------------------------- /lib/services/logService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const chalk_1 = require("chalk"); 4 | class LogService { 5 | log(message) { 6 | console.log(message); 7 | } 8 | error(message) { 9 | console.log(chalk_1.default.red(message)); 10 | } 11 | warning(message) { 12 | console.log(chalk_1.default.yellow(message)); 13 | } 14 | success(message) { 15 | console.log(chalk_1.default.green(message)); 16 | } 17 | info(message) { 18 | console.log(chalk_1.default.blue(message)); 19 | } 20 | } 21 | exports.LogService = LogService; 22 | -------------------------------------------------------------------------------- /lib/services/projectService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const fileService_1 = require("../services/fileService"); 4 | const projectData_1 = require("../data/projectData"); 5 | const newProject_1 = require("../data/newProject"); 6 | class ProjectDataService { 7 | constructor(_pipeline, _styleFormat) { 8 | this._pipeline = _pipeline; 9 | this._styleFormat = _styleFormat; 10 | this._fileService = new fileService_1.FileService(); 11 | this._projectTypeData = this.getProjectTypeData(); 12 | } 13 | getProjectData(projectName) { 14 | if (this._pipeline !== newProject_1.newProject.options.pipeline.parcel) 15 | this.createBundleConfigurationFile(projectName); 16 | if (this._pipeline !== newProject_1.newProject.options.pipeline.grunt) 17 | this.createPostCssConfig(projectName); 18 | return { 19 | devDependencies: this.getProjectDependencies(), 20 | projectCommands: this._projectTypeData.npmCommands 21 | }; 22 | } 23 | getHtmlTemplate(cssPath, jsPath) { 24 | return projectData_1.projectData.indexHtml 25 | .replace(/%%cssDir%%/g, cssPath) 26 | .replace(/%%jsDir%%/g, jsPath); 27 | } 28 | createBundleConfigurationFile(projectName) { 29 | let rootPath = './' + projectName; 30 | this._fileService.saveFile(rootPath + '/' + this._projectTypeData.configFile, this.getConfigFileContents()); 31 | } 32 | createPostCssConfig(projectName) { 33 | let content = 'module.exports = ' + JSON.stringify(projectData_1.projectData.postCssConfig, null, '\t'); 34 | this._fileService.saveFile(`./${projectName}/postcss.config.js`, content); 35 | } 36 | getConfigFileContents() { 37 | let extension = this._fileService.getFileExtension(null).replace('.', ''); 38 | let styleFormat = extension === 'less' ? 'less' : 'sass'; 39 | return this.updateConfigTemplateWithProjectData(extension, styleFormat); 40 | } 41 | updateConfigTemplateWithProjectData(extension, styleFormat) { 42 | let contents = this._projectTypeData.configContents 43 | .replace(/%%styleFormat%%/g, styleFormat) 44 | .replace(/%%extension%%/g, extension); 45 | if (this._pipeline === 'Grunt') { 46 | if (styleFormat === 'less') { 47 | contents = contents.replace(/grunt-less/g, 'grunt-contrib-less'); 48 | } 49 | if (styleFormat === 'sass') { 50 | contents = 'const sass = require("node-sass");\n\n' + contents; 51 | contents = contents.replace(/sass: {\n/g, 'sass: {\n\t\t\toptions: {\n\t\t\t\timplementation: sass,\n\t\t\t\tsourceMap: true\n\t\t\t},\n'); 52 | } 53 | } 54 | if (this._pipeline === 'Webpack') { 55 | if (styleFormat === 'less') { 56 | contents = contents.replace('(scss|sass)', 'less'); 57 | } 58 | } 59 | return contents; 60 | } 61 | getProjectDependencies() { 62 | let dependencies = this._projectTypeData.devDependencies; 63 | return dependencies.concat(this._styleFormat === newProject_1.newProject.options.styleFormat.less ? this._projectTypeData.lessDependencies : this._projectTypeData.sassDependencies); 64 | } 65 | getProjectTypeData() { 66 | switch (true) { 67 | case this._pipeline === newProject_1.newProject.options.pipeline.grunt: 68 | return projectData_1.projectData.grunt; 69 | case this._pipeline === newProject_1.newProject.options.pipeline.gulp: 70 | return projectData_1.projectData.gulp; 71 | case this._pipeline === newProject_1.newProject.options.pipeline.parcel: 72 | return projectData_1.projectData.parcel; 73 | default: 74 | return projectData_1.projectData.webpack; 75 | } 76 | } 77 | } 78 | exports.ProjectDataService = ProjectDataService; 79 | -------------------------------------------------------------------------------- /lib/services/shellService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const child_process_1 = require("child_process"); 4 | const logService_1 = require("./logService"); 5 | class ShellService { 6 | constructor() { 7 | this._logger = new logService_1.LogService(); 8 | } 9 | executeCommand(command) { 10 | try { 11 | this._logger.info('Installing your dependencies... (This may take a minute.)'); 12 | child_process_1.execSync(command); 13 | } 14 | catch (error) { 15 | this._logger.error('There was a problem installing your dependencies.'); 16 | this._logger.error(error); 17 | return; 18 | } 19 | this._logger.warning(`Your command '${command}' was successfully executed.`); 20 | } 21 | installNPMDependencies(projectName, packages, devDependency = false) { 22 | let dependencyType = devDependency ? "-D" : "-S"; 23 | let command = `cd ./${projectName} && npm install ${dependencyType} ${packages.join(' ')}`; 24 | this.executeCommand(command); 25 | } 26 | } 27 | exports.ShellService = ShellService; 28 | -------------------------------------------------------------------------------- /lib/taskManager.js: -------------------------------------------------------------------------------- 1 | // import { INewProject, NewProject } from "./commands/new"; 2 | // import { IAdd, Add } from "./commands/add"; 3 | // import { IRemove, Remove } from "./commands/remove"; 4 | // import { IConfig, Config } from "./commands/config"; 5 | // import { ILogService, LogService } from "./services/logService"; 6 | // export interface ITaskService { 7 | // processUserAction(commands: string[]); 8 | // } 9 | // export class TaskService implements ITaskService { 10 | // _logService: ILogService = new LogService(); 11 | // async processUserAction(commands: string[]) { 12 | // let action = commands[0]; 13 | // if (action) { 14 | // switch (action.toLowerCase()) { 15 | // case 'new': 16 | // let newProject: INewProject = new NewProject(); 17 | // // newProject.init(); 18 | // break; 19 | // case 'add': 20 | // let add: IAdd = new Add(); 21 | // // add.addNewFile(); 22 | // break; 23 | // case 'remove': 24 | // let remove: IRemove = new Remove(); 25 | // // remove.removeFile(); 26 | // break; 27 | // case 'config': 28 | // let config: IConfig = new Config(); 29 | // config.updateConfig(); 30 | // break; 31 | // default: 32 | // this._logService.warning(`${commands[0]} is not a recognized command.`); 33 | // break; 34 | // } 35 | // } else { 36 | // this._logService.warning('Please enter a valid command. Try "clarion --help" for more information.'); 37 | // } 38 | // } 39 | // } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clarion", 3 | "version": "3.9.0", 4 | "description": "A CSS and Design System Framework for rapidly building applications.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha -r ts-node/register tests/**/*.ts", 8 | "update": "tsc && npm i -g", 9 | "deploy": "tsc && npm publish" 10 | }, 11 | "author": "Burton Smith ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "chalk": "^2.4.1", 15 | "commander": "^2.16.0", 16 | "inquirer": "^6.2.1" 17 | }, 18 | "bin": { 19 | "clarion": "./bin/clarion" 20 | }, 21 | "install": "npm i -g clarion", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/break-stuff/clarion" 25 | }, 26 | "homepage": "https://projectclarion.com", 27 | "keywords": [ 28 | "clarion", 29 | "css", 30 | "css architecture", 31 | "sass", 32 | "sass architecture", 33 | "scss", 34 | "scss architecture", 35 | "less", 36 | "less architecture", 37 | "design systems", 38 | "design system framework", 39 | "starter project", 40 | "scaffolding", 41 | "CLI", 42 | "build", 43 | "methodology", 44 | "boilerplate", 45 | "architecture", 46 | "javascript", 47 | "webpack", 48 | "parcel", 49 | "grunt", 50 | "gulp" 51 | ], 52 | "devDependencies": { 53 | "@types/chai": "^4.1.4", 54 | "@types/chalk": "^0.4.31", 55 | "@types/commander": "^2.12.2", 56 | "@types/mocha": "^5.2.4", 57 | "@types/node": "^8.10.20", 58 | "chai": "^4.1.2", 59 | "fsevents": "^2.0.6", 60 | "mocha": "^7.1.2", 61 | "ts-node": "^5.0.1", 62 | "typescript": "^2.9.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/clarion.ts: -------------------------------------------------------------------------------- 1 | import * as commander from "commander"; 2 | import { prompt } from "inquirer"; 3 | import { starterQuestions } from './data/newProject'; 4 | import { NewProject } from "./commands/new"; 5 | import { IAdd, Add } from "./commands/add"; 6 | import { IRemove, Remove } from "./commands/remove"; 7 | import { IConfigService, ConfigService } from "./services/configService"; 8 | 9 | commander 10 | .version("3.8.4") 11 | .usage(""); 12 | 13 | commander 14 | .command("new") 15 | .description('Create a new project') 16 | .action(async () => { 17 | let projectName: string = ''; 18 | let pipeline: string = ''; 19 | const projectStartResponse = await prompt(starterQuestions.projectStart); 20 | const newProject = new NewProject(); 21 | if (projectStartResponse.projectStart === 'Manual Configuration') { 22 | const projectTypeResponse = await prompt(starterQuestions.projectType) 23 | if (projectTypeResponse.projectType === 'Starter Project') { 24 | const projectNameResponse = await prompt(starterQuestions.projectName); 25 | const pipelineResponse = await prompt(starterQuestions.pipeline); 26 | 27 | projectName = projectNameResponse.projectName; 28 | pipeline = pipelineResponse.pipeline; 29 | } 30 | 31 | const styleFormatResponses = await prompt(starterQuestions.styleFormat); 32 | newProject.init(projectTypeResponse.projectType, projectName, styleFormatResponses.styleFormat, pipeline); 33 | } else { 34 | const projectNameResponse = await prompt(starterQuestions.projectName); 35 | newProject.init('default', projectNameResponse.projectName, 'SCSS', 'Webpack'); 36 | } 37 | }); 38 | 39 | 40 | commander 41 | .command('add [filename]') 42 | .description('Use "add " to add a style sheet to a specific directory || Use "add " to add a style sheet to current directory') 43 | .action((dir, filename) => { 44 | let add: IAdd = new Add(); 45 | 46 | if (dir === 'directory') { 47 | add.addNewDirectory(filename); 48 | } else { 49 | if (filename) 50 | add.addFileToSpecifiedDirectory(dir, filename); 51 | else 52 | add.addFileToCurrentDirectory(dir); 53 | } 54 | }); 55 | 56 | commander 57 | .command('mkdir ') 58 | .description('Add new style directory to architecture') 59 | .action((foldername) => { 60 | let add: IAdd = new Add(); 61 | add.addNewDirectory(foldername); 62 | }); 63 | 64 | commander 65 | .command('remove [filename]') 66 | .description('Use "add " to add a style sheet to a specific directory || Use "add " to add a style sheet to current directory') 67 | .action((dir, filename) => { 68 | let remove: IRemove = new Remove(); 69 | if(filename) 70 | remove.removeFileFromSpecifiedDirectory(dir, filename); 71 | else 72 | remove.removeFileFromCurrentDirectory(dir); 73 | }); 74 | 75 | 76 | commander 77 | .command('config') 78 | .description('Configure Clarion to your development environment') 79 | .action(() => { 80 | let configService: IConfigService = new ConfigService(); 81 | configService.updateConfigInfo(); 82 | }); 83 | 84 | commander.parse(process.argv); 85 | -------------------------------------------------------------------------------- /src/commands/add.ts: -------------------------------------------------------------------------------- 1 | import { FileService, IFileService } from "../services/fileService"; 2 | import { ILogService, LogService } from "../services/logService"; 3 | import { IDirectoryService, DirectoryService } from "../services/directoryService"; 4 | import { IConfigService, ConfigService } from "../services/configService"; 5 | 6 | export interface IAdd { 7 | addNewDirectory(folderName: string): void; 8 | addFileToCurrentDirectory(fileName: string): void; 9 | addFileToSpecifiedDirectory(dir: string, fileName: string): void; 10 | } 11 | 12 | export class Add implements IAdd { 13 | _fileService: IFileService = new FileService(); 14 | _logService: ILogService = new LogService(); 15 | _directoryService: IDirectoryService = new DirectoryService(); 16 | _configService: IConfigService = new ConfigService(); 17 | _config = this._configService.getConfigData(); 18 | 19 | addFileToCurrentDirectory(fileName: string): void { 20 | if (!this._fileService.getManifestFile("./")) { 21 | this._logService.warning( 22 | "Sorry, this is not a directory you can add styles to or you may be missing parameters." 23 | ); 24 | } else { 25 | this.processNewFile(".", fileName); 26 | } 27 | } 28 | 29 | addNewDirectory(folderName: string) { 30 | let extension = this._fileService.getFileExtension("/"); 31 | let rootDirectory = this._directoryService.findStyleRootDirectory(); 32 | 33 | this._directoryService.createDirectory( 34 | `${rootDirectory}/${folderName}` 35 | ); 36 | this._fileService.saveFile( 37 | `${rootDirectory}/${folderName}/index.${extension}`, 38 | "" 39 | ); 40 | this._fileService.addFileToManifest( 41 | `@import './${folderName}/index${this._fileService.getImportExtension(extension)}`, 42 | `${rootDirectory}/styles.${extension}`, 43 | true 44 | ); 45 | } 46 | 47 | addFileToSpecifiedDirectory(dir: string, fileName: string): void { 48 | let directoryName = this._directoryService.findDirectoryByName(dir); 49 | 50 | if (directoryName) { 51 | let pathToDirectory = this._directoryService.findDirectory( 52 | directoryName 53 | ); 54 | if (pathToDirectory) { 55 | this.processNewFile(pathToDirectory, fileName); 56 | } else { 57 | this._logService.warning( 58 | "Sorry, the directory you specified was not found." 59 | ); 60 | } 61 | } else { 62 | this._directoryService.promptForMissingDirectory() 63 | .then(directory => this.addFileToSpecifiedDirectory(directory, fileName)); 64 | } 65 | } 66 | 67 | processNewFile(pathToDirectory: string, fileName: string) { 68 | let extension = this._fileService.getFileExtension(pathToDirectory); 69 | let newFile = this.getNewFile(fileName, extension); 70 | let manifestFile = `${pathToDirectory}/${this._fileService.getManifestFile( 71 | pathToDirectory 72 | )}`; 73 | 74 | if (!this._fileService.fileExists(`${pathToDirectory}/${newFile}`)) { 75 | let pathToRoot = this.getPathToRoot(fileName); 76 | let importStatement = 77 | this._config.importAbstracts && 78 | !pathToDirectory.includes("00_Abstracts") 79 | ? `@import '${pathToRoot}00_Abstracts/index${this._fileService.getImportExtension(extension)}` 80 | : ""; 81 | this._fileService.saveFile( 82 | `${pathToDirectory}/${newFile}`, 83 | importStatement 84 | ); 85 | 86 | if (this._config.addToManifest) 87 | this._fileService.addFileToManifest( 88 | extension === 'sass' || extension === 'scss' ? fileName : newFile, 89 | manifestFile, 90 | false 91 | ); 92 | } else { 93 | this._logService.warning(newFile + " already exists."); 94 | } 95 | } 96 | 97 | getNewFile(fileName: string, extension: string) { 98 | let directories = fileName.split("/"); 99 | 100 | if (directories.length > 1) { 101 | directories[directories.length - 1] = `_${ 102 | directories[directories.length - 1] 103 | }.${extension}`; 104 | return directories.join("/"); 105 | } 106 | 107 | return `_${fileName}.${extension}`; 108 | } 109 | 110 | getPathToRoot(fileName: string) { 111 | let pathDepth = fileName.split("/").length; 112 | let pathToRoot = "../"; 113 | 114 | for (let i = 1; i < pathDepth; i++) { 115 | pathToRoot += "../"; 116 | } 117 | 118 | return pathToRoot; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/commands/new.ts: -------------------------------------------------------------------------------- 1 | import { newProject } from '../data/newProject'; 2 | import { FileService, IFileService } from "../services/fileService"; 3 | import { IProjectData, IProjectDataService, ProjectDataService } from '../services/projectService'; 4 | import { ILogService, LogService } from "../services/logService"; 5 | import { IDirectoryService, DirectoryService } from "../services/directoryService"; 6 | import { IShellService, ShellService } from '../services/shellService' 7 | import { projectData } from "../data/projectData"; 8 | import { styleContent } from '../data/styleContentData'; 9 | 10 | export interface INewProject { 11 | init(projectType: string, projectName: string, styleFormat: string, pipeline: string): void; 12 | } 13 | 14 | export class NewProject implements INewProject { 15 | _fileService: IFileService = new FileService(); 16 | _logService: ILogService = new LogService(); 17 | _directoryService: IDirectoryService = new DirectoryService(); 18 | _shellService: IShellService = new ShellService(); 19 | _projectService: IProjectDataService; 20 | _projectName: string; 21 | _projectType: string; 22 | _styleRootPath: string; 23 | _styleFormat: string; 24 | _pipeline: string; 25 | 26 | _styleFileRootPath: string = ''; 27 | _projectData: IProjectData; 28 | 29 | init(projectType: string, projectName: string, styleFormat: string, pipeline: string) { 30 | this._projectType = projectType; 31 | this._projectName = projectName; 32 | this._styleFormat = styleFormat; 33 | this._pipeline = pipeline; 34 | this._projectService = new ProjectDataService(pipeline, styleFormat); 35 | this.createNewProject(); 36 | } 37 | 38 | createNewProject(): void { 39 | if (this._projectType !== newProject.options.projectType.architectureOnly 40 | && this._projectType !== newProject.options.projectType.stylesOnly) { 41 | this.createScriptScaffolding(); 42 | this.createTaskRunner(); 43 | this.createPackageJson(); 44 | this.createIndexHtml(); 45 | } 46 | 47 | this.createStyleScaffolding(); 48 | this.addStyleFramework(); 49 | this.displayStartupInstructions(); 50 | } 51 | 52 | createScriptScaffolding() { 53 | let extension = this._styleFormat.toLowerCase(); 54 | this._styleFileRootPath = `./src/${extension}/styles.${extension}`; 55 | let mainContents = this._pipeline === newProject.options.pipeline.webpack ? `import '../${extension}/styles.${extension}';` : ''; 56 | if (this._projectName) { 57 | this._directoryService.createDirectory(this._projectName); 58 | } 59 | 60 | projectData.projectDirectories.forEach(x => { 61 | this._directoryService.createDirectory(x.replace('%%projectName%%', this._projectName)); 62 | }); 63 | this._fileService.saveFile(`./${this._projectName}/src/scripts/main.js`, mainContents); 64 | } 65 | 66 | createStyleScaffolding() { 67 | let extension = this._styleFormat.toLowerCase(); 68 | this.createStyleRootDirectory(extension); 69 | let importStatements = this.createStyleDirectories(extension); 70 | this.createRootManifest(extension, importStatements); 71 | } 72 | 73 | createStyleRootDirectory(extension: string) { 74 | this._styleRootPath = `./${extension}`; 75 | if ( 76 | this._projectType !== newProject.options.projectType.architectureOnly 77 | && this._projectType !== newProject.options.projectType.stylesOnly 78 | ) { 79 | this._styleRootPath = `./${this._projectName}/src/${extension}`; 80 | } 81 | 82 | this._directoryService.createDirectory(this._styleRootPath); 83 | } 84 | 85 | createStyleDirectories(extension: string): string { 86 | let importStatements = ''; 87 | 88 | projectData.styleDirectories.forEach(styleDirectory => { 89 | this._directoryService.createDirectory(`${this._styleRootPath}/${styleDirectory.name}`); 90 | this._fileService.saveFile(`${this._styleRootPath}/${styleDirectory.name}/index.${extension}`, ''); 91 | this._fileService.saveFile(`${this._styleRootPath}/${styleDirectory.name}/README.md`, styleDirectory.readMe); 92 | importStatements += `@import './${styleDirectory.name}/index${this._fileService.getImportExtension(extension)}\n`; 93 | }); 94 | 95 | return importStatements 96 | } 97 | 98 | createRootManifest(extension: string, importStatements: string): void { 99 | this._fileService.saveFile(`${this._styleRootPath}/styles.${extension}`, importStatements); 100 | } 101 | 102 | createPackageJson() { 103 | projectData.packageJson.name = this.convertToKebabCase(this._projectName); 104 | projectData.packageJson.scripts = this._projectData.projectCommands; 105 | this._fileService.saveFile(`./${this._projectName}/package.json`, JSON.stringify(projectData.packageJson, null, '\t')); 106 | this._shellService.installNPMDependencies(this._projectName, this._projectData.devDependencies, true); 107 | } 108 | 109 | convertToKebabCase(text: string) { 110 | return text.replace(/\s+/g, '-') 111 | .replace(/([a-z0-9])([A-Z])/g, '$1-$2') 112 | .toLowerCase(); 113 | } 114 | 115 | createTaskRunner(): void { 116 | this._projectData = this._projectService.getProjectData(this._projectName); 117 | } 118 | 119 | createIndexHtml() { 120 | let isParcel = this._pipeline === newProject.options.pipeline.parcel; 121 | let jsDir = isParcel ? './src/scripts/main.js' : './dist/scripts.js'; 122 | let cssDir = isParcel ? this._styleFileRootPath : './dist/styles.css'; 123 | let contents = this._projectService.getHtmlTemplate(cssDir, jsDir); 124 | this._fileService.saveFile(`./${this._projectName}/index.html`, contents); 125 | } 126 | 127 | displayStartupInstructions(): void { 128 | if (this._projectType !== newProject.options.projectType.architectureOnly 129 | && this._projectType !== newProject.options.projectType.stylesOnly) { 130 | this._logService.info('\nTo get started run the following command:'); 131 | if (this._projectName) this._logService.info(`cd ${this._projectName}`); 132 | this._logService.info('npm run dev'); 133 | } 134 | } 135 | 136 | addStyleFramework(): void { 137 | if (this._projectType !== newProject.options.projectType.architectureOnly) { 138 | this._directoryService.createDirectory(`${this._styleRootPath}/00_Abstracts/mixins`); 139 | this._directoryService.createDirectory(`${this._styleRootPath}/00_Abstracts/functions`); 140 | styleContent 141 | .filter(x => x.format === this._styleFormat.toLowerCase()) 142 | .forEach(x => x.styles 143 | .forEach(y => this._fileService.saveFile(`${this._styleRootPath}/${y.file}`, y.content))); 144 | } 145 | } 146 | } -------------------------------------------------------------------------------- /src/commands/remove.ts: -------------------------------------------------------------------------------- 1 | import { FileService, IFileService } from "../services/fileService"; 2 | import { ILogService, LogService } from "../services/logService"; 3 | import { IDirectoryService, DirectoryService } from "../services/directoryService"; 4 | 5 | export interface IRemove { 6 | removeFileFromCurrentDirectory(fileName: string): void; 7 | removeFileFromSpecifiedDirectory(dir: string, fileName: string): void; 8 | } 9 | 10 | export class Remove implements IRemove { 11 | _fileService: IFileService = new FileService(); 12 | _logService: ILogService = new LogService(); 13 | _directoryService: IDirectoryService = new DirectoryService(); 14 | 15 | removeFileFromCurrentDirectory(fileName: string): void { 16 | if (!this._fileService.getManifestFile('./')) { 17 | this._logService.warning('Sorry, this is not a directory you can remove styles from or you may be missing parameters.'); 18 | } else { 19 | this.processFileRemoval('.', fileName); 20 | } 21 | } 22 | 23 | removeFileFromSpecifiedDirectory(dir: string, fileName: string): void { 24 | let directoryName = this._directoryService.findDirectoryByName(dir); 25 | 26 | if (directoryName) { 27 | let pathToDirectory = this._directoryService.findDirectory(directoryName); 28 | if (pathToDirectory) { 29 | this.processFileRemoval(pathToDirectory, fileName); 30 | } else { 31 | this._logService.warning( 32 | "Sorry, the directory you specified was not found." 33 | ); 34 | } 35 | } else { 36 | this._directoryService.promptForMissingDirectory() 37 | .then(directory => this.removeFileFromSpecifiedDirectory(directory, fileName)); 38 | } 39 | } 40 | 41 | processFileRemoval(pathToDirectory: string, fileName: string): void { 42 | let extension = this._fileService.getFileExtension(pathToDirectory); 43 | let fileToRemove = `_${fileName}.${extension}`; 44 | let manifestFile = `${pathToDirectory}/${this._fileService.getManifestFile(pathToDirectory)}`; 45 | this._fileService.removeFile(`${pathToDirectory}/${fileToRemove}`); 46 | this._fileService.removeFileFromManifest(extension === 'sass' || extension === 'scss' ? fileName : fileToRemove, manifestFile); 47 | } 48 | } -------------------------------------------------------------------------------- /src/data/newProject.ts: -------------------------------------------------------------------------------- 1 | export interface INewProjectInfo { 2 | projectName: string; 3 | projectType: string; 4 | styleFormat: string; 5 | pipeline: string; 6 | } 7 | 8 | export var starterQuestions = { 9 | projectStart: [ 10 | { 11 | type: "list", 12 | name: "projectStart", 13 | message: "Start with default settings or would you like to customize your setup?", 14 | choices: ["Default (Starter Project, SCSS, Webpack)", "Manual Configuration"] 15 | } 16 | ], 17 | projectType: [ 18 | { 19 | type: "list", 20 | name: "projectType", 21 | message: "What can we get you started with?", 22 | choices: ["Starter Project", "Styles Only", "Architecture Only"] 23 | } 24 | ], 25 | projectName: [ 26 | { 27 | type: "input", 28 | name: "projectName", 29 | message: "Project Name:", 30 | validate: (value: string) => value.length > 0 || "Project Name is required." 31 | } 32 | ], 33 | styleFormat: [ 34 | { 35 | type: "list", 36 | name: "styleFormat", 37 | message: "What style format would you like to use?", 38 | choices: ["SCSS", "SASS", "LESS"] 39 | } 40 | ], 41 | pipeline: [ 42 | { 43 | type: "list", 44 | name: "pipeline", 45 | message: "What bundler or task-runner would you like to use?", 46 | choices: ["Webpack", "Parcel", "Gulp", "Grunt"] 47 | } 48 | ] 49 | } 50 | 51 | export var newProject = { 52 | options: { 53 | projectType: { 54 | starter: "Starter Project", 55 | stylesOnly: "Styles Only", 56 | architectureOnly: "Architecture Only" 57 | }, 58 | styleFormat: { 59 | scss: "SCSS", 60 | sass: "SASS", 61 | less: "LESS" 62 | }, 63 | pipeline: { 64 | webpack: "Webpack", 65 | parcel: "Parcel", 66 | gulp: "Gulp", 67 | grunt: "Grunt" 68 | } 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /src/data/projectData.ts: -------------------------------------------------------------------------------- 1 | export interface IProjectData { 2 | styleDirectories: {name:string; readMe:string;}[]; 3 | styleTypes: string[]; 4 | projectDirectories: string[]; 5 | packageJson: IPackageJson; 6 | clarionConfig: IClarionConfig; 7 | postCssConfig: object; 8 | grunt: IProjectTypeData; 9 | gulp: IProjectTypeData; 10 | parcel: IProjectTypeData; 11 | webpack: IProjectTypeData; 12 | indexHtml: string; 13 | } 14 | 15 | export interface IPackageJson { 16 | name: string; 17 | version: string; 18 | description: string; 19 | main: string; 20 | scripts: object; 21 | keywords: string[]; 22 | author: string; 23 | license: string; 24 | } 25 | 26 | export interface IClarionConfig { 27 | paths: { 28 | styles: string, 29 | scripts: string 30 | }, 31 | format: { 32 | styles: string, 33 | scripts: string 34 | }, 35 | addToManifest: string, 36 | importAbstracts: string 37 | } 38 | 39 | export interface IProjectTypeData { 40 | configFile: string; 41 | devDependencies: string[]; 42 | lessDependencies: string[]; 43 | sassDependencies: string[]; 44 | npmCommands: object; 45 | configContents: string; 46 | } 47 | 48 | export var projectData: IProjectData = { 49 | styleDirectories: [ 50 | { 51 | name: "00_Abstracts", 52 | readMe: '# Abstracts\n\nThis folder is for your style tools and helpers used across the project. Your reusable code such as global variables, functions, mixins and placeholders should be put in here.\n\nThe code contained in this folder should not be code that outputs CSS directly.\n\nWith larger projects you may want to break this into subdirectories like variables, functions, and mixins. Subdirectories are supported in the Clarion CLI.' 53 | }, 54 | { 55 | name: "01_Base", 56 | readMe: '# Base\n\nThis folder holds the boilerplate code for the project. You would add your reset files and typographic rules. Keep in mind that most front-end frameworks like Bootstrap and Foundation already have their own CSS resets built into their styles, so additional resets may not be necessary.' 57 | }, 58 | { 59 | name: "02_Vendors", 60 | readMe: '# Vendors\n\nThe Themes folder contains third-party themes for your website and for things like styles for plugins. This folder has been added higher in the architecture so that if you would like to create customizations, you can do so in the subsequent folders like Components, Layouts, and Pages. This will allow you to override the theme\'s rules without modifying the theme directly and ruining the integrity of the code.' 61 | }, 62 | { 63 | name: "03_Elements", 64 | readMe: '# Elements\n\nThe Elements folder is for styling specific elements such as links, buttons, form inputs, and header tags. These are the smallest building blocks in the web and cannot be broken up into smaller parts. Browsers, by default, have their own styling in place for these elements, but tend to be inconsistent. Because of this, adding a reset to the 01_Base directory or adding one to the build pipeline is recommended.' 65 | }, 66 | { 67 | name: "04_Components", 68 | readMe: '# Components\n\nComponents are groups of elements. This can be things like a search box, navbar, or carousel. These groups of elements together have a specific purpose.' 69 | }, 70 | { 71 | name: "05_Layouts", 72 | readMe: '# Layouts\n\nThis folder contains everything that takes part in laying out the web site or application. These could be containers used for organizing elements and components within them like like a header, footer, or sidebar. This could also be used for your grid-system.' 73 | }, 74 | { 75 | name: "06_Pages", 76 | readMe: '# Pages\n\nThis folder is for any page-specific styles, if you have any. Some sites may have custom styling for the Home page or the Contact Us page.' 77 | }, 78 | { 79 | name: "07_Utilities", 80 | readMe: '# Utilities\n\nThe Utilities are used to create overrides or call out specific rules for an elements or components. For example, you could have a class for making text bold or aligning it to the right. The rule of thumb is to make it specific and make it simple.' 81 | } 82 | ], 83 | styleTypes: [ 84 | "less", 85 | "sass", 86 | "scss" 87 | ], 88 | projectDirectories: [ 89 | './%%projectName%%/dist', 90 | './%%projectName%%/src', 91 | './%%projectName%%/src/scripts', 92 | './%%projectName%%/src/scripts/components', 93 | './%%projectName%%/src/scripts/services' 94 | ], 95 | packageJson: { 96 | name: '', 97 | version: "0.1.0", 98 | description: "", 99 | main: "index.js", 100 | scripts: {}, 101 | keywords: [], 102 | author: "", 103 | license: "ISC", 104 | }, 105 | clarionConfig: { 106 | paths: { 107 | styles: './src', 108 | scripts: './src' 109 | }, 110 | format: { 111 | styles: 'scss', 112 | scripts: 'js' 113 | }, 114 | addToManifest: "true", 115 | importAbstracts: "true" 116 | }, 117 | postCssConfig: { 118 | plugins: { 119 | autoprefixer: {}, 120 | cssnano: {} 121 | } 122 | }, 123 | grunt: { 124 | configFile: 'gruntfile.js', 125 | devDependencies: [ 126 | "autoprefixer", 127 | "cssnano", 128 | "grunt", 129 | "grunt-postcss", 130 | "grunt-contrib-watch", 131 | "grunt-contrib-connect", 132 | "cross-env" 133 | ], 134 | lessDependencies: [ 135 | 'grunt-contrib-less' 136 | ], 137 | sassDependencies: [ 138 | 'grunt-sass', 139 | "node-sass" 140 | ], 141 | npmCommands: { 142 | "dev": "cross-env NODE_ENV=development grunt dev", 143 | "build": "cross-env NODE_ENV=production grunt build" 144 | }, 145 | configContents: `module.exports = function(grunt) { 146 | grunt.initConfig({ 147 | pkg: grunt.file.readJSON("package.json"), 148 | %%styleFormat%%: { 149 | dist: { 150 | files: { 151 | "dist/styles.css": "src/%%extension%%/styles.%%extension%%" 152 | } 153 | } 154 | }, 155 | watch: { 156 | css: { 157 | files: "**/*.%%extension%%", 158 | tasks: ["%%styleFormat%%", "postcss"], 159 | options: { 160 | livereload: true 161 | } 162 | } 163 | }, 164 | postcss: { 165 | options: { 166 | map: true, 167 | processors: [ 168 | require("autoprefixer")(), 169 | require("cssnano")() 170 | ] 171 | }, 172 | dist: { 173 | src: "dist/*.css" 174 | } 175 | }, 176 | connect: { 177 | server: { 178 | options: { 179 | port: 8000, 180 | hostname: "*", 181 | open: true, 182 | onCreateServer: function(server, connect, options) {} 183 | } 184 | } 185 | } 186 | }); 187 | grunt.loadNpmTasks("grunt-%%styleFormat%%"); 188 | grunt.loadNpmTasks("grunt-postcss"); 189 | grunt.loadNpmTasks("grunt-contrib-watch"); 190 | grunt.loadNpmTasks("grunt-contrib-connect"); 191 | grunt.registerTask("dev", ["%%styleFormat%%", "postcss", "connect", "watch"]); 192 | grunt.registerTask("build", ["%%styleFormat%%", "postcss"]); 193 | }; 194 | ` 195 | }, 196 | gulp: { 197 | configFile: 'gulpfile.js', 198 | devDependencies: [ 199 | "autoprefixer", 200 | "cssnano", 201 | "cross-env", 202 | "gulp", 203 | "gulp-postcss", 204 | "gulp-webserver", 205 | "gulp-sourcemaps" 206 | ], 207 | lessDependencies: [ 208 | 'gulp-less' 209 | ], 210 | sassDependencies: [ 211 | 'gulp-sass' 212 | ], 213 | npmCommands: { 214 | "dev": "cross-env NODE_ENV=development gulp dev", 215 | "build": "cross-env NODE_ENV=production gulp build" 216 | }, 217 | configContents: 218 | `'use strict'; 219 | 220 | const { src, watch, dest, parallel } = require("gulp"); 221 | const %%styleFormat%% = require("gulp-%%styleFormat%%"); 222 | const postcss = require("gulp-postcss"); 223 | const webserver = require("gulp-webserver"); 224 | const sourcemaps = require("gulp-sourcemaps"); 225 | 226 | function localServer() { 227 | return src(".").pipe( 228 | webserver({ 229 | livereload: true, 230 | directoryListing: false, 231 | open: true 232 | }) 233 | ); 234 | } 235 | 236 | function css() { 237 | return src("./src/%%extension%%/styles.%%extension%%") 238 | .pipe(sourcemaps.init()) 239 | .pipe(%%styleFormat%%().on("error", %%styleFormat%%.logError)) 240 | .pipe(postcss()) 241 | .pipe(sourcemaps.write("./")) 242 | .pipe(dest("./dist")); 243 | } 244 | 245 | exports.dev = function() { 246 | localServer(); 247 | css(); 248 | watch("./src/%%extension%%/**/*.%%extension%%", css); 249 | }; 250 | 251 | exports.build = function() { 252 | parallel(css); 253 | };` 254 | }, 255 | parcel: { 256 | configFile: null, 257 | devDependencies: [ 258 | "autoprefixer", 259 | "cssnano", 260 | "cross-env", 261 | ], 262 | lessDependencies: [ 263 | ], 264 | sassDependencies: [ 265 | ], 266 | npmCommands: { 267 | "dev": "cross-env NODE_ENV=development parcel index.html", 268 | "build": "cross-env NODE_ENV=production parcel build index.html -d ./dist" 269 | }, 270 | configContents: null 271 | }, 272 | webpack: { 273 | configFile: 'webpack.config.js', 274 | devDependencies: [ 275 | "autoprefixer", 276 | "css-loader", 277 | "style-loader", 278 | "cross-env", 279 | "mini-css-extract-plugin", 280 | "postcss-loader", 281 | "webpack", 282 | "webpack-cli", 283 | "webpack-dev-server", 284 | "cssnano", 285 | "clean-webpack-plugin", 286 | "html-webpack-plugin", 287 | ], 288 | lessDependencies: [ 289 | 'less-loader' 290 | ], 291 | sassDependencies: [ 292 | 'node-sass', 293 | 'sass-loader' 294 | ], 295 | npmCommands: { 296 | "dev": "cross-env NODE_ENV=development webpack-dev-server --progress --open --hot --mode development", 297 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules -p --mode production" 298 | }, 299 | configContents: 300 | `const path = require('path'); 301 | const {CleanWebpackPlugin} = require('clean-webpack-plugin'); 302 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 303 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 304 | const webpack = require('webpack'); 305 | 306 | module.exports = { 307 | entry: './src/scripts/main.js', 308 | output: { 309 | filename: 'scripts.js', 310 | path: path.resolve(__dirname, 'dist') 311 | }, 312 | devServer: { 313 | contentBase: path.resolve(__dirname, '.'), 314 | inline: true, 315 | hot: true 316 | }, 317 | module: { 318 | rules: [ 319 | { 320 | test: /\.(scss|sass)$/, 321 | use: [ 322 | MiniCssExtractPlugin.loader, 323 | 'css-loader', 324 | 'sass-loader', 325 | 'postcss-loader' 326 | ] 327 | } 328 | ] 329 | }, 330 | plugins: [ 331 | new CleanWebpackPlugin(), 332 | new MiniCssExtractPlugin({ 333 | filename: "styles.css" 334 | }), 335 | new HtmlWebpackPlugin({ 336 | filename: 'index.html', 337 | template: 'index.html' 338 | }), 339 | new webpack.NamedModulesPlugin(), 340 | new webpack.HotModuleReplacementPlugin() 341 | ] 342 | }` 343 | }, 344 | indexHtml: 345 | ` 346 | 347 | 348 | 349 | 350 | Clarion 351 | 352 | 353 | 354 | 355 |
356 |

Congratulations! You did it!

357 | tobias huzzah! 358 |

This page is intentionally ugly!

359 |

Create some styles and start having fun!

360 |
    361 |
  • Modify headings by creating a headings file:
    362 | clarion add element headings 363 |
  • 364 |
  • Modify lists by creating a lists file:
    365 | clarion add element lists 366 |
  • 367 |
368 |

Here is some sweet, sweet dummy text to play with

369 |

Bacon ipsum dolor amet ball tip hamburger adipisicing chicken prosciutto non. Shoulder venison quis, flank leberkas turducken dolor tenderloin nostrud. Ham strip steak swine boudin tempor. Shoulder doner mollit brisket. Cillum strip steak picanha kevin et culpa commodo lorem pastrami.

370 |

Bacon ipsum

371 |

Elit est ut prosciutto sausage spare ribs tenderloin pork loin cupidatat brisket dolore pancetta occaecat. Elit meatball leberkas burgdoggen ham hock beef ribs ut bresaola voluptate pork belly eu culpa t-bone esse pork loin. Quis corned beef minim eu velit excepteur. Quis consequat bacon corned beef boudin chicken anim sint labore kielbasa do ipsum sed. Frankfurter laborum turkey do brisket elit exercitation adipisicing doner irure jowl leberkas. Culpa flank kevin drumstick sunt porchetta kielbasa pancetta picanha ea dolor ad. Irure tongue in pork belly alcatra sirloin mollit reprehenderit tri-tip dolore.

372 |

Tri-tip Meatball Pork Belly

373 |

Id tri-tip meatball pork belly, mollit burgdoggen leberkas cupim. Do nostrud sirloin jerky capicola ham hock deserunt, spare ribs pork belly boudin culpa salami in duis. Biltong ut aute chuck nostrud drumstick short ribs et ham hock in lorem. Prosciutto exercitation salami in enim. Laborum shoulder ribeye kielbasa exercitation bacon officia ut alcatra rump pork turkey. Excepteur ut voluptate, qui labore est pork chop pastrami hamburger cupim laborum doner shank.

374 |
375 | 376 | 377 | 378 | ` 379 | } -------------------------------------------------------------------------------- /src/services/configService.ts: -------------------------------------------------------------------------------- 1 | import { prompt } from "inquirer"; 2 | import { IClarionConfig, projectData } from '../data/projectData'; 3 | import { IFileService, FileService } from "../services/fileService"; 4 | import { ILogService, LogService } from "./logService"; 5 | 6 | interface IConfig { 7 | paths: { 8 | styles: string, 9 | }, 10 | format: { 11 | styles: string, 12 | }, 13 | addToManifest: boolean, 14 | importAbstracts: boolean 15 | } 16 | 17 | export interface IConfigService { 18 | getConfigData(): any; 19 | updateConfigInfo(): void; 20 | } 21 | 22 | export class ConfigService implements IConfigService { 23 | _fileService: IFileService = new FileService(); 24 | _logger: ILogService = new LogService(); 25 | _configFileName = 'clarion-config.json'; 26 | 27 | getConfigData(): IClarionConfig { 28 | if (this._fileService.fileExists('./' + this._configFileName)) { 29 | let config = this._fileService.readFile('./' + this._configFileName); 30 | 31 | return JSON.parse(config); 32 | } 33 | 34 | return projectData.clarionConfig; 35 | } 36 | 37 | async updateConfigInfo() { 38 | let responses = await this.promptUserInput(); 39 | let config = this.updateConfigData(responses); 40 | this._fileService.saveFile(this._configFileName, JSON.stringify(config, null, '\t')); 41 | } 42 | 43 | updateConfigData(responses: any): IConfig { 44 | return { 45 | paths: { 46 | styles: responses.stylePath || './src', 47 | }, 48 | format: { 49 | styles: responses.styleFormat || 'scss', 50 | }, 51 | addToManifest: responses.addToManifest === true, 52 | importAbstracts: responses.importAbstracts === true 53 | }; 54 | } 55 | 56 | async promptUserInput() { 57 | let questions = [ 58 | { 59 | type: "input", 60 | name: "stylePath", 61 | message: "What is the path to your styles directory?", 62 | default: "./src", 63 | validate: value => value.length > 0 || "This questions is required." 64 | }, 65 | { 66 | type: "input", 67 | name: "styleFormat", 68 | message: "What format are your styles in?", 69 | default: "scss", 70 | validate: value => value.length > 0 || "Project Name is required." 71 | }, 72 | { 73 | type: "confirm", 74 | name: "addToManifest", 75 | message: "Would you like a reference to your new style sheets to automatically be added to the directory manifest (it's very convenient)?", 76 | default: "y" 77 | }, 78 | { 79 | type: "confirm", 80 | name: "importAbstracts", 81 | message: "Would you like a reference to your abstracts to automatically be added to your new style sheets (again, it's very convenient)?", 82 | default: "y" 83 | } 84 | ]; 85 | 86 | return await prompt(questions);; 87 | } 88 | } -------------------------------------------------------------------------------- /src/services/directoryService.ts: -------------------------------------------------------------------------------- 1 | import { prompt } from "inquirer"; 2 | import * as path from "path"; 3 | import * as fs from "fs"; 4 | import { projectData} from '../data/projectData'; 5 | import { ILogService, LogService } from "./logService"; 6 | import { IConfigService, ConfigService } from "./configService"; 7 | 8 | 9 | export interface IDirectoryService { 10 | createDirectory(pathName: string): void; 11 | directoryExists(directoryName: string): boolean; 12 | findDirectoryByName(directoryName: string): string; 13 | findDirectory(directory: string): string; 14 | findStyleRootDirectory(): string; 15 | getAllStyleDirectories(): string[]; 16 | promptForMissingDirectory(): Promise; 17 | } 18 | 19 | export class DirectoryService implements IDirectoryService { 20 | _logger: ILogService = new LogService(); 21 | _configService: IConfigService = new ConfigService(); 22 | _config = this._configService.getConfigData(); 23 | 24 | createDirectory(pathName: string): void { 25 | try { 26 | pathName = pathName.replace('//', '/'); 27 | fs.mkdirSync(pathName); 28 | this._logger.success(`Created directory: ${pathName}`); 29 | } catch (error) { 30 | this._logger.error(`There was an error creating this directory: ${pathName} \n${error}`); 31 | } 32 | } 33 | 34 | directoryExists(directoryName: string): boolean { 35 | try { 36 | return fs.statSync(directoryName).isDirectory(); 37 | } catch (err) { 38 | return false; 39 | } 40 | } 41 | 42 | promptForMissingDirectory(): Promise { 43 | let directories = this.getAllStyleDirectories(); 44 | let question = { 45 | type: "list", 46 | name: "directory", 47 | message: "We could not find the directory you entered. Are you looking for one of these?", 48 | choices: directories 49 | }; 50 | 51 | if (directories.length === 0) { 52 | this._logger.warning("Please specify a directory."); 53 | } 54 | else { 55 | return prompt(question).then(answers => answers.directory); 56 | } 57 | } 58 | 59 | findDirectoryByName(directoryName: string): string { 60 | let directory: string; 61 | let directories = this.getAllStyleDirectories(); 62 | 63 | if (directoryName) { 64 | directory = directories.find(x => { 65 | return x.toLowerCase().includes(directoryName.toLowerCase()); 66 | }); 67 | } 68 | 69 | return directory; 70 | } 71 | 72 | findDirectory(directory: string): string { 73 | let pathToDirectory = ''; 74 | 75 | if (this.directoryExists(`./${directory}`)) { 76 | pathToDirectory = `./${directory}`; 77 | } else { 78 | projectData.styleTypes.forEach(x => { 79 | let proposedPath = path.resolve(this.findStyleRootDirectory(), directory); 80 | if (this.directoryExists(proposedPath)) { 81 | pathToDirectory = proposedPath; 82 | } 83 | }); 84 | } 85 | 86 | return pathToDirectory; 87 | } 88 | 89 | getAllStyleDirectories(): string[] { 90 | let config = this._configService.getConfigData(); 91 | let stylePath = this.findStyleRootDirectory(); 92 | let directories = this.getDirectoriesInDirectory(stylePath); 93 | 94 | return directories; 95 | } 96 | 97 | findStyleRootDirectory(): string { 98 | let stylesDirectory = ''; 99 | 100 | projectData.styleTypes.forEach(type => { 101 | let proposedPath = path.resolve('./', this._config.paths.styles, type); 102 | if (this.directoryExists(proposedPath)) { 103 | stylesDirectory = proposedPath; 104 | } 105 | }); 106 | 107 | return stylesDirectory; 108 | } 109 | 110 | getDirectoriesInDirectory(path): string[] { 111 | return fs.readdirSync(path).filter(function (file) { 112 | return fs.statSync(path+'/'+file).isDirectory(); 113 | }); 114 | } 115 | } -------------------------------------------------------------------------------- /src/services/fileService.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | import * as fs from "fs"; 3 | import * as program from "commander"; 4 | import { ILogService, LogService } from "./logService"; 5 | 6 | export interface IFileService { 7 | saveFile(destination: string, content: string): void; 8 | fileExists(fileName: string): boolean; 9 | getManifestFile(filePath: string): string; 10 | getFileExtension(directory: string): string; 11 | removeFile(filePath: string): void; 12 | readFile(filePath: string): string; 13 | getStyleFormat(extension: string): string; 14 | addFileToManifest(fileName: string, manifestFile: string, sort: boolean): void; 15 | removeFileFromManifest(fileName: string, manifestFile: string): void; 16 | getImportExtension(extension: string): string; 17 | } 18 | 19 | export class FileService implements IFileService { 20 | _logger: ILogService = new LogService(); 21 | 22 | saveFile(destination: string, content: string): void { 23 | try { 24 | destination = destination.replace('//', '/'); 25 | fs.writeFileSync(destination, content); 26 | this._logger.success(`Saved file: ${destination}`); 27 | } catch (error) { 28 | this._logger.error(`There was an error saving this file: ${destination} \n${error}`); 29 | } 30 | } 31 | 32 | fileExists(fileName: string): boolean { 33 | try { 34 | return fs.statSync(fileName).isFile(); 35 | } catch (err) { 36 | return false; 37 | } 38 | } 39 | 40 | 41 | getManifestFile(filePath: string): string { 42 | if (!filePath) return undefined; 43 | 44 | let manifestFile; 45 | 46 | fs.readdirSync(filePath).forEach(file => { 47 | if (file.startsWith('index') || file.startsWith('style')) 48 | manifestFile = file; 49 | }); 50 | 51 | return manifestFile; 52 | } 53 | 54 | getFileExtension(directory: string): string { 55 | switch (true) { 56 | case program.sass: 57 | return '.sass'; 58 | case program.scss: 59 | return '.scss'; 60 | case program.less: 61 | return '.less'; 62 | default: 63 | return this.getManifestExtension(directory); 64 | } 65 | } 66 | 67 | getManifestExtension(directory: string): string { 68 | let manifest = this.getManifestFile(directory); 69 | return manifest ? path.extname(manifest).replace('.', '') : 'scss'; 70 | } 71 | 72 | addFileToManifest(fileName: string, manifestFile: string, sort: boolean): void { 73 | if(sort) { 74 | var data = fs.readFileSync(manifestFile, 'utf8'); 75 | let importStatements = data.split('\n').filter(String); 76 | importStatements.push(fileName); 77 | importStatements.sort(); 78 | this.saveFile(manifestFile, importStatements.join('\n')); 79 | } else { 80 | fs.appendFileSync(manifestFile, `@import '${fileName}';\n`); 81 | } 82 | 83 | this._logger.success(`Saved file: ${fileName} was added to the manifest.`); 84 | } 85 | 86 | removeFileFromManifest(fileName: string, manifestFile: string): void { 87 | let importStatements = this.readFile(manifestFile).split('\n'); 88 | let fileIndex = importStatements.indexOf(`@import '${fileName}';`); 89 | 90 | if (fileIndex < 0) { 91 | this._logger.warning('File to be removed was not found in your manifest.'); 92 | } else { 93 | importStatements.splice(fileIndex, 1); 94 | let formattedImportStatements = importStatements.join('\n'); 95 | this.saveFile(manifestFile, formattedImportStatements); 96 | } 97 | } 98 | 99 | removeFile(filePath: string): void { 100 | try { 101 | if (this.fileExists(filePath)) { 102 | fs.unlinkSync(filePath); 103 | this._logger.success(`File removed: ${filePath}`); 104 | } else { 105 | this._logger.warning(filePath + ' was not found'); 106 | } 107 | } catch (error) { 108 | this._logger.error(`There was an error removing this file: ${filePath} \n${error}`); 109 | } 110 | } 111 | 112 | 113 | readFile(filePath: string): string { 114 | return this.fileExists(filePath) ? fs.readFileSync(filePath).toString() : ''; 115 | } 116 | 117 | getStyleFormat(extension: string): string { 118 | return extension.replace('.', '') === 'less' ? 'less' : 'sass'; 119 | } 120 | 121 | getImportExtension(extension: string): string { 122 | return `${extension !== 'sass' && extension !== 'scss' ? '.' + extension : ''}${extension === 'sass' ? '\'' : '\';'}`; 123 | } 124 | } -------------------------------------------------------------------------------- /src/services/logService.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | 3 | export interface ILogService { 4 | log(message: string): void; 5 | error(message: string): void; 6 | warning(message: string): void; 7 | success(message: string): void; 8 | info(message: string): void; 9 | } 10 | 11 | export class LogService implements ILogService { 12 | log(message: string): void { 13 | console.log(message); 14 | } 15 | 16 | error(message: string): void { 17 | console.log(chalk.red(message)); 18 | } 19 | 20 | warning(message: string): void { 21 | console.log(chalk.yellow(message)); 22 | } 23 | 24 | success(message: string): void { 25 | console.log(chalk.green(message)); 26 | } 27 | 28 | info(message: string): void { 29 | console.log(chalk.blue(message)); 30 | } 31 | } -------------------------------------------------------------------------------- /src/services/projectService.ts: -------------------------------------------------------------------------------- 1 | import { IFileService, FileService } from "../services/fileService"; 2 | import { IProjectTypeData, projectData } from '../data/projectData'; 3 | import { INewProjectInfo, newProject } from "../data/newProject"; 4 | 5 | export interface IProjectData { 6 | devDependencies: string[]; 7 | projectCommands: object; 8 | } 9 | 10 | export interface IProjectDataService { 11 | getProjectData(projectName: string): IProjectData; 12 | getHtmlTemplate(cssPath: string, jsPath: string): string; 13 | } 14 | 15 | export class ProjectDataService implements IProjectDataService { 16 | _fileService: IFileService = new FileService(); 17 | _projectTypeData: IProjectTypeData; 18 | 19 | constructor(private _pipeline: string, private _styleFormat: string) { 20 | this._projectTypeData = this.getProjectTypeData(); 21 | } 22 | 23 | getProjectData(projectName: string): IProjectData { 24 | if (this._pipeline !== newProject.options.pipeline.parcel) 25 | this.createBundleConfigurationFile(projectName); 26 | 27 | if (this._pipeline !== newProject.options.pipeline.grunt) 28 | this.createPostCssConfig(projectName); 29 | 30 | return { 31 | devDependencies: this.getProjectDependencies(), 32 | projectCommands: this._projectTypeData.npmCommands 33 | }; 34 | } 35 | 36 | getHtmlTemplate(cssPath: string, jsPath: string): string { 37 | return projectData.indexHtml 38 | .replace(/%%cssDir%%/g, cssPath) 39 | .replace(/%%jsDir%%/g, jsPath); 40 | } 41 | 42 | createBundleConfigurationFile(projectName: string): void { 43 | let rootPath = './' + projectName; 44 | this._fileService.saveFile(rootPath + '/' + this._projectTypeData.configFile, this.getConfigFileContents()); 45 | } 46 | 47 | createPostCssConfig(projectName: string): void { 48 | let content = 'module.exports = ' + JSON.stringify(projectData.postCssConfig, null, '\t'); 49 | this._fileService.saveFile(`./${projectName}/postcss.config.js`, content); 50 | } 51 | 52 | getConfigFileContents(): string { 53 | let extension = this._fileService.getFileExtension(null).replace('.', ''); 54 | let styleFormat = extension === 'less' ? 'less' : 'sass'; 55 | 56 | return this.updateConfigTemplateWithProjectData(extension, styleFormat); 57 | } 58 | 59 | updateConfigTemplateWithProjectData(extension: string, styleFormat: string) { 60 | let contents: string = this._projectTypeData.configContents 61 | .replace(/%%styleFormat%%/g, styleFormat) 62 | .replace(/%%extension%%/g, extension); 63 | 64 | if (this._pipeline === 'Grunt') { 65 | if (styleFormat === 'less') { 66 | contents = contents.replace(/grunt-less/g, 'grunt-contrib-less'); 67 | } 68 | 69 | if (styleFormat === 'sass') { 70 | contents = 'const sass = require("node-sass");\n\n' + contents; 71 | contents = contents.replace(/sass: {\n/g, 'sass: {\n\t\t\toptions: {\n\t\t\t\timplementation: sass,\n\t\t\t\tsourceMap: true\n\t\t\t},\n'); 72 | } 73 | } 74 | 75 | if (this._pipeline === 'Webpack') { 76 | if (styleFormat === 'less') { 77 | contents = contents.replace('(scss|sass)', 'less'); 78 | } 79 | } 80 | 81 | return contents; 82 | } 83 | 84 | getProjectDependencies(): string[] { 85 | let dependencies = this._projectTypeData.devDependencies; 86 | return dependencies.concat(this._styleFormat === newProject.options.styleFormat.less ? this._projectTypeData.lessDependencies : this._projectTypeData.sassDependencies); 87 | } 88 | 89 | getProjectTypeData() { 90 | switch (true) { 91 | case this._pipeline === newProject.options.pipeline.grunt: 92 | return projectData.grunt; 93 | case this._pipeline === newProject.options.pipeline.gulp: 94 | return projectData.gulp; 95 | case this._pipeline === newProject.options.pipeline.parcel: 96 | return projectData.parcel; 97 | default: 98 | return projectData.webpack; 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /src/services/shellService.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import { ILogService, LogService } from "./logService"; 3 | 4 | export interface IShellService { 5 | executeCommand(command: string); 6 | installNPMDependencies(projectName:string, packages: string[], devDependency:boolean); 7 | } 8 | 9 | export class ShellService implements IShellService { 10 | _logger: ILogService = new LogService(); 11 | 12 | executeCommand(command: string) { 13 | try { 14 | this._logger.info('Installing your dependencies... (This may take a minute.)') 15 | execSync(command); 16 | } catch (error) { 17 | this._logger.error('There was a problem installing your dependencies.'); 18 | this._logger.error(error); 19 | return 20 | } 21 | 22 | this._logger.warning(`Your command '${command}' was successfully executed.`); 23 | } 24 | 25 | installNPMDependencies(projectName:string, packages: string[], devDependency:boolean = false) { 26 | let dependencyType = devDependency ? "-D" : "-S"; 27 | let command = `cd ./${projectName} && npm install ${dependencyType} ${packages.join(' ')}`; 28 | 29 | this.executeCommand(command); 30 | } 31 | } -------------------------------------------------------------------------------- /tests/commands/add.spec.ts: -------------------------------------------------------------------------------- 1 | import { should, expect } from 'chai'; 2 | import { IAdd, Add } from '../../src/commands/add'; 3 | 4 | describe('New Project Services', () => { 5 | describe('getNewFile()', () => { 6 | it('it should convert new file path to proper file format', () => { 7 | // arrange 8 | let fileName = 'test/test/file'; 9 | let extension = 'sass'; 10 | let sut = new Add(); 11 | 12 | // act 13 | let result = sut.getNewFile(fileName, extension); 14 | 15 | // assert 16 | expect(result).to.equal('test/test/_file.sass'); 17 | }); 18 | }); 19 | 20 | describe('getPathToRoot()', () => { 21 | it('it should return the path to the root directory', () => { 22 | // arrange 23 | let fileName = 'test/test/file'; 24 | let sut = new Add(); 25 | 26 | // act 27 | let result = sut.getPathToRoot(fileName); 28 | 29 | // assert 30 | expect(result).to.equal('../../../'); 31 | }); 32 | }); 33 | }); -------------------------------------------------------------------------------- /tests/commands/new.spec.ts: -------------------------------------------------------------------------------- 1 | import { should, expect } from 'chai'; 2 | import { INewProject, NewProject } from '../../src/commands/new'; 3 | 4 | describe('New Project Services', () => { 5 | describe('convertToKebabCase()', () => { 6 | it('it should convert string to kebab case', () => { 7 | // Arrange 8 | let sut = new NewProject(); 9 | let text = 'MyTestProjectName'; 10 | let expectedResult = 'my-test-project-name'; 11 | 12 | // Act 13 | let result = sut.convertToKebabCase(text); 14 | 15 | // Assert 16 | expect(result).is.equal(expectedResult); 17 | }); 18 | }); 19 | 20 | // describe('', ()=> { 21 | 22 | // }); 23 | }); -------------------------------------------------------------------------------- /tests/services/configService.spec.ts: -------------------------------------------------------------------------------- 1 | import { should, expect } from 'chai'; 2 | import { ConfigService } from '../../src/services/configService'; 3 | 4 | describe('Config Service', () => { 5 | describe('updateConfigData()', () => { 6 | it('it should return an updated config object', () => { 7 | // arrange 8 | let sut = new ConfigService(); 9 | let response = { 10 | stylePath: '/src/test', 11 | styleFormat: 'sass', 12 | addToManifest: false, 13 | importAbstracts: true 14 | }; 15 | let expectedResult = { 16 | paths: { 17 | styles: '/src/test', 18 | }, 19 | format: { 20 | styles: 'sass', 21 | }, 22 | addToManifest: false, 23 | importAbstracts: true 24 | }; 25 | 26 | // act 27 | let config = sut.updateConfigData(response); 28 | 29 | // assert 30 | expect(config).to.deep.equal(expectedResult); 31 | }); 32 | }); 33 | }); -------------------------------------------------------------------------------- /tests/services/projectDataService.spec.ts: -------------------------------------------------------------------------------- 1 | import { should, expect } from 'chai'; 2 | import { IProjectTypeData, projectData } from '../../src/data/projectData'; 3 | import { INewProjectInfo, newProject } from "../../src/data/newProject"; 4 | import { IProjectDataService, ProjectDataService } from '../../src/services/projectService'; 5 | 6 | describe('Project Data Service', () => { 7 | describe('getHtmlTemplate()', () => { 8 | it('it should replace template strings with specified file paths', () => { 9 | // arrange 10 | let sut = new ProjectDataService('grunt', 'scss'); 11 | 12 | // act 13 | let htmlTemplate = sut.getHtmlTemplate('/dist/styles.css', '/dist/scripts.js'); 14 | 15 | // assert 16 | expect(htmlTemplate.includes('/dist/styles.css')).to.be.true; 17 | expect(htmlTemplate.includes('/dist/scripts.js')).to.be.true; 18 | }); 19 | }); 20 | 21 | describe('updateConfigTemplateWithProjectData()', () => { 22 | it('it should update Grunt config with "grunt-contrib-less"', () => { 23 | // arrange 24 | let sut = new ProjectDataService('Grunt', 'less'); 25 | 26 | // act 27 | let gruntConfig = sut.updateConfigTemplateWithProjectData('less', 'less'); 28 | 29 | // assert 30 | expect(gruntConfig.includes('grunt-contrib-less')).to.be.true; 31 | }); 32 | 33 | it('it should import "node-sass" library into Grunt config', () => { 34 | // arrange 35 | let sut = new ProjectDataService('Grunt', 'sass'); 36 | 37 | // act 38 | let gruntConfig = sut.updateConfigTemplateWithProjectData('scss', 'sass'); 39 | 40 | // assert 41 | expect(gruntConfig.includes('const sass = require("node-sass");')).to.be.true; 42 | }); 43 | 44 | it('it should update Grunt config to look for files in the "SCSS" format', () => { 45 | // arrange 46 | let sut = new ProjectDataService('Grunt', 'sass'); 47 | 48 | // act 49 | let gruntConfig = sut.updateConfigTemplateWithProjectData('scss', 'sass'); 50 | 51 | // assert 52 | expect(gruntConfig.includes('src/scss/styles.scss')).to.be.true; 53 | }); 54 | 55 | it('it should update Webpack config to look for files in the "LESS" format', () => { 56 | // arrange 57 | let sut = new ProjectDataService('Webpack', 'less'); 58 | 59 | // act 60 | let webpackConfig = sut.updateConfigTemplateWithProjectData('less', 'less'); 61 | 62 | // assert 63 | expect(webpackConfig.includes('.less$')).to.be.true; 64 | }); 65 | }); 66 | 67 | describe('getProjectDependencies()', () => { 68 | it('it should return an array of "sass" dependencies for Grunt', () => { 69 | // arrange 70 | let sut = new ProjectDataService('Grunt', 'SCSS'); 71 | 72 | // act 73 | let sassDependencies = sut.getProjectDependencies(); 74 | 75 | // assert 76 | expect(sassDependencies.some(x => x === 'grunt-sass')).to.be.true; 77 | }); 78 | }); 79 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "target": "es6", 6 | "noImplicitAny": false, 7 | "sourceMap": false, 8 | "outDir": "./lib", 9 | "newLine": "LF" 10 | }, 11 | "include": [ 12 | "src/**/*" 13 | ], 14 | "exclude": [ 15 | "node_modules", 16 | "**/*.spec.ts" 17 | ] 18 | } --------------------------------------------------------------------------------