├── .eslintrc.js ├── .github └── workflows │ └── npm-publish-github-packages.yml ├── .gitignore ├── .npmrc ├── .prettierrc.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _config.yml ├── bin └── gloria ├── build └── .gitkeep ├── docs ├── commands │ ├── collect.md │ └── copy.md ├── index.draft.md ├── index.md └── tests.html ├── layouts └── tailwind │ ├── blog.layout.md │ ├── index.layout.md │ ├── index.page.draft.md │ └── main.css ├── lib ├── bin │ ├── commands │ │ ├── build.js │ │ ├── collect.js │ │ ├── copy.js │ │ ├── css.tailwind.js │ │ ├── extract.js │ │ ├── prebuild.js │ │ ├── setup.js │ │ └── write.js │ └── gloria.js ├── build │ ├── handlebars-helpers.js │ └── render.js ├── commands │ ├── build.js │ ├── collect.js │ ├── copy.js │ ├── css.tailwind.js │ ├── extract.js │ ├── init.js │ ├── migrate.js │ ├── new.js │ ├── new │ │ ├── create-content.js │ │ └── meta.js │ ├── prebuild.js │ ├── serve.js │ ├── setup.js │ └── write.html.js ├── core │ ├── project.config.js │ └── project.js ├── utils │ ├── compilers.js │ ├── configstore.js │ ├── filters.js │ ├── fs.js │ ├── handlebars.js │ ├── logger.js │ └── meta.js └── version.js ├── logs └── .gitkeep ├── package.json ├── public ├── CNAME └── logo.png ├── test ├── commands │ ├── build.spec.js │ ├── index.spec.js │ ├── init.spec.js │ ├── new.spec.js │ └── serve.spec.js ├── test.spec.js └── utils │ └── logger.spec.js ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint'], 4 | extends: [ 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:prettier/recommended', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish-github-packages.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - run: yarn install 19 | - run: npm test 20 | 21 | publish-gpr: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | permissions: 25 | contents: read 26 | packages: write 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: 16 32 | registry-url: https://npm.pkg.github.com/ 33 | - run: yarn install 34 | - run: npm publish 35 | env: 36 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.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 | # node-waf configuration 27 | .lock-wscript 28 | 29 | # Compiled binary addons (http://nodejs.org/api/addons.html) 30 | build/Release 31 | 32 | # Dependency directories 33 | node_modules 34 | jspm_packages 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional eslint cache 40 | .eslintcache 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | 45 | # Output of 'npm pack' 46 | *.tgz 47 | 48 | # Yarn Integrity file 49 | .yarn-integrity 50 | 51 | # files that are generated by the tests 52 | sample 53 | 54 | # VSCode metadata 55 | .vscode 56 | 57 | #Jetbrains metadata 58 | .idea/ 59 | 60 | # Folder to ignore for contributors 61 | _notes 62 | node_modules 63 | build-logs/* 64 | build/ 65 | cli.js -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @gloriajs:registry=https://npm.pkg.github.com -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # NEXTVERSION 2 | 3 | # 2.2.3 4 | - Stopped collecting files to be copied to avoid certain bugs 5 | 6 | # 2.2.2 7 | - Added basic handlebars filters for easy navigation 8 | 9 | # 2.2.1 10 | - Creating setup command to generate default _config.yml 11 | 12 | # 2.2.0-a 13 | - Fixing copy command that was corrupting images 14 | 15 | # 2.1.8 16 | - Wrote github action to publish github package 17 | 18 | # 2.1.7 19 | - Write command created 20 | 21 | # 2.1.6 22 | - Copying files that are meant to be copied before running CSS 23 | - Fixed sample files 24 | 25 | # 2.1.5 26 | - Tailwind command 27 | - Writes to file abstracted 28 | 29 | # 2.1.4 30 | - Build command correctly writes temp files to disk 31 | 32 | # 2.1.3 33 | - Build command works with md and handlebars 34 | - Partial typescript support 35 | - Added the prebuild commandd 36 | - Added the build command 37 | 38 | # 2.1.2 39 | 40 | - Added the extract command 41 | 42 | # 2.1.1 43 | 44 | - Added the copy command 45 | 46 | # 2.1 47 | 48 | - Rewriting commands to be more modular 49 | 50 | # 2 51 | 52 | - Dropped support for sass 53 | 54 | # 0.18.0 55 | 56 | - added a meta tag for responsive in default template 57 | - using UTC for moment when parsing dates 58 | 59 | # 0.17.0 60 | 61 | - Reveresed 0.16.3 62 | 63 | # 0.16.3 64 | 65 | - Fixed error where pages data was duplicated 66 | 67 | # 0.16.2 68 | 69 | - Fixed error with including data and pages to for content 70 | 71 | # 0.16.1 72 | 73 | - Added direction to each with sort 74 | 75 | # 0.16.0 76 | 77 | - Adds a timestamp property to pages data 78 | - Created a method eachWithSort for a handlebar helper 79 | 80 | # 0.15.1 81 | 82 | - Added formatDate helper to handlebars helpers 83 | 84 | # 0.15.0 85 | 86 | - adding filename to the data available for page 87 | - Use frontmatter to customize layouts and includes (Issue #13) 88 | 89 | # 0.14.0 90 | 91 | - Not clearing destination folder by default when serving 92 | 93 | #0.13.0 94 | 95 | - Reading files from the theme folder and including them in the build process 96 | - Allowing users to have different themes, and include the correct files from the theme selected 97 | - Fixed bug occuring to windows users when building/serving #97 98 | 99 | #0.12.0 100 | 101 | - Using an utils file for logger 102 | - Using config store to save configuration information for the module 103 | - Suggesting users to download the latest version if available 104 | 105 | #0.11.0 106 | 107 | - Removed unused dependencies 108 | - Added code of conduct 109 | - Replaced jscs with eslint 110 | 111 | #0.10.0 112 | 113 | - Fixed bug that prevent init from working on windows 114 | - Added build stats 115 | - Watches files and builds during serve 116 | - Allows to add additional data as json files, in the \_data folder. 117 | The data can be used in the templates as {{data.'jsonFileName'}} 118 | 119 | #0.9.6 120 | 121 | - Fix issue #59, every post has a correct url, even when it has categories 122 | 123 | #0.9.5 124 | 125 | - Performance improvements in the build command 126 | - Fixed bugs with prompts 127 | - Increased test coverage 128 | - Allows to chose a layout from the provided ones 129 | - Added support for \_stylus files 130 | - Added a `watch` flag for serve command 131 | 132 | #0.9.4 133 | 134 | - Added the `new` command to make it easier adding new content 135 | 136 | #0.9.3 137 | 138 | - Refactored the render file, for the build command 139 | 140 | #0.9.2 141 | 142 | - Added performance fixes 143 | 144 | #0.9.1 145 | #0.9.0 146 | 147 | ## Added the option to include posts 148 | 149 | - Posts can be included in a folder \_posts, they will be rendered using the 150 | layout `post.html`. 151 | 152 | - There's a property named posts, that can be used to get a lists of posts 153 | to lists them. 154 | 155 | - The bootstrap layout, includes examples on how to use them. 156 | 157 | **@todo**: - Prevent errors when templates don't exists. - Create helpers to allow nesting layout/template - Create new command 158 | 159 | #0.8.3 160 | 161 | - Fixed typo in package.json 162 | 163 | #0.8.2 164 | 165 | - Replaced the markdown engine with [marked](https://github.com/chjj/marked). 166 | 167 | #0.8.1 168 | 169 | - Simple bug fixes 170 | 171 | #0.8.0 172 | **Simple support for sass** 173 | 174 | - Using node-sass, it will look for main.scss files in a \_sass folder. 175 | It will then output the compiled files into {dest}/sass/ 176 | 177 | **@todo**: allow to customize the source and destination of sass files. 178 | 179 | #0.7.0 180 | **Added a migrate command** 181 | 182 | - Allows to migrate a jekyll site to gloria. Poorly, 183 | as described [here](https://github.com/gloriajs/gloria/issues/15). 184 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Our open source community is a group of people committed to developing awesome software that Works. 2 | Every other concern is subordinate to this goal. 3 | We recognize that there are certain kinds of childish behavior that are unwelcome in our community. 4 | We respond to such behavior by generally ignoring and/or—in extreme cases—making sport of, individuals who engage in such behavior. 5 | We accept everyone's contributions, we don't care about your personal life! In fact, we won't bring it up, or ask. 6 | Those conversations have no place in the repo, issues or community spaces unless it refers to accesbility, inclusion or other important features of the tool. 7 | Repeat offenders will get mocked and banned. 8 | If you need to complain about someone's behavior or report something we suggest to message the maintainers. 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | Contributions in the form of ideas, designs, and code are always welcome no matter how large or small. 4 | If you want to help but are not sure where to start, you can check out our project's 5 | [Issues](https://github.com/gloriajs/gloria/issues). 6 | 7 | For contributing instructions look at this page: 8 | [gloria.js.org/contributing/](https://gloria.js.org//contributing/). 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy of 2 | this software and associated documentation files (the "Software"), to deal in 3 | the Software with specific restrictions FOR the purposes of satire, evil, or 4 | advancing evil, including but not limited to: 5 | 6 | Horatian, Juvenalian, Menippean, Irony, Hyperbole, Understatement, Allegory, 7 | bringing down the American Empire and ending the War on Drugs. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gloria - static site generator 2 | 3 | _gloria is spanish for glory, also the name of my mom and the name was available in npm_ 4 | 5 | **V2 is deprecating lots of old functions, make sure to read the migrating docs.** We're also slowly moving into typescript, and while I think it should work very well for your projects, legally I need to remind you that this is a satire of other projects. 6 | 7 | It currently supports having a content management system using MD files and tailwind CSS, with some bugs, but the main functionality and philosophy have been accomplished. 8 | 9 | This project aims to be a substitute for [jekyll](https://jekyllrb.com/), to help you create static websites without depending on ruby. 10 | 11 | Because of its ease of use, extensibility and support for md files, is also great for creating websites out of documentatation that lives in a source code. 12 | 13 | ## Community 14 | 15 | We're going to have a discord or something some day. 16 | 17 | ## Quickstart 18 | 19 | Gloria aims to be the easiest simple static web generator there is, get yourself a good looking website for your portfolio, projects, community or hobbies in less than 5 minutes. 20 | 21 | ### Web 22 | 23 | Fork the quick start theme repo and modify `_config.yml`, add and change pages, and publish to your [github pages](https://pages.github.com/) for free. 24 | 25 | ### Locally 26 | 27 | Add gloria as a dependency `yarn add gloriajs` and follow build and deploy instructions. We're using a `yarn.lock`. 28 | 29 | ### Custom domains 30 | 31 | Github pages offers support for custom domains, follow the instructions on [their docs](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site). 32 | 33 | [JS.org](https://js.org/) Offers free domains to open source 34 | 35 | ## Installation 36 | 37 | For more information check our [website](https://gloria.js.org). 38 | 39 | ### Npm Registry 40 | 41 | To install from the npm registry run `yarn add gloriajs` or `npm install gloriajs`. 42 | 43 | ### Github packages 44 | 45 | Add the `@gloriajs` scope to your `.npmrc` or your project before installing. If added globally it only needs to be done once, but is better to add it on each project so your collaborators don't have to do it too before getting started. I can't remember my npm login, but once I figure it out we can also publish there I guess. 46 | 47 | ``` 48 | @gloriajs:registry=npm.pkg.github.com 49 | ``` 50 | 51 | Install by 52 | 53 | ``` 54 | npm i @gloriajs/gloria 55 | ``` 56 | 57 | ## Usage 58 | 59 | Now gloria is globally installed in your computer, and you can run commands like `gloria --version` to retrieve the version. Or if installed per project you can use npx. 60 | 61 | ### Locally 62 | 63 | Clone this repo and point to it from another one adding this line to your dependencies: 64 | 65 | ``` 66 | "gloria-local": "./node_modules/gloria-local/bin/gloria" 67 | ``` 68 | 69 | Then you can add scripts to your `package.json` as: 70 | 71 | ``` 72 | "gloria-local": "./node_modules/gloria-local/bin/gloria" 73 | ``` 74 | 75 | And use `gloria` from that folder like: 76 | 77 | ``` 78 | npm run gloria-local -- --version 79 | ``` 80 | 81 | ## Commands 82 | 83 | - `collect` - `[output]` traverses the include-paths and saves information about the available files 84 | - `copy` - `[path] [dest]` copies the static files from source paths to dest folders 85 | - `extract` - `[dest]` finds metadata and frontmatter information from every collected file 86 | - `prebuild` - `[dest]` creates the output placeholders so they can be processed 87 | - `build` - `[path] [dest]` interpolates the content into the layout 88 | - `css:tailwind` - `[path] [dest]` runs tailwind in the html output 89 | - `scripts` - `[path] [dest]` @TODO processes scripts 90 | - `write` - `[dest]` writes production version to disk 91 | - `cleanup` - `[dest]` @TODO: cleans up temp files after a successful build 92 | - `version` - ` ` returns the current package version 93 | 94 | The commands all should work on their own, and the ones that combine multiple steps do it very nicely by using promises, we pass the project around, and store and read from its properties, to create new commands or amplify existing ones, for plugins and templates, review the data structure. 95 | 96 | ## Development and contributing 97 | 98 | Refer to our [Contributing page](CONTRIBUTING.md) and local installation instructions. 99 | 100 | **Here's our @TODO in no particular order for inspiration:** 101 | 102 | - [x] Write the previous commands to restore basic features 103 | - [x] Fix build so it interpolates the parsed markdown 104 | - [x] Recreate documentation website 105 | - [ ] Move to typescript 106 | - [ ] Delete unused files 107 | - [ ] Data sources 108 | - [x] interpolations 109 | - [ ] bug: only excluding first condition 110 | 111 | ## Data structure 112 | 113 | The current data structure is something like: 114 | 115 | ``` 116 | const project = { 117 | prebuilt: { 118 | html, 119 | css, 120 | }, 121 | }; 122 | ``` 123 | 124 | Once we're in typescript it will be much easier to see by referencing the interfaces. 125 | 126 | ## CSS 127 | 128 | I'm using tailwind coz it seemed easy, should be quite extendable and possible to include other css pre-processors, or change the configuration flag. 129 | 130 | ## Tests 131 | 132 | I removed this line `./node_modules/.bin/mocha --reporter spec` and need to add it later, but now that we're using TS and new testing tools have come out, perhaps we don't need mocha. 133 | 134 | ## Troubleshooting 135 | 136 | Try on your own or open an [issue](https://github.com/gloriajs/gloria/issues) describing your problem. 137 | 138 | Go for a walk and wait. 139 | 140 | ## google analytics 141 | 142 | I recommend not tracking vanity metrics when you could be going for a walk, but if you must, add it to the layout or wait for a way to append_to_head using metadata from a page. 143 | 144 | ## Slava Ukraini 145 | 146 | Gloria and Slava are the same word. A friend named Slava told me. 147 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # sample configuration 2 | # is also used when testing commands on this folder 3 | name: Gloria JS 4 | tagline: 'A simple static website generator' 5 | baseurl: 'https://gloriajs.github.io' 6 | author: 'daveed silva' 7 | description: 'Documentation for the tool gloriajs' 8 | dest: build 9 | theme: 10 | - layouts/tailwind 11 | css: tailwind 12 | include: 13 | - docs 14 | exclude: 15 | - '.draft.md' 16 | copy: 17 | - public 18 | -------------------------------------------------------------------------------- /bin/gloria: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/bin/gloria'); 3 | -------------------------------------------------------------------------------- /build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gloriajs/gloria/be7d9cbce14c36a191047f4cc0a0622a6b86e19a/build/.gitkeep -------------------------------------------------------------------------------- /docs/commands/collect.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Collect command 3 | description: Documentation website for the collect command GloriaJS 4 | permalink: docs/command/collect 5 | layout: blog 6 | --- 7 | 8 | # Command 9 | 10 | ``` 11 | ./bin/gloria collect --dest=logs 12 | ``` 13 | 14 | Debugging command. 15 | 16 | Runs the collect command and writes the output to the console, or to a file. 17 | 18 | Useful for debuging or profiling. 19 | 20 | Sample output: 21 | 22 | ``` 23 | {"include":[],"copy":[],"styles":[]} 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/commands/copy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Copy command 3 | description: Documentation website for the copy command GloriaJS 4 | permalink: docs/command/copy 5 | layout: blog 6 | --- 7 | 8 | # Command 9 | 10 | ``` 11 | ./bin/gloria copy --dest=build --path=public 12 | ``` 13 | 14 | Command to copy the static assets into destination folder. 15 | -------------------------------------------------------------------------------- /docs/index.draft.md: -------------------------------------------------------------------------------- 1 | This post will be ignored by the collector 2 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: GloriaJS 3 | description: Documentation for gloriajs 4 | permalink: docs/ 5 | --- 6 | 7 | # Welcome to GloriaJS 8 | 9 | The following commands are available and exist: 10 | 11 | - [x] collect all files 12 | - [x] collect assets 13 | - [x] pre-process content pages 14 | - [x] process content data and layouts 15 | - [x] process tailwind 16 | - [x] write to file system 17 | 18 | We might open a discord later. 19 | 20 | This project hopes to make simple websites super simple to create and deploy, specially for super easy things you don't wanna worry about, that way everyone can focus on their amazing content without worrying about obscure compile errors. 21 | 22 | A configuration file looks like this: 23 | 24 | ``` 25 | name: Gloria JS 26 | tagline: 'A simple static website generator' 27 | baseurl: 'https://gloriajs.github.io' 28 | author: 'daveed silva' 29 | description: 'Documentation for the tool gloriajs' 30 | dest: build 31 | theme: 32 | - layouts/tailwind 33 | css: tailwind 34 | include: 35 | - docs 36 | exclude: 37 | - '.draft.md' 38 | copy: 39 | - public 40 | ``` 41 | 42 | You can add any additional properties here and they will be available for you in the templates as properties of project.config. 43 | 44 | ## Commands 45 | 46 | The following commands are available to use 47 | 48 | 49 | - `collect` - `[output]` traverses the include-paths and saves information about the available files 50 | - `copy` - `[path] [dest]` copies the static files from source paths to dest folders 51 | - `extract` - `[dest]` finds metadata and frontmatter information from every collected file 52 | - `prebuild` - `[dest]` creates the output placeholders so they can be processed 53 | - `build` - `[path] [dest]` interpolates the content into the layout 54 | - `css:tailwind` - `[path] [dest]` runs tailwind in the html output 55 | - `scripts` - `[path] [dest]` @TODO processes scripts 56 | - `write` - `[dest]` @TODO writes production version to disk and cleans temp files 57 | - `version` - ` ` returns the current package version 58 | 59 | The commands all should work on their own, and the ones that combine multiple steps do it very nicely by using promises, we pass the project around, and store and read from its properties, to create new commands or amplify existing ones, for plugins and templates, review the data structure. 60 | 61 | Find the website documentation in [github](https://github.com/gloriajs/gloriajs.github.io) 62 | 63 | Or the live website in [gloria.js.org](https://gloria.js.org/) 64 | 65 |

Template

66 | 67 |

Templates are html or markdown files, they are interpreted with handlebars.

68 | 69 |

There are three main objects you can access on your template, site has access to the properties specified 70 | in _config.yml. 71 | page has access to the properties specified in the header of the page, and args has 72 | access to the arguments given to the command build.

73 | 74 |

Layouts

75 | 76 |

A layout file is used to have a structure common to different pages, for example a navigation menu, header, css includes, 77 | scripts, etc.

78 | 79 |

The name of the layout will be the name of the file, by default default.html is included, additional 80 | files included in the _layout folder will be used. If no layout exists or is especified the file 81 | will be rendered on its own.

82 | 83 |

The main thing that templates require is {{{page.content}}} that will get replaced with the actual content 84 | of the page.

85 | 86 |

Includes

87 | 88 |

Include files, or also known as partials, are files that can be included in your content or layouts, they can be 89 | useful to reuse snippets like a head element, or a share button.

90 | 91 |

Any amount of partials and includes can exists, in the _includes folder, they are named as their file 92 | name, and they can be used like this {{> head}}. Data will be interpolated there as expected.

93 | 94 |

Frontmatter

95 | 96 |

Every page can have additional attributes that will be accessible to it, the layout or the includes.

97 | 98 |

The additional attributes should be included in the beginning of the file in the following format, use three dashes 99 | in the first line of the file, and add any key pair of attributes using the 100 | Yaml syntax, and finish with three dashes. Like this: 101 | `

102 |

103 | This change allows accessing variables defined in layouts' and includes' frontmatters. This could be achieved through the following syntax. {{ page.g.layout.var1}} for accessing layout variables or 104 | 105 | {{ page.g.head.var1}} for accessing includes' (hbs partials) variables. head here is the name of the include. g (short for gloria) acts as a namespace to avoid collision between common variable names like 'title' or 'description'. 106 | 107 |

108 |
109 | 110 |

111 |


112 | 
113 | ---
114 | 
115 | title: About us
116 | url: /about
117 | description:
118 | 
119 | ---
120 | 
121 |     
122 |

123 | 124 |
125 | 126 |

Issues

127 | 128 |

There's no layouts or including other files yet.

129 | 130 |

Roadmap, also includes having access to other pages, to be able to loop in blog posts, or read categories for example.

131 | 132 |

Deployment

133 | 134 |

You can use any service that lets you host HTML or static sites. The quickest ones to get working are github pages and 135 | firebase.

136 | 137 |

Firebase instructions

138 | 139 |

Create a firebase account and a project, then run firebase init on your root folder and follow the instructions. 140 | When prompted what folder to use, chose your destination folder.

141 | 142 |

Github pages instructions

143 | 144 |

The best option to use github pages is to build your site to a folder called docs, push to a repo, and 145 | then select that folder in the github-pages configuration for that repo.

146 | 147 |

Contributing

148 |

149 | We are creating a contributing guide, find more information 150 | here. 151 |

152 | 153 | 154 |

Troubleshooting

155 | 156 |

I'm currently using:

157 | 158 |


159 | 
160 | node v6.4.0
161 | npm 3.10.3
162 | 

163 | 164 |

Try upgrading your version of node and run yarn again. Or open an issue describing your problem.

165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /docs/tests.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: GloriaJS 3 | description: Documentation for testing with gloriaJS 4 | permalink: tests/ 5 | layout: blog 6 | --- 7 |

Tests

8 |

Tests are not only possible, but desirable.

-------------------------------------------------------------------------------- /layouts/tailwind/blog.layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: index 3 | name: blog 4 | title: Blog 5 | --- 6 | 7 | 8 | 9 | 10 | {{site.title}} 11 | 12 | 13 | 16 | 17 | 18 |
19 |
20 |

{{page.title}}

21 |
22 | {{{page.content}}} 23 |
24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /layouts/tailwind/index.layout.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Welcome! 3 | description: Meow 4 | permalink: '' 5 | name: index 6 | --- 7 | 8 | 9 | 10 | 11 | {{page.title}} 12 | 13 | 14 | 17 | 18 | 19 |
20 |
21 | {{{content}}} 22 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /layouts/tailwind/index.page.draft.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Welcome draft 3 | description: 4 | --- 5 | 6 | Skip this page during prebuild. 7 | -------------------------------------------------------------------------------- /layouts/tailwind/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /lib/bin/commands/build.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../utils/logger'); 2 | const config = require('../../core/project.config'); 3 | const _collect = require('../../commands/collect'); 4 | const _extract = require('../../commands/extract'); 5 | const _prebuild = require('../../commands/prebuild'); 6 | const _build = require('../../commands/build'); 7 | const _write_html = require('../../commands/write.html'); 8 | const fs = require('fs'); 9 | 10 | /** 11 | * work in progress, always add on top of existing ways and prevent 12 | * backwards compatibility issues 13 | * 14 | * Default include-paths are pages and posts. 15 | * 16 | * This method 17 | * @param {string} argv.dest If present, write result to destination file 18 | * @param {string} argv.folder If present writes files to folder 19 | * @param {string} argv.write If false, skips writing files to disk 20 | * 21 | * @return {object} information like content and medatata from every coollected path 22 | */ 23 | const build = (argv) => { 24 | const project = { 25 | config: config.get(), 26 | collected: {}, 27 | content: {}, 28 | extracted: {}, 29 | prebuilt: { 30 | files: {}, 31 | styles: {}, 32 | scripts: {}, 33 | }, 34 | html: [], 35 | }; 36 | 37 | _collect 38 | .run(project) 39 | .then((project) => _extract.run(project)) 40 | .then((project) => _prebuild.run(project)) 41 | .then((project) => _build.run(project)) 42 | .then((prochecto) => { 43 | const { config } = prochecto; 44 | 45 | if (argv.dest) { 46 | const dest = `./${argv.dest}/built.${config._hash}.json`; 47 | const ztring = JSON.stringify(prochecto); 48 | fs.writeFileSync(dest, ztring); 49 | } 50 | 51 | // logger.log({ ...prochecto }); 52 | return _write_html.run(prochecto); 53 | }) 54 | .catch((e) => { 55 | logger.error({ e }); 56 | }); 57 | }; 58 | 59 | const options = { 60 | dest: { 61 | default: null, 62 | description: `Destination folder to output JSON report.`, 63 | }, 64 | }; 65 | 66 | /** 67 | * Command to compile html files 68 | */ 69 | module.exports = { 70 | command: `build [dest]`, 71 | aliases: ['html'], 72 | describe: `Compiles files, interpolates data and writes temp files to chosen destination. 73 | By default it will use a folder name 'build' in the root directory of the project, 74 | Check your _config.yml to change. 75 | It won't build to a parent folder. 76 | The command will fail if _config.yml is invalid or not present.`, 77 | builder: options, 78 | handler: build, 79 | }; 80 | -------------------------------------------------------------------------------- /lib/bin/commands/collect.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../utils/logger'); 2 | const config = require('../../core/project.config'); 3 | const _collect = require('../../commands/collect'); 4 | const fs = require('fs'); 5 | 6 | /** 7 | * Normally called as part of another command, but it can be used 8 | * directly for debugging purposes. 9 | * When given an output argument it will write the results and 10 | * additional information to disk in a json file. 11 | * 12 | * Default include-paths are pages and posts. 13 | * @param {string} argv.dest If present, write result to destination file 14 | */ 15 | const collect = (argv) => { 16 | const project = { 17 | config: config.get(), 18 | }; 19 | 20 | _collect.run(project).then((project) => { 21 | const { config } = project; 22 | 23 | if (argv.dest) { 24 | const dest = `./${argv.dest}/collected.${config._hash}.json`; 25 | const ztring = JSON.stringify(project.collected); 26 | fs.writeFileSync(dest, ztring); 27 | } 28 | 29 | // logger.log({ collected: project.collected }); 30 | 31 | return project; 32 | }); 33 | }; 34 | 35 | const options = { 36 | dest: { 37 | default: null, 38 | description: `Destination path or folder to output a json file with results.`, 39 | }, 40 | }; 41 | 42 | module.exports = { 43 | command: `collect [output]`, 44 | aliases: [], 45 | describe: `Collects the files and data that will be processed.`, 46 | builder: options, 47 | handler: collect, 48 | }; 49 | -------------------------------------------------------------------------------- /lib/bin/commands/copy.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../utils/logger'); 2 | const config = require('../../core/project.config'); 3 | const _copy = require('../../commands/copy'); 4 | const fs = require('node:fs'); 5 | const path = require('path'); 6 | const root = process.cwd(); 7 | 8 | /** 9 | * Normally called as part of another command, but it can be used 10 | * directly for debugging purposes or faster asset collection. 11 | * 12 | * @param {string} argv.dest If present, write result to destination folder 13 | * @param {string} argv.path If present, override the configuration 14 | */ 15 | const copy = (argv) => { 16 | const project = { 17 | config: config.get(), 18 | }; 19 | 20 | if (argv.dest) { 21 | project.config.dest = argv.dest; 22 | } 23 | 24 | _copy.run(project).then((project) => { 25 | const { config } = project; 26 | 27 | config.copy.map((p) => { 28 | const sourceDir = path.join(root, p); 29 | const destinationDir = path.join(root, config.dest); 30 | 31 | if (!fs.existsSync(destinationDir)) { 32 | fs.mkdirSync(destinationDir, { recursive: true }); 33 | } 34 | 35 | fs.cp(sourceDir, destinationDir, { recursive: true }, function (error) { 36 | if (error) { 37 | throw error; 38 | } 39 | }); 40 | }); 41 | 42 | return project; 43 | }); 44 | }; 45 | 46 | const options = { 47 | dest: { 48 | default: null, 49 | description: `Destination folder`, 50 | }, 51 | path: { 52 | default: null, 53 | description: 'Ignore the path on configuration and use this folder instead', 54 | }, 55 | }; 56 | 57 | module.exports = { 58 | command: `copy [dest] [paths]`, 59 | aliases: [], 60 | describe: `Copy the asset files into the output folder.`, 61 | builder: options, 62 | handler: copy, 63 | }; 64 | -------------------------------------------------------------------------------- /lib/bin/commands/css.tailwind.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../utils/logger'); 2 | const config = require('../../core/project.config'); 3 | const _collect = require('../../commands/collect'); 4 | const _extract = require('../../commands/extract'); 5 | const _prebuild = require('../../commands/prebuild'); 6 | const _build = require('../../commands/build'); 7 | const _css_tailwind = require('../../commands/css.tailwind'); 8 | const fs = require('fs'); 9 | const upath = require('upath'); 10 | const _write_html = require('../../commands/write.html'); 11 | 12 | /** 13 | * work in progress, always add on top of existing ways and prevent 14 | * backwards compatibility issues 15 | * 16 | * Default include-paths are pages and posts. 17 | * 18 | * This method 19 | * @param {string} argv.dest If present, write result to destination file 20 | * @param {string} argv.folder If present writes files to folder 21 | * @param {string} argv.write If false, skips writing files to disk 22 | * 23 | * @return {object} information like content and medatata from every coollected path 24 | */ 25 | const build = (argv) => { 26 | const project = { 27 | config: config.get(), 28 | collected: {}, 29 | content: {}, 30 | extracted: {}, 31 | prebuilt: { 32 | files: {}, 33 | styles: {}, 34 | scripts: {}, 35 | }, 36 | html: [], 37 | }; 38 | 39 | _collect 40 | .run(project) 41 | .then((project) => _extract.run(project)) 42 | .then((project) => _prebuild.run(project)) 43 | .then((project) => _build.run(project)) 44 | .then((prochecto) => { 45 | const { config, html } = prochecto; 46 | 47 | if (argv.dest) { 48 | const dest = `./${argv.dest}/built.${config._hash}.json`; 49 | const ztring = JSON.stringify(prochecto); 50 | fs.writeFileSync(dest, ztring); 51 | } 52 | 53 | return _write_html.run(prochecto); 54 | }) 55 | .then((project) => _css_tailwind.run(project)) 56 | .then((project) => { 57 | // logger.log({ ...project }); 58 | }) 59 | .catch((e) => { 60 | logger.error({ e }); 61 | }); 62 | }; 63 | 64 | const options = { 65 | dest: { 66 | default: null, 67 | description: `Destination for stylesheet.`, 68 | }, 69 | }; 70 | 71 | /** 72 | * Command to compile html files 73 | */ 74 | module.exports = { 75 | command: `css:tailwind [dest]`, 76 | aliases: ['tailwind'], 77 | describe: `Reads the html output and creates a tailwind stylesheet. 78 | The command will fail if _config.yml is invalid or not present.`, 79 | builder: options, 80 | handler: build, 81 | }; 82 | -------------------------------------------------------------------------------- /lib/bin/commands/extract.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../utils/logger'); 2 | const config = require('../../core/project.config'); 3 | const _collect = require('../../commands/collect'); 4 | const _extract = require('../../commands/extract'); 5 | const fs = require('fs'); 6 | 7 | /** 8 | * work in progress, always add on top of existing ways and prevent 9 | * backwards compatibility issues 10 | * 11 | * Default include-paths are pages and posts. 12 | * 13 | * This method 14 | * @param {string} argv.dest If present, write result to destination file 15 | * @param {string} argv.path If present overrides configuration file 16 | * 17 | * @return {object} information like content and medatata from every coollected path 18 | */ 19 | const extract = (argv) => { 20 | const project = { 21 | config: config.get(), 22 | collected: {}, 23 | content: {}, 24 | extracted: {}, 25 | }; 26 | 27 | _collect 28 | .run(project) 29 | .then(_extract.run) 30 | .then((p) => { 31 | const { config } = p; 32 | 33 | if (argv.dest) { 34 | const dest = `./${argv.dest}/extracted.${config._hash}.json`; 35 | const ztring = JSON.stringify(p.extracted); 36 | fs.writeFileSync(dest, ztring); 37 | } 38 | 39 | // logger.log({ ...p, ...p.extracted }); 40 | return p; 41 | }) 42 | .catch((e) => { 43 | logger.error({ e }); 44 | }); 45 | }; 46 | 47 | const options = { 48 | dest: { 49 | default: null, 50 | description: `Destination folder to output a json file with results.`, 51 | }, 52 | }; 53 | 54 | /** 55 | * Command to read md files and other data and create taxonomy 56 | */ 57 | module.exports = { 58 | command: `extract [dest] [path]`, 59 | aliases: [], 60 | describe: `Traverses the source files and extracts metadata.`, 61 | builder: options, 62 | handler: extract, 63 | }; 64 | -------------------------------------------------------------------------------- /lib/bin/commands/prebuild.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../utils/logger'); 2 | const config = require('../../core/project.config'); 3 | const _collect = require('../../commands/collect'); 4 | const _extract = require('../../commands/extract'); 5 | const _prebuild = require('../../commands/prebuild'); 6 | const fs = require('fs'); 7 | 8 | /** 9 | * work in progress, always add on top of existing ways and prevent 10 | * backwards compatibility issues 11 | * 12 | * Default include-paths are pages and posts. 13 | * 14 | * This method 15 | * @param {string} argv.dest If present, write result to destination file 16 | * @param {string} argv.path If present overrides configuration file 17 | * 18 | * @return {object} information like content and medatata from every coollected path 19 | */ 20 | const prebuild = (argv) => { 21 | const project = { 22 | config: config.get(), 23 | collected: {}, 24 | content: {}, 25 | extracted: {}, 26 | prebuilt: { 27 | files: {}, 28 | styles: {}, 29 | scripts: {}, 30 | }, 31 | }; 32 | 33 | _collect 34 | .run(project) 35 | .then(_extract.run) 36 | .then(_prebuild.run) 37 | .then((prochecto) => { 38 | const { config } = prochecto; 39 | 40 | if (argv.dest) { 41 | const dest = `./${argv.dest}/extracted.${config._hash}.json`; 42 | const ztring = JSON.stringify(prochecto.extracted); 43 | fs.writeFileSync(dest, ztring); 44 | } 45 | 46 | return prochecto; 47 | }) 48 | .catch((e) => { 49 | logger.error({ e }); 50 | }); 51 | }; 52 | 53 | const options = { 54 | dest: { 55 | default: null, 56 | description: `Destination folder to output a json file with results.`, 57 | }, 58 | }; 59 | 60 | /** 61 | * Command to prepare everything for compilation 62 | */ 63 | module.exports = { 64 | command: `prebuild [dest] [path]`, 65 | aliases: [], 66 | describe: `Gets all files ready for compilation, applying the metadata extracted.`, 67 | builder: options, 68 | handler: prebuild, 69 | }; 70 | -------------------------------------------------------------------------------- /lib/bin/commands/setup.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../utils/logger'); 2 | const config = require('../../core/project.config'); 3 | const _setup = require('../../commands/setup'); 4 | const fs = require('fs'); 5 | 6 | /** 7 | * Normally called as part of another command, but it can be used 8 | * directly for debugging purposes. 9 | * When given an output argument it will write the results and 10 | * additional information to disk in a json file. 11 | * 12 | * Default include-paths are pages and posts. 13 | * @param {string} argv.dest If present, write result to destination file 14 | */ 15 | const setup = (argv) => { 16 | const { defaults } = config; 17 | 18 | _setup.run({ config: defaults }, argv.overwrite).then((project) => { 19 | // logger.log(); 20 | 21 | return project; 22 | }); 23 | }; 24 | 25 | const options = { 26 | overwrite: { 27 | default: false, 28 | description: `Overwrite existing files: not yet supported.`, 29 | }, 30 | }; 31 | 32 | module.exports = { 33 | command: `setup`, 34 | aliases: [], 35 | describe: `Attemps to create a _config.yml file and the necesary folders.`, 36 | builder: options, 37 | handler: setup, 38 | }; 39 | -------------------------------------------------------------------------------- /lib/bin/commands/write.js: -------------------------------------------------------------------------------- 1 | const logger = require('../../utils/logger'); 2 | const config = require('../../core/project.config'); 3 | const _collect = require('../../commands/collect'); 4 | const _extract = require('../../commands/extract'); 5 | const _prebuild = require('../../commands/prebuild'); 6 | const _build = require('../../commands/build'); 7 | const _write_html = require('../../commands/write.html'); 8 | const fs = require('fs'); 9 | 10 | /** 11 | * work in progress, always add on top of existing ways and prevent 12 | * backwards compatibility issues 13 | * 14 | * Default include-paths are pages and posts. 15 | * 16 | * This method 17 | * @param {string} argv.dest If present, write result to destination file 18 | * @param {string} argv.folder If present writes files to folder 19 | * @param {string} argv.write If false, skips writing files to disk 20 | * 21 | * @return {object} information like content and medatata from every coollected path 22 | */ 23 | const write = (argv) => { 24 | const project = { 25 | config: config.get(), 26 | collected: {}, 27 | content: {}, 28 | extracted: {}, 29 | prebuilt: { 30 | files: {}, 31 | styles: {}, 32 | scripts: {}, 33 | }, 34 | html: [], 35 | }; 36 | 37 | _collect 38 | .run(project) 39 | .then((project) => _extract.run(project)) 40 | .then((project) => _prebuild.run(project)) 41 | .then((project) => _build.run(project)) 42 | .then((prochecto) => { 43 | const { config } = prochecto; 44 | 45 | if (argv.dest) { 46 | const dest = `./${argv.dest}/built.${config._hash}.json`; 47 | const ztring = JSON.stringify(prochecto); 48 | fs.writeFileSync(dest, ztring); 49 | } 50 | 51 | // logger.log({ ...prochecto }); 52 | return _write_html.run(prochecto); 53 | }) 54 | .catch((e) => { 55 | logger.error({ e }); 56 | }); 57 | }; 58 | 59 | const options = { 60 | dest: { 61 | default: null, 62 | description: `Destination folder.`, 63 | }, 64 | }; 65 | 66 | /** 67 | * Command to compile html files 68 | */ 69 | module.exports = { 70 | command: `write [dest]`, 71 | aliases: ['write:html'], 72 | describe: `Compiles files, interpolates data and writes files to their correct url. 73 | By default it will use a folder name 'build' in the root directory of the project, 74 | Check your _config.yml to change. 75 | It won't build to a parent folder. 76 | The command will fail if _config.yml is invalid or not present.`, 77 | builder: options, 78 | handler: write, 79 | }; 80 | -------------------------------------------------------------------------------- /lib/bin/gloria.js: -------------------------------------------------------------------------------- 1 | process.bin = process.title = `gloria`; 2 | const version = require('../version'); 3 | const yargs = require(`yargs`); 4 | 5 | /** 6 | * @name init 7 | * @description uses yargs to enable the commands available 8 | */ 9 | const init = () => { 10 | yargs 11 | .commandDir(`./commands`, { 12 | extensions: ['js', 'ts'], 13 | }) 14 | .alias('v', 'version') 15 | .version(version) 16 | .help() 17 | .usage( 18 | `Gloria is a static site generator. Use the command init to get started.`, 19 | ) 20 | .epilogue( 21 | `For more information, check out the documentation in github.com/gloriajs/gloria`, 22 | ).argv; 23 | }; 24 | 25 | init(); 26 | -------------------------------------------------------------------------------- /lib/build/handlebars-helpers.js: -------------------------------------------------------------------------------- 1 | const $log = require('../utils/logger'); 2 | const Handlebars = require('handlebars'); 3 | const fse = require('fs-extra'); 4 | const moment = require('moment'); 5 | 6 | Handlebars.registerHelper('replace', (options) => ''); 7 | Handlebars.registerHelper('noop', (options) => options.fn(this)); 8 | Handlebars.registerHelper('for', (options) => $log.log(options)); 9 | 10 | Handlebars.registerHelper('capitalize', function (value) { 11 | return value && typeof value === 'string' 12 | ? value.replace(/\b\w/g, (l) => l.toUpperCase()) 13 | : ''; 14 | }); 15 | 16 | Handlebars.registerHelper('eachWithSort', (array, key, direction, opts) => { 17 | let data; 18 | if (opts.data) { 19 | data = Handlebars.createFrame(opts.data); 20 | } 21 | 22 | const sorted = array.sort((a, b) => { 23 | a = a[key]; 24 | b = b[key]; 25 | if (direction === '-') { 26 | return a < b ? 1 : a == b ? 0 : -1; 27 | } else { 28 | return a > b ? 1 : a == b ? 0 : -1; 29 | } 30 | }); 31 | 32 | let s = ''; 33 | sorted.forEach((e, i) => { 34 | if (data) { 35 | data.index = i; 36 | } 37 | 38 | s += opts.fn(e, data); 39 | }); 40 | 41 | return s; 42 | }); 43 | 44 | Handlebars.registerHelper('formatDate', function (value, format) { 45 | format = format || 'MMM Do YY'; 46 | return moment.utc(value).format(format); 47 | }); 48 | 49 | Handlebars.registerHelper('ifeq', function (a, b, options) { 50 | if (a === b) { 51 | return options.fn(this); 52 | } 53 | 54 | return options.inverse(this); 55 | }); 56 | 57 | Handlebars.registerHelper('ifnoteq', function (a, b, options) { 58 | if (a === b) { 59 | return options.fn(this); 60 | } 61 | 62 | return options.inverse(this); 63 | }); 64 | 65 | Handlebars.registerPartials = function registerPartials(files) { 66 | const partials = {}; 67 | files.forEach((file) => { 68 | if ([`.html`, `.svg`, `.md`].indexOf(file.extension) === -1) { 69 | return; 70 | } 71 | 72 | // calling require here to avoid circular dependencies 73 | // http://selfcontained.us/2012/05/08/node-js-circular-dependencies/ 74 | const Render = require('./render'); 75 | const fm = Render.extract(file); 76 | const partial = Object.assign({}, file, { 77 | content: fm.content, 78 | variables: fm.fm, 79 | name: file.name.replace('.html', ''), 80 | }); 81 | partials[partial.name] = partial; 82 | 83 | Handlebars.registerPartial(partial.name, partial.content); 84 | Handlebars.registerPartial(file.name, partial.content); 85 | }); 86 | 87 | return partials; 88 | }; 89 | 90 | module.exports = Handlebars; 91 | -------------------------------------------------------------------------------- /lib/build/render.js: -------------------------------------------------------------------------------- 1 | const fse = require('fs-extra'); 2 | const stylus = require('stylus'); 3 | const marked = require('marked'); 4 | const fm = require('front-matter'); 5 | const S = require(`string`); 6 | 7 | // const sass = require(`node-sass`); 8 | const path = require(`path`); 9 | const $log = require('../utils/logger'); 10 | const moment = require('moment'); 11 | 12 | //@todo: let the user select their preferred template engine 13 | const Handlebars = require('./handlebars-helpers'); 14 | const root = process.cwd(); 15 | const Render = {}; 16 | Render.layouts = {}; 17 | Render.partials = {}; 18 | 19 | const registerLayouts = function (files) { 20 | for (var k in files) { 21 | if (files[k].name.match('.html') === null) { 22 | return; 23 | } 24 | 25 | var layoutFrontMatter = extract(files[k]); 26 | Object.assign(files[k], layoutFrontMatter); 27 | Render.layouts[k] = files[k]; 28 | } 29 | 30 | return Render.layouts; 31 | }; 32 | 33 | const registerPartials = function (files) { 34 | Render.partials = Handlebars.registerPartials(files); 35 | }; 36 | 37 | const renderCommon = function (page, content, data) { 38 | let template; 39 | try { 40 | template = Handlebars.compile(content); 41 | } catch (e) { 42 | $log.warn(`failed to compile template ${template}`); 43 | } 44 | 45 | let result = template(data); 46 | const newname = (page.name || ``).replace(/\.md$/, '.html'); 47 | const destination = { 48 | folder: page.basePath, 49 | file: newname, 50 | }; 51 | if (newname !== 'index.html') { 52 | destination.folder = `${page.basePath}${page.name.replace('.html', '')}`; 53 | destination.file = `index.html`; 54 | } 55 | 56 | if (data.page.url) { 57 | const folder = data.page.url; 58 | destination.folder = `${folder}`; 59 | page.name = 'index.html'; 60 | } else { 61 | const folder = data.page.title 62 | ? `blog/${S(data.page.category).slugify().s}/${ 63 | S(data.page.title).slugify().s 64 | }` 65 | : page.name.replace('.html', ''); 66 | destination.folder = `${folder}`; 67 | page.name = 'index.html'; 68 | } 69 | data.page.content = result; 70 | const layoutName = page.data.layout || `default`; 71 | if (Render.layouts[layoutName]) { 72 | // g is a namespace for layout and hbs partials' vars 73 | data.page.g = {}; 74 | data.page.g.layout = Render.layouts[layoutName].fm; 75 | Object.assign( 76 | data.page.g, 77 | extractPartialsVariables(Render.layouts[layoutName].content), 78 | ); 79 | try { 80 | template = Handlebars.compile(Render.layouts[layoutName].content); 81 | } catch (e) { 82 | $log.warn(`Failed to compile file ${template}`); 83 | } 84 | result = template(data); 85 | } 86 | 87 | return { 88 | destination: destination, 89 | content: result, 90 | data: data, 91 | }; 92 | }; 93 | 94 | const extract = function (file) { 95 | let content = ``; 96 | try { 97 | content = fse.readFileSync(file.path, `utf-8`); 98 | } catch (err) { 99 | if (err) { 100 | throw err; 101 | } 102 | } 103 | 104 | content = fm(content); 105 | const timestamp = moment 106 | .utc(content.date ? content.date : file.stats.birthtime) 107 | .format('X'); 108 | 109 | const destination = { 110 | folder: file.basePath, 111 | file: file.name, 112 | }; 113 | 114 | return { 115 | destination: destination, 116 | content: content.body, 117 | fm: Object.assign({ timestamp }, content.attributes), 118 | }; 119 | }; 120 | 121 | const renderStyles = function (item, dest) { 122 | $log.log(`ignoring sass file: ${item.basePath}${item.name}`); 123 | const result = extract(item); 124 | result.css = ''; 125 | 126 | // sass.renderSync({ 127 | // data: result.content, 128 | // includePaths: [ `${root}/${item.basePath}` ], 129 | // }); 130 | return result; 131 | }; 132 | 133 | const renderStylus = function (item, dest) { 134 | $log.log(`rendering stylus file: ${item.basePath}${item.name}`); 135 | const result = extract(item); 136 | result.css = stylus(result.content) 137 | .set('paths', [`${root}/${item.basePath}`]) 138 | .render(); 139 | return result; 140 | }; 141 | 142 | const renderHtml = function (page, data) { 143 | data.page = page.data; 144 | return renderCommon(page, page.content, data); 145 | }; 146 | 147 | const renderMD = function (page, data) { 148 | data.page = page.data; 149 | const html = marked(page.content); 150 | return renderCommon(page, html, data); 151 | }; 152 | 153 | const renderPost = function (page, data) { 154 | let result = {}; 155 | const html = marked(page.content); 156 | data.page = page.data; 157 | data.page.url = page.data.url; 158 | result = renderCommon(page, html, data); 159 | result.destination.folder = path.normalize(`${result.destination.folder}`); 160 | return result; 161 | }; 162 | 163 | const extractPartialsVariables = function (document) { 164 | var regex = /{{>([\s\w]+)}}/g; 165 | var partialsVars = {}; 166 | while ((result = regex.exec(document)) !== null) { 167 | var partialName = result[1].trim(); 168 | var currrentPartial = Render.partials[partialName]; 169 | if (currrentPartial) { 170 | partialsVars[partialName] = currrentPartial.variables; 171 | } 172 | } 173 | 174 | return partialsVars; 175 | }; 176 | 177 | Render['.html'] = renderHtml; 178 | Render['.md'] = renderMD; 179 | Render.post = renderPost; 180 | Render.extract = extract; 181 | Render.registerPartials = registerPartials; 182 | Render.registerLayouts = registerLayouts; 183 | Render.renderStyles = renderStyles; 184 | Render.renderStylus = renderStylus; 185 | 186 | module.exports = Render; 187 | -------------------------------------------------------------------------------- /lib/commands/build.js: -------------------------------------------------------------------------------- 1 | const logger = require('../utils/logger'); 2 | 3 | /** 4 | * Creates the resulting html by reading all the pages and parsing with the layouts 5 | * 6 | * @param {*} project 7 | * @returns project 8 | */ 9 | const run = (project) => { 10 | const promise = new Promise((resolve, reject) => { 11 | if (!project.collected || !project.extracted || !project.prebuilt) { 12 | logger.error('Missing prebuilt data at build step'); 13 | return reject(); 14 | } 15 | 16 | const result = []; 17 | 18 | const _files = project.prebuilt.files; 19 | const _data = project.prebuilt.data; 20 | 21 | _files.map((file, k) => { 22 | result[k] = file.compile_method(file, _files, { 23 | ..._data, 24 | page: { 25 | ...file.item.meta, 26 | ...file.item.attrs, 27 | content: file.item.content, 28 | location: file.destination, 29 | }, 30 | }); 31 | }); 32 | 33 | project.html = result; 34 | 35 | return resolve(project); 36 | }); 37 | 38 | return promise; 39 | }; 40 | 41 | module.exports = { 42 | run, 43 | }; 44 | -------------------------------------------------------------------------------- /lib/commands/collect.js: -------------------------------------------------------------------------------- 1 | const klaw = require('klaw'); 2 | const $q = require('q'); 3 | const logger = require('../utils/logger'); 4 | const root = process.cwd(); 5 | const S = require(`string`); 6 | const path = require('path'); 7 | 8 | const _klaw = (folder, items, config) => { 9 | const promise = new Promise(function (resolve, reject) { 10 | klaw(folder) 11 | .on('data', (item) => { 12 | // @TODO: fix bug where is not ignoring excludes 13 | if (item.path.endsWith(config.exclude[0])) { 14 | return; 15 | } else { 16 | const shortPath = S(item.path).chompLeft(`${root}/`).s; 17 | /** excludes the folder it was found in, for copying files as they are usually referenced this way */ 18 | const relativePath = S(shortPath).chompLeft(folder).s; 19 | 20 | const details = { 21 | ...item, 22 | shortPath, 23 | relativePath, 24 | }; 25 | 26 | items.push(details); 27 | } 28 | }) 29 | .on('error', (e) => { 30 | logger.error(e); 31 | reject(); 32 | }) 33 | .on('end', () => { 34 | resolve(); 35 | }); 36 | }); 37 | 38 | return promise; 39 | }; 40 | 41 | // Actual collect file that the bin file calls after it processed the arguments or whatever 42 | const run = (project) => { 43 | const promiseArray = []; 44 | 45 | const items = { 46 | include: [], 47 | copy: [], 48 | styles: [], 49 | theme: [], 50 | }; 51 | 52 | project.config.include.map((path) => { 53 | promiseArray.push(_klaw(path, items.include, project.config)); 54 | }); 55 | 56 | // project.config.copy.map((path) => { 57 | // promiseArray.push(_klaw(path, items.copy, project.config)); 58 | // }); 59 | 60 | project.config.theme.map((path) => { 61 | promiseArray.push(_klaw(path, items.theme, project.config)); 62 | }); 63 | 64 | return $q.all(promiseArray).then(() => { 65 | project.collected = items; 66 | return project; 67 | }); 68 | }; 69 | 70 | module.exports = { 71 | run, 72 | }; 73 | -------------------------------------------------------------------------------- /lib/commands/copy.js: -------------------------------------------------------------------------------- 1 | const klaw = require('klaw'); 2 | const $q = require('q'); 3 | const logger = require('../utils/logger'); 4 | 5 | /** 6 | * 7 | * Didn't abstract this yet coz is slightly different than collect 8 | * and will hopefully change differently 9 | * */ 10 | const _klaw = (path, items, config) => { 11 | const promise = new Promise(function (resolve, reject) { 12 | klaw(path) 13 | .on('data', (item) => { 14 | // @TODO: fix bug where is not ignoring excludes 15 | if (item.path.endsWith(config.exclude[0])) { 16 | return; 17 | } else { 18 | items.push(item); 19 | } 20 | }) 21 | .on('error', (e) => { 22 | logger.error(e); 23 | reject(); 24 | }) 25 | .on('end', () => { 26 | resolve(); 27 | }); 28 | }); 29 | 30 | return promise; 31 | }; 32 | 33 | // Actual copy method that collects the files 34 | const run = (project) => { 35 | const promiseArray = []; 36 | 37 | const items = { 38 | copy: [], 39 | }; 40 | 41 | project.config.copy.map((path) => { 42 | promiseArray.push(_klaw(path, items.copy, project.config)); 43 | }); 44 | 45 | return $q.all(promiseArray).then(() => { 46 | project.collected = items; 47 | return project; 48 | }); 49 | }; 50 | 51 | module.exports = { 52 | run, 53 | }; 54 | -------------------------------------------------------------------------------- /lib/commands/css.tailwind.js: -------------------------------------------------------------------------------- 1 | const logger = require('../utils/logger'); 2 | const tailwind = require("tailwindcss"); 3 | const postcss = require("postcss"); 4 | const fs = require('fs'); 5 | 6 | /** 7 | * Creates the resulting CSS by reading all the pages and getting the tailwind classes 8 | * 9 | * @param {*} project 10 | * @returns project 11 | */ 12 | const run = (project) => { 13 | const promise = new Promise((resolve, reject) => { 14 | const { html, config } = project; 15 | if (!html) { 16 | logger.error('Missing html data at css step'); 17 | return reject(); 18 | } 19 | 20 | (async () => { 21 | const result = await postcss([ 22 | tailwind({ 23 | //...config, 24 | content: [`${config.dest}/**/*.{html,js,md}`], 25 | }), 26 | ]).process(`@tailwind base;@tailwind components;@tailwind utilities;`, { 27 | from: undefined, 28 | }); 29 | 30 | fs.writeFileSync(`${config.dest}/main.css`, result.css); 31 | })(); 32 | 33 | return resolve(project); 34 | }); 35 | 36 | return promise; 37 | }; 38 | 39 | module.exports = { 40 | run, 41 | }; 42 | -------------------------------------------------------------------------------- /lib/commands/extract.js: -------------------------------------------------------------------------------- 1 | const klaw = require('klaw'); 2 | const $q = require('q'); 3 | const logger = require('../utils/logger'); 4 | const fm = require('front-matter'); 5 | const path = require('path'); 6 | const fse = require('fs-extra'); 7 | const moment = require('moment'); 8 | const S = require(`string`); 9 | const root = process.cwd(); 10 | const meta = require('../utils/meta'); 11 | 12 | /** 13 | * Reads the content of a file to extract the font matter attributes and anything else. 14 | * Traverses the collections of files collected before, including directories 15 | * 16 | * @param {*} file a collected object representing a file in disk 17 | * @param {*} items the collection where to store the extracted information 18 | * @returns {object} attributes with default placeholders for content combined with thouse found 19 | */ 20 | const extract = (file, items) => { 21 | const stat = fse.statSync(path.normalize(file.path)); 22 | 23 | const item = { 24 | destination: null, 25 | content: null, 26 | stat, 27 | isDir: stat.isDirectory(), 28 | path: file.path, 29 | shortPath: file.shortPath, 30 | relativePath: file.relativePath, 31 | }; 32 | 33 | if (item.isDir) { 34 | items.push(item); 35 | return items; 36 | } 37 | 38 | let content = ``; 39 | try { 40 | content = fse.readFileSync(file.path, `utf-8`); 41 | } catch (err) { 42 | if (err) { 43 | throw err; 44 | } 45 | } 46 | 47 | const attrs = meta.read(content); 48 | const timestamp = moment 49 | .utc(attrs.date ? attrs.date : stat.birthtime) 50 | .format('X'); 51 | 52 | const destination = { 53 | file: attrs.permalink ? 'index.html' : '', 54 | path: attrs.permalink || item.shortPath, 55 | }; 56 | 57 | items.push({ 58 | ...item, 59 | attrs, 60 | destination, 61 | content: attrs.raw.body, 62 | meta: { timestamp, ...content.attributes }, 63 | }); 64 | return items; 65 | }; 66 | 67 | /** 68 | * Combines stats and metadata to extract as much information before processing 69 | * 70 | * @param {*} project 71 | * @returns 72 | */ 73 | const run = (project) => { 74 | const promise = new Promise((resolve, reject) => { 75 | if (!project.collected || !project.collected.include) { 76 | logger.error('Missing collected property in project at extract step'); 77 | return reject(); 78 | } 79 | 80 | const items = { 81 | include: [], 82 | theme: [], 83 | copy: [], 84 | }; 85 | 86 | project.collected.include.map((c) => { 87 | extract(c, items.include); 88 | }); 89 | 90 | project.collected.copy.map((c) => { 91 | extract(c, items.copy); 92 | }); 93 | 94 | project.collected.theme.map((c) => { 95 | extract(c, items.theme); 96 | }); 97 | 98 | project.extracted = items; 99 | return resolve(project); 100 | }); 101 | 102 | return promise; 103 | }; 104 | 105 | module.exports = { 106 | run, 107 | }; 108 | -------------------------------------------------------------------------------- /lib/commands/init.js: -------------------------------------------------------------------------------- 1 | // using variable name $log, because most methods are named the same, 2 | // so even plugin in a diff library (if we decide to) is easier 3 | 4 | const $log = require('../utils/logger'); 5 | const $q = require(`q`); 6 | const fs = require(`../utils/fs`); 7 | const S = require(`string`); 8 | const prompt = require(`prompt-sync`)(); 9 | const chalk = require('chalk'); 10 | const Project = require('../core/project'); 11 | const version = require('../version'); 12 | const path = require(`path`); 13 | 14 | const optionsBuilder = function (argv) { 15 | const layoutsDir = __dirname 16 | .split(`${path.sep}`) 17 | .slice(0, 7) 18 | .concat('layouts') 19 | .join(`${path.sep}`); 20 | if (!fs.existsSync(`${layoutsDir}${path.sep}${argv.layout}`)) { 21 | $log.log(`${argv.layout} doesn't exist, setting layout to default.`); 22 | argv.layout = 'bootstrap'; 23 | } 24 | 25 | return { 26 | name: S(argv.name).slugify().s, 27 | location: `${S(argv.name).slugify().s}`, 28 | longname: S(argv.name).humanize().s, 29 | layout: argv.layout || `bootstrap`, 30 | author: '', 31 | }; 32 | }; 33 | 34 | const inputNameAndLocation = function (argv, project, options) { 35 | if (argv.interactive) { 36 | project.config.name = 37 | options.name || prompt(`Enter short name for the blog: `, options.name); 38 | project.config.location = 39 | options.location || 40 | prompt( 41 | `Enter a folder name if different from [${options.name}]:`, 42 | options.location, 43 | ); 44 | project.config.location = options.location 45 | ? options.location 46 | : `${S(options.name).slugify().s}`; 47 | } 48 | 49 | if (!project.config.name || !project.config.location) { 50 | $log.warn(chalk.red(`A name and location are required`)); 51 | return false; 52 | } 53 | 54 | return true; 55 | }; 56 | 57 | const inputRest = function (argv, project, options) { 58 | if (argv.interactive) { 59 | project.config.author = options.author || prompt(`Author name:`); 60 | project.config.longname = 61 | options.longname || 62 | prompt( 63 | `Enter the longname to use in your site's title: [${options.name}]`, 64 | options.name, 65 | ); 66 | project.config.description = 67 | options.description || 68 | prompt(`Enter the description to use in your site:`); 69 | } 70 | 71 | project.config.version = version; 72 | return true; 73 | }; 74 | 75 | const showSiteInfo = function (project) { 76 | $log.log( 77 | `\n${chalk.bold.cyan( 78 | 'Site will be created with the following information', 79 | )}`, 80 | ); 81 | $log.log(project.getConfig('yaml')); 82 | }; 83 | 84 | const confirmSite = function (argv) { 85 | let confirm = 'Y'; 86 | if (argv.interactive) { 87 | confirm = prompt(`Looks good?: [Yn]`, `Y`); 88 | } 89 | 90 | if (!(confirm === `y` || confirm === `Y`)) { 91 | return false; 92 | } else { 93 | return true; 94 | } 95 | }; 96 | 97 | const fileLocationNotAvailable = function (argv, options) { 98 | const location = `${process.cwd()}${path.sep}${options.location}`; 99 | if ( 100 | fs.existsSync(`${location}${path.sep}_config.yml`) && 101 | argv.force !== true 102 | ) { 103 | confirm = prompt( 104 | chalk.bgRed(`It looks like a site already 105 | exists on that folder, 106 | do you want to overwrite?: [yN]`), 107 | `N`, 108 | ); 109 | if (confirm !== `y` && confirm !== `Y`) { 110 | $log.log(chalk.green(`Aborting!`)); 111 | return true; 112 | } else { 113 | return false; 114 | } 115 | } 116 | }; 117 | 118 | const createSite = function (project) { 119 | project.create({ force: true }).then( 120 | () => { 121 | $log.log( 122 | chalk.green(`Saved! 123 | cd into the directory '${project.config.location}' and run 124 | 'gloria serve' to get started.`), 125 | ); 126 | }, 127 | (err) => { 128 | $log.log(chalk.red(`Error creating project!`), err); 129 | }, 130 | ); 131 | }; 132 | 133 | const handler = function (argv) { 134 | const options = optionsBuilder(argv); 135 | const project = new Project(options); 136 | $log.log( 137 | `Creating new gloria site.\n` + 138 | (options.name 139 | ? `named: ${options.name}, in folder: ./${options.name}` 140 | : ``), 141 | ); 142 | $log.log(`Please complete the following information to continue:`); 143 | if (!inputNameAndLocation(argv, project, options)) { 144 | return; 145 | } 146 | 147 | inputRest(argv, project, options); 148 | showSiteInfo(project); 149 | if (!confirmSite(argv)) { 150 | return; 151 | } 152 | 153 | if (fileLocationNotAvailable(argv, options)) { 154 | return; 155 | } 156 | 157 | createSite(project); 158 | }; 159 | 160 | const builder = { 161 | name: { 162 | default: ``, 163 | description: `Short name to use in the site's configuration, folder name`, 164 | }, 165 | longname: { 166 | default: ``, 167 | description: `A longer name to use in your site's content`, 168 | }, 169 | description: { 170 | default: ``, 171 | description: `Set a description for your site`, 172 | }, 173 | location: { 174 | default: ``, 175 | description: `Folder where your files and site will be at`, 176 | }, 177 | layout: { 178 | default: `bootstrap`, 179 | description: `There are two layouts currently available: 180 | Bootstrap, includes bootstrap css files with several themes form bootswatch 181 | Default, is basically an empty layout.`, 182 | }, 183 | interactive: { 184 | default: true, 185 | description: `Use the interactive prompt to complete the details`, 186 | }, 187 | force: { 188 | default: false, 189 | description: `If files exists, overwrite them?`, 190 | }, 191 | }; 192 | 193 | module.exports = { 194 | command: `init [name]`, 195 | aliases: [`create`], 196 | describe: `Initializes a new site, interactively or using the given parameters. 197 | It will create a base configuration file and sample pages using the desired layout.`, 198 | builder: builder, 199 | handler: handler, 200 | optionsBuilder: optionsBuilder, 201 | showSiteInfo: showSiteInfo, 202 | confirmSite: confirmSite, 203 | inputNameAndLocation: inputNameAndLocation, 204 | inputRest: inputRest, 205 | fileLocationNotAvailable: fileLocationNotAvailable, 206 | createSite: createSite, 207 | }; 208 | -------------------------------------------------------------------------------- /lib/commands/migrate.js: -------------------------------------------------------------------------------- 1 | const $log = require('../utils/logger'); 2 | const chalk = require(`chalk`); 3 | const fse = require(`fs-extra`); 4 | const S = require(`string`); 5 | const path = require(`path`); 6 | const fs = require(`../utils/fs`); 7 | 8 | const Project = require('../core/project'); 9 | const project = new Project(); 10 | 11 | const root = process.cwd(); 12 | 13 | function handler(argv) { 14 | if (argv.source !== 'jekyll') { 15 | return $log.warn(chalk.red(`Sorry, only jekyll is supported now`)); 16 | } 17 | 18 | let dest = project.config.dest || argv.dest; 19 | project.loadConfigFromYamlFile(); 20 | project.change({ 21 | dest: dest, 22 | longname: project.config.title, 23 | }); 24 | project.saveYAML(true, root); 25 | dest = `${root}/${dest}`; 26 | 27 | if (argv.exporto.lastIndexOf(`..`, 0) === 0) { 28 | $log.log(`Copying files to destination folder ${root}/${argv.exporto}`); 29 | fse.copySync(root, dest); 30 | } 31 | 32 | fse.walk(`${root}`).on('data', (item) => { 33 | if (S(item.path).include(dest)) { 34 | return; 35 | } 36 | 37 | item.isDirectory = item.stats.isDirectory(); 38 | item.shortPath = S(item.path).chompLeft(root).s; 39 | item.name = path.basename(item.path); 40 | item.basePath = S(item.shortPath).chompRight(item.name).s; 41 | item.extension = path.extname(item.path); 42 | if ( 43 | item.shortPath.lastIndexOf(`/.git`, 0) === 0 && 44 | item.name !== `.gitignore` 45 | ) { 46 | return; 47 | } 48 | 49 | if (item.shortPath.lastIndexOf(`/_site`, 0) === 0) { 50 | return; 51 | } 52 | 53 | if ([`.html`, `.md`].indexOf(item.extension) !== -1) { 54 | $log.log(`migrating file`, item.path); 55 | let content = fse.readFileSync(item.path, `utf-8`); 56 | 57 | // content = convert('{liquid}') 58 | content = content.replace(/{%\s*include/g, `{{>`); 59 | content = content.replace(/{%\s*if/g, `{{#if`); 60 | content = content.replace(/{%\s*else/g, `{{^`); 61 | content = content.replace(/{%\s*endif/g, `{{/if`); 62 | content = content.replace(/\|.+}}/g, `}}`); 63 | content = content.replace(/{%/g, `{{`); 64 | content = content.replace(/%}/g, `}}`); 65 | try { 66 | fs.writeFileSync(item.path, content); 67 | } catch (err) { 68 | throw err; 69 | } 70 | 71 | $log.log(`Migrated file `, item.path); 72 | } 73 | }); 74 | } 75 | 76 | const builder = { 77 | source: { 78 | default: `jekyll`, 79 | description: `Source platform.`, 80 | }, 81 | exporto: { 82 | default: false, 83 | description: `When specified, the folder where the new files will be exported`, 84 | }, 85 | dest: { 86 | default: 'docs', 87 | }, 88 | }; 89 | 90 | module.exports = { 91 | command: `migrate [source]`, 92 | aliases: [], 93 | describe: `Migrates an existing website from a different platform to gloria. 94 | I'ts pretty buggy now and only works with jekyll. 95 | The replacement is pretty poor right now, it uses regex to find some liquid tags 96 | and replaces them with handlebars. It ignores some helpers like loops. 97 | It requires some extra manual work. If that's not cool with you, please consider a PR.`, 98 | builder: builder, 99 | handler: handler, 100 | }; 101 | -------------------------------------------------------------------------------- /lib/commands/new.js: -------------------------------------------------------------------------------- 1 | /** @module new */ 2 | 3 | const $log = require('../utils/logger'); 4 | const S = require('string'); 5 | const chalk = require('chalk'); 6 | const path = require('path'); 7 | 8 | // the object meta holds information about the different types that can be 9 | // created, like it's location, template and whether or not it uses frontmatter 10 | 11 | const meta = require('./new/meta'); 12 | const fs = require('../utils/fs'); 13 | const createContent = require(`./new/create-content`); 14 | 15 | /** @function 16 | * @name handler 17 | * 18 | * @param argv Command line arguments, pased to this function by the argv package 19 | */ 20 | function handler(argv) { 21 | const options = argv; 22 | options.name = S(options.name || options.title).dasherize().s; 23 | if (!meta[options.type]) { 24 | return $log.error( 25 | chalk.red(`Invalid type provided, please use one of: 26 | post|page|layout|partial|sass|css|public`), 27 | ); 28 | } 29 | 30 | Object.assign(options, meta[options.type]); 31 | options.ext = path.extname(options.name) 32 | ? path.extname(options.name) 33 | : options.ext; 34 | options.path = `.${path.sep}${options.folder + path.sep}${options.dest}`; 35 | options.fullPath = `${options.path}${options.name}${options.ext}`; 36 | const file = createContent(options); 37 | 38 | fs.ensureDir(path.dirname(options.fullPath)) 39 | .then(function () { 40 | if (argv.verbose) { 41 | $log.log( 42 | chalk.green(`Folder ${path.dirname(options.fullPath)} verified.`), 43 | ); 44 | } 45 | 46 | return fs.writeFile(options.fullPath, file.content); 47 | }) 48 | .then(function (data, err) { 49 | if (argv.verbose) { 50 | $log.log(chalk.green(`File ${options.fullPath} created.`)); 51 | } 52 | }); 53 | 54 | return options; 55 | } 56 | 57 | const builder = { 58 | type: { 59 | default: `post`, 60 | description: `Type of content to create. 61 | post|page|layout|partial|sass|css|public are available.`, 62 | }, 63 | title: { 64 | default: '', 65 | description: 'Title for the page or content.', 66 | }, 67 | name: { 68 | default: '', 69 | description: 'Name for the file, if none is provided, title will be used.', 70 | }, 71 | description: { 72 | default: '', 73 | description: 'Description for the page or content.', 74 | }, 75 | category: { 76 | default: '', 77 | description: 'Category, usually included in posts.', 78 | }, 79 | verbose: { 80 | default: true, 81 | description: `Supress logs and warnings`, 82 | }, 83 | folder: { 84 | default: '', 85 | description: `Can be used to prepend to the directory where the file is created.`, 86 | type: 'path', 87 | }, 88 | }; 89 | 90 | module.exports = { 91 | command: `new [type] [title]`, 92 | aliases: [`n`], 93 | describe: `Creates new content, with different templates, depending on the type. 94 | Examples: 95 | gloria new post hello-world 96 | gloria new --type=page --title='Contact Us' --description='Contact form.' `, 97 | builder: builder, 98 | handler: handler, 99 | }; 100 | -------------------------------------------------------------------------------- /lib/commands/new/create-content.js: -------------------------------------------------------------------------------- 1 | /** @module new/createContent 2 | * 3 | * It has the methods necesary to build content for the new command, 4 | * it uses the options and templates pre-defined to create the file. 5 | * It will return the string that will be the content of the file. 6 | */ 7 | 8 | const YAML = require('yamljs'); 9 | 10 | const buildHeaders = function buildHeaders(options) { 11 | return { 12 | title: options.title || '', 13 | description: options.description || '', 14 | type: options.type || '', 15 | layout: options.layout || '', 16 | category: options.category || '', 17 | url: (options.category || '') + options.name, 18 | }; 19 | }; 20 | 21 | module.exports = function createContent(options) { 22 | let headers; 23 | let content = ``; 24 | const body = ``; 25 | if (options.fm !== false) { 26 | headers = buildHeaders(options); 27 | content = `---\n${YAML.stringify(headers)}\n---`; 28 | } 29 | 30 | return { 31 | headers: headers, 32 | body: body, 33 | content: content, 34 | }; 35 | }; 36 | -------------------------------------------------------------------------------- /lib/commands/new/meta.js: -------------------------------------------------------------------------------- 1 | /** @module meta 2 | * 3 | * Is an object that holds the information required by the `new` command. 4 | * Every type of content that can be created will have options like location, 5 | * extension and whether or not it uses frontmatter 6 | */ 7 | 8 | const path = require(`path`); 9 | 10 | module.exports = { 11 | page: { dest: ``, ext: `.html` }, 12 | post: { dest: `_posts${path.sep}`, ext: `.html` }, 13 | partial: { dest: `_includes${path.sep}`, fm: false, ext: `.html` }, 14 | include: { dest: `_includes${path.sep}`, fm: false, ext: `.html` }, 15 | layout: { dest: `_layout${path.sep}`, fm: false, ext: `.html` }, 16 | sass: { dest: `_sass${path.sep}`, fm: false, ext: `.scss` }, 17 | css: { dest: `_public${path.sep}css${path.sep}`, fm: false, ext: `.css` }, 18 | public: { dest: `_public${path.sep}`, fm: false }, 19 | }; 20 | -------------------------------------------------------------------------------- /lib/commands/prebuild.js: -------------------------------------------------------------------------------- 1 | const klaw = require('klaw'); 2 | const $q = require('q'); 3 | const logger = require('../utils/logger'); 4 | const fm = require('front-matter'); 5 | const path = require('path'); 6 | const fse = require('fs-extra'); 7 | const moment = require('moment'); 8 | const S = require(`string`); 9 | const root = process.cwd(); 10 | const meta = require('../utils/meta'); 11 | const compilers = require('../utils/compilers').compilers; 12 | const rejectPrebuild = require('../utils/filters').rejectPrebuild; 13 | 14 | /** 15 | * Reads from the available information and generates a string where the file goes 16 | * @param {*} item 17 | * @param {string} item.destination.path 18 | * @param {string} item.destination.file 19 | * @returns {string} destination to write content to 20 | */ 21 | const calculateDestination = (item) => { 22 | const { destination } = item; 23 | const { path, file } = destination; 24 | 25 | if (item.shortPath.endsWith('CNAME')) { 26 | return 'CNAME'; 27 | } 28 | 29 | if (!path && !file) { 30 | return ''; 31 | } 32 | 33 | if (!path) { 34 | return '' + file; 35 | } 36 | 37 | if (typeof path !== 'string') { 38 | return ''; 39 | } 40 | 41 | if (typeof path === 'string' && typeof file === 'string') { 42 | return `${destination.path.replace(/\/$/, '')}/${destination.file}`.replace( 43 | /\/$/, 44 | '', 45 | ); 46 | } 47 | 48 | return 'index.html'; 49 | }; 50 | 51 | /** 52 | * Reads the content of a file to extract the font matter attributes and anything else. 53 | * Traverses the collections of files collected before, including directories 54 | * 55 | * @param {*} file a collected object representing a file in disk 56 | * @param {*} items the collection where to store the extracted information 57 | * @returns {object} attributes with default placeholders for content combined with thouse found 58 | */ 59 | const prebuild = (item, items) => { 60 | const result = { 61 | item, 62 | compile_method: '', 63 | }; 64 | 65 | if (rejectPrebuild(item)) { 66 | logger.log(`Ignoring ${item.shortPath}`); 67 | return items; 68 | } 69 | 70 | result.destination = calculateDestination(item); 71 | result.compile_method = compilers(item); 72 | 73 | items.files.push(result); 74 | 75 | return items; 76 | }; 77 | 78 | /** 79 | * Combines stats and metadata to extract as much information before processing 80 | * 81 | * @param {*} project 82 | * @returns 83 | */ 84 | const run = (project) => { 85 | const promise = new Promise((resolve, reject) => { 86 | if (!project.collected || !project.collected.include) { 87 | logger.error('Missing collected property in project at extract step'); 88 | return reject(); 89 | } 90 | 91 | /** 92 | * Contains all the files, the keys are the path where it will be written. 93 | * items with matching urls will be overwritten by others processed later 94 | * is not a bug but it can confuse people 95 | */ 96 | const items = { 97 | styles: {}, 98 | scripts: {}, 99 | files: [], 100 | data: { 101 | site: project.config, 102 | head: {}, 103 | }, 104 | }; 105 | 106 | project.extracted.theme.map((c) => { 107 | prebuild(c, items); 108 | }); 109 | 110 | project.extracted.copy.map((c) => { 111 | prebuild(c, items); 112 | }); 113 | 114 | project.extracted.include.map((c) => { 115 | prebuild(c, items); 116 | }); 117 | 118 | project.prebuilt = items; 119 | 120 | return resolve(project); 121 | }); 122 | 123 | return promise; 124 | }; 125 | 126 | module.exports = { 127 | run, 128 | }; 129 | -------------------------------------------------------------------------------- /lib/commands/serve.js: -------------------------------------------------------------------------------- 1 | const $log = require('../utils/logger'); 2 | const chalk = require(`chalk`); 3 | const express = require('express'); 4 | const path = require('path'); 5 | const watch = require('node-watch'); 6 | const Project = require('../core/project'); 7 | const project = new Project(); 8 | const build = require(`./build`); 9 | const open = require('open'); 10 | const root = process.cwd(); 11 | 12 | const initializeSite = function (dest) { 13 | $log.log(`Preparing to serve static files from: ${root}${path.sep}${dest}`); 14 | return express(); 15 | }; 16 | 17 | const serveStaticAssets = function (app, dest) { 18 | return app.use(express.static(`${root}${path.sep}${dest}`)); 19 | }; 20 | 21 | const setRoutes = function (app, dest) { 22 | return app.all('*', (req, res, next) => { 23 | $log.log('loading: ', req.originalUrl); 24 | res.sendFile(`${root}${path.sep}${dest}/404.html`); 25 | }); 26 | }; 27 | 28 | const serveAndBuildSite = function (argv, dest) { 29 | const app = initializeSite(dest); 30 | argv.dest = dest; 31 | build.handler(argv); 32 | serveStaticAssets(app, dest); 33 | setRoutes(app, dest); 34 | return app; 35 | }; 36 | 37 | const watchSourceFiles = function (dest, argv) { 38 | const ignore = new RegExp(`node_modules|${dest}|\.git`); 39 | $log.info(chalk.cyan(`Watching ${root} folder, ignoring ${ignore}`)); 40 | watch( 41 | root, 42 | { 43 | filter: (name) => !ignore.test(name), 44 | }, 45 | (file) => { 46 | $log.log(`File`, file, ` changed`); 47 | try { 48 | build.handler(Object.assign({ save: false }, argv)); 49 | } catch (e) { 50 | $log.log('Failed building project'); 51 | $log.log(e); 52 | } 53 | }, 54 | ); 55 | }; 56 | 57 | const launchSite = function (argv, app, dest) { 58 | // Using an arbitrary number to wait for files to build 59 | setTimeout(() => { 60 | if (argv.watch === true) { 61 | watchSourceFiles(dest, argv); 62 | } 63 | 64 | app.listen(argv.port, function () { 65 | $log.warn( 66 | chalk.green(`Serving static files from ${root}${path.sep}${dest}`), 67 | ); 68 | $log.warn(chalk 69 | .bold.red`Never use this in production. See the deployment section 70 | in the documentation for more information about deployment.`); 71 | $log.warn(chalk.green(`listening in port ${argv.port}`)); 72 | if (!argv.suppressBrowser) { 73 | open('http://localhost:' + argv.port); 74 | } 75 | }); 76 | }, 1 * 1000); 77 | }; 78 | 79 | const handler = function (argv) { 80 | argv = argv || {}; 81 | if (argv.silent) { 82 | $log.silent(); 83 | } 84 | 85 | const options = project.loadConfig('yaml'); 86 | const dest = project.config.dest || argv.dest; 87 | const app = serveAndBuildSite(argv, dest); 88 | launchSite(argv, app, dest); 89 | }; 90 | 91 | const builder = { 92 | dest: { 93 | default: `site`, 94 | description: `Destination path or folder to serve the site from.`, 95 | }, 96 | port: { 97 | default: '3300', 98 | description: `Port on which to serve the site.`, 99 | }, 100 | 'suppress-browser': { 101 | default: false, 102 | description: `Don't open the browser automatically.`, 103 | type: 'boolean', 104 | alias: 'b', 105 | }, 106 | watch: { 107 | default: true, 108 | description: `Watchs the source files for changes, and re-builds the site.`, 109 | alias: 'w', 110 | type: 'boolean', 111 | }, 112 | clear: { 113 | default: false, 114 | description: `Removes the current content of the source directory`, 115 | alias: 'c', 116 | type: 'boolean', 117 | }, 118 | silent: { 119 | default: false, 120 | description: `Limit the amount of output to the console.`, 121 | alias: 's', 122 | type: 'boolean', 123 | }, 124 | }; 125 | 126 | module.exports = { 127 | command: `serve [dest]`, 128 | aliases: [], 129 | describe: `Serves the site from the last known destination, or from the specific folder given.`, 130 | builder: builder, 131 | handler: handler, 132 | serveAndBuildSite: serveAndBuildSite, 133 | initializeSite: initializeSite, 134 | serveStaticAssets: serveStaticAssets, 135 | setRoutes: setRoutes, 136 | watchSourceFiles: watchSourceFiles, 137 | launchSite: launchSite, 138 | }; 139 | -------------------------------------------------------------------------------- /lib/commands/setup.js: -------------------------------------------------------------------------------- 1 | const $q = require('q'); 2 | const logger = require('../utils/logger'); 3 | const config = require('../core/project.config'); 4 | const root = process.cwd(); 5 | const path = require('path'); 6 | 7 | // Actual collect file that the bin file calls after it processed the arguments or whatever 8 | const run = (project) => { 9 | const { writeYaml } = config; 10 | 11 | // @TODO: create folders described in _config files to avoid errors during first run 12 | 13 | writeYaml(project.config); 14 | logger.log(project.config); 15 | return $q.resolve(); 16 | }; 17 | 18 | module.exports = { 19 | run, 20 | }; 21 | -------------------------------------------------------------------------------- /lib/commands/write.html.js: -------------------------------------------------------------------------------- 1 | const logger = require('../utils/logger'); 2 | const upath = require('upath'); 3 | const fs = require('fs'); 4 | 5 | /** 6 | * Writes the HTHL property from the project to disk 7 | * 8 | * @param {*} project 9 | * @returns project 10 | */ 11 | const run = (project) => { 12 | const promise = new Promise((resolve, reject) => { 13 | const { html, config } = project; 14 | const { dest } = config; 15 | 16 | if (!html) { 17 | logger.error('Missing html data at css step'); 18 | return reject(); 19 | } 20 | 21 | if (!fs.existsSync(dest)) { 22 | fs.mkdirSync(dest, { recursive: true }); 23 | } 24 | 25 | html.map((file) => { 26 | const destination = upath.normalize(`${dest}/${file.destination}`); 27 | 28 | const parsed = upath.parse(destination); 29 | 30 | logger.log({ "Looking for dir:": parsed.dir }); 31 | if (!fs.existsSync(parsed.dir)) { 32 | logger.log({ "Created dir:": parsed.dir }); 33 | fs.mkdirSync(parsed.dir, { recursive: true }); 34 | } 35 | 36 | logger.log({ "Writing to dest:": destination }); 37 | 38 | fs.writeFileSync(destination, file.content); 39 | }); 40 | 41 | // project.extracted.copy.map((file) => { 42 | // const destination = upath.normalize(`${dest}/${file.relativePath}`); 43 | // const parsed = upath.parse(destination); 44 | 45 | // if (parsed.dir && !fs.existsSync(parsed.dir)) { 46 | // fs.mkdirSync(parsed.dir, { recursive: true }); 47 | // } 48 | 49 | // if (!file.isDir) { 50 | // fs.writeFileSync(destination, file.content); 51 | // } 52 | 53 | // }); 54 | 55 | return resolve(project); 56 | }); 57 | 58 | return promise; 59 | }; 60 | 61 | module.exports = { 62 | run, 63 | }; 64 | -------------------------------------------------------------------------------- /lib/core/project.config.js: -------------------------------------------------------------------------------- 1 | const logger = require('../utils/logger'); 2 | const { v4: uuidv4 } = require('uuid'); 3 | const yaml = require(`yamljs`); 4 | const fs = require(`../utils/fs`); 5 | const path = require('path'); 6 | // import chalk = require('chalk'); 7 | 8 | const loadConfigFromYamlFile = () => { 9 | var root = process.cwd(); 10 | if (!fs.existsSync(path.normalize(`${root}/_config.yml`))) { 11 | //require(`yargs`) 12 | logger.warn(`${root}/_config.yml was not found`); 13 | return {}; 14 | } 15 | 16 | const config = yaml.load(path.normalize(`${root}/_config.yml`)); 17 | 18 | return config; 19 | }; 20 | 21 | const writeConfigToYamlFile = (config, overwrite = false) => { 22 | var root = process.cwd(); 23 | const configPath = path.normalize(`${root}/_config.yml`); 24 | 25 | if (!fs.existsSync(configPath)) { 26 | //require(`yargs`) 27 | logger.warn(`${root}/_config.yml already exists`); 28 | return {}; 29 | } 30 | 31 | const configString = yaml.stringify(config); 32 | 33 | fs.writeFile(path.normalize(`${root}/_config.yml`), configString, (err) => { 34 | if (err) { 35 | return logger.error(err); 36 | } 37 | logger.log('_config.yml was created!'); 38 | }); 39 | return {}; 40 | }; 41 | 42 | const defaults = { 43 | include: ['pages'], 44 | name: 'Slava', 45 | tagline: 'A website generated with gloriaJs', 46 | author: 'gloriajs', 47 | dest: 'build', 48 | copy: ['public'], 49 | exclude: ['.draft.md'], 50 | theme: ['layouts/tailwind'], 51 | css: 'tailwind', 52 | engine: 'default', 53 | version: '2', 54 | }; 55 | 56 | /** 57 | * Call to read project configuration information from the defaults and the config files 58 | * 59 | * @returns {object} configuration settings for the project 60 | */ 61 | const get = () => { 62 | const defaultConfig = defaults; 63 | const yamlConfig = loadConfigFromYamlFile(); 64 | 65 | /** 66 | * Configuration settings for the project. 67 | * Combined settings from defaults and _config.yml 68 | */ 69 | const config = { 70 | _hash: uuidv4(), 71 | ...defaultConfig, 72 | ...yamlConfig, 73 | }; 74 | 75 | return config; 76 | }; 77 | 78 | module.exports = { 79 | get: get, 80 | getYaml: loadConfigFromYamlFile, 81 | writeYaml: writeConfigToYamlFile, 82 | defaults, 83 | }; 84 | -------------------------------------------------------------------------------- /lib/core/project.js: -------------------------------------------------------------------------------- 1 | const $log = require('../utils/logger'); 2 | const $q = require(`q`); 3 | const yaml = require(`yamljs`); 4 | const fs = require(`../utils/fs`); 5 | // const chalk = require(`chalk`); 6 | const path = require(`path`); 7 | 8 | function Project(config) { 9 | this.config = config || {}; 10 | this.pages = []; 11 | this.posts = []; 12 | this.count = { 13 | others: 0, 14 | }; 15 | } 16 | 17 | Project.prototype.getConfig = function (format) { 18 | if (format === 'yaml') { 19 | return yaml.stringify(this.config, 4); 20 | } 21 | return {}; 22 | }; 23 | 24 | Project.prototype.loadConfigFromYamlFile = function () { 25 | var root = process.cwd(); 26 | if (!fs.existsSync(path.normalize(`${root}/_config.yml`))) { 27 | $log.warn((`${root}/_config.yml was not found`)); 28 | return; 29 | } 30 | 31 | const config = yaml.load(path.normalize(`${root}/_config.yml`)); 32 | if ( 33 | fs.existsSync(path.normalize(`${root}/themes/${config.theme}/_config.yml`)) 34 | ) { 35 | this.themeConfig = yaml.load( 36 | path.normalize(`${root}/themes/${config.theme}/_config.yml`), 37 | ); 38 | } 39 | 40 | this.config = Object.assign({}, this.themeConfig, config); 41 | return this.config; 42 | }; 43 | 44 | Project.prototype.loadConfig = function (format) { 45 | if (format === 'yaml') { 46 | return this.loadConfigFromYamlFile(); 47 | } 48 | }; 49 | 50 | Project.prototype.getLocation = function () { 51 | if (!this.config.location) { 52 | throw 'invalid location in configuration'; 53 | } 54 | 55 | return `${process.cwd()}/${this.config.location}`; 56 | }; 57 | 58 | Project.prototype.change = function (options) { 59 | Object.assign(this.config, options); 60 | return options; 61 | }; 62 | 63 | Project.prototype.saveYAML = function (force, location) { 64 | const deferred = $q.defer(); 65 | location = location || this.getLocation(); 66 | $log.log(`Saving configuration to file ${location}/_config.yml`); 67 | if (fs.existsSync(`${location}/_config.yml`) && force !== true) { 68 | return $q.reject({ error: `Project exist, won't override` }); 69 | } 70 | 71 | fs.writeFileSync(`${location}/_config.yml`, this.getConfig(`yaml`), { 72 | flag: 'w', 73 | }); 74 | return $q.when({}); 75 | }; 76 | 77 | Project.prototype.create = function (options) { 78 | options = options || {}; 79 | const _this = this; 80 | const deferred = $q.defer(); 81 | let layout = _this.config.layout || 'default'; 82 | layout = `${__dirname}/../../layouts/${this.config.layout}/`; 83 | var location = this.getLocation(); 84 | 85 | if (!_this.config) { 86 | return $q.reject({ error: `Invalid configuration data` }); 87 | } 88 | 89 | if (!_this.config.location) { 90 | return $q.reject({ error: `Invalid location given` }); 91 | } 92 | 93 | if ( 94 | fs.existsSync(`${_this.config.location}/_config.yml`) && 95 | options.force !== true 96 | ) { 97 | return $q.reject({ error: `Project exist, won't override` }); 98 | } 99 | 100 | if (!fs.existsSync(location)) { 101 | fs.mkdirSync(location); 102 | $log.log(`Created new folder ${location}`); 103 | } 104 | 105 | if (!fs.existsSync(layout)) { 106 | return $q.reject({ 107 | error: `Selected layout ${_this.config.layout} doesn't exist`, 108 | }); 109 | } 110 | 111 | fs.copy(layout, location).then(function (err) { 112 | if (err) { 113 | return deferred.reject({ 114 | error: `failed copying layout files`, 115 | err: err, 116 | }); 117 | } 118 | 119 | $log.log(`Copied layout files into project folder.`); 120 | _this.saveYAML(options.force); 121 | deferred.resolve(); 122 | }); 123 | 124 | return deferred.promise; 125 | }; 126 | 127 | module.exports = Project; 128 | -------------------------------------------------------------------------------- /lib/utils/compilers.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const marked = require('marked'); 3 | const hbs = require('./handlebars'); 4 | const logger = require('./logger'); 5 | 6 | /** 7 | * Find layout file from collected ones, and parse html to interpolate the current file into it 8 | * @param {*} item 9 | * @param {*} files 10 | * @returns html string 11 | */ 12 | const getLayoutHTML = (item, files) => { 13 | let html = '{{{content}}}'; 14 | 15 | if (!item.attrs.layout) { 16 | return html; 17 | } 18 | 19 | const layout_file = files.find( 20 | (file) => file.item.attrs.name === item.attrs.layout, 21 | ); 22 | 23 | if (layout_file) { 24 | html = marked.parse(layout_file.item.content); 25 | } 26 | 27 | return html; 28 | }; 29 | 30 | /** 31 | * Interpolates the content of the paage and available data into the layout and returns the full content of the page 32 | * @param {*} layout 33 | * @param {*} item 34 | * @param {*} data 35 | * @param {[]} files 36 | * @returns html string 37 | */ 38 | const interpolateLayoutAndContent = (layout, item, data, files) => { 39 | // currently no support for nested layouts 40 | const html = marked.parse(item.content); 41 | // compile template using site data and content of page 42 | const layout_template = hbs.compile(layout); 43 | const layout_with_html = layout_template({ ...data, content: html, files }); 44 | 45 | // replace variables with values (handlebars) in full content 46 | const template = hbs.compile(layout_with_html); 47 | const result = template({ ...data, files }); 48 | 49 | return result; 50 | }; 51 | 52 | /** 53 | * Has a dictionary with keys as the file extensions and methods that process the content 54 | * of the file to create the pages, currently supporting markdown. 55 | * CSS due to tailwind, is processed globally after passing thru all the items 56 | * 57 | * @param item {object} 58 | */ 59 | const compilers = (item) => { 60 | const ext = path.extname(item.shortPath); 61 | 62 | const noop = (file) => { 63 | const { item, destination } = file; 64 | return { 65 | destination, 66 | content: item.content || '', 67 | }; 68 | }; 69 | 70 | const options = { 71 | '.md': function (file, files, data) { 72 | const { item, destination } = file; 73 | // get html from layout 74 | const layout = getLayoutHTML(item, files); 75 | // compile md to html and include in layout 76 | const result = interpolateLayoutAndContent(layout, item, data, files); 77 | logger.info(`Compiled: ${item.shortPath}`); 78 | 79 | return { 80 | destination, 81 | content: result, 82 | }; 83 | }, 84 | '.html': function (file, files, data) { 85 | const { item, destination } = file; 86 | // get html from layout 87 | const layout = getLayoutHTML(item, files); 88 | // compile md to html and include in layout 89 | const result = interpolateLayoutAndContent(layout, item, data, files); 90 | logger.info(`Compiled: ${item.shortPath}`); 91 | 92 | return { 93 | destination, 94 | content: result, 95 | }; 96 | }, 97 | }; 98 | 99 | if (options[ext]) { 100 | return options[ext]; 101 | } 102 | console.log(`${item.shortPath} has no associated compiler`); 103 | 104 | return noop; 105 | }; 106 | 107 | module.exports = { 108 | compilers, 109 | }; 110 | -------------------------------------------------------------------------------- /lib/utils/configstore.js: -------------------------------------------------------------------------------- 1 | const Configstore = require('configstore'); 2 | var pkg = require('../../package.json'); 3 | 4 | // Init a Configstore instance with an unique ID eg. package name 5 | // and optionally some default values 6 | module.exports = new Configstore(pkg.name); 7 | -------------------------------------------------------------------------------- /lib/utils/filters.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the rules we use to filter files during the prebuild process 3 | * @param {object} item representing a file and its attributes 4 | */ 5 | const rejectPrebuild = (item /* project */) => { 6 | if (item.isDir) { 7 | return true; 8 | } 9 | 10 | if (item.path.endsWith('.draft.md')) { 11 | return true; 12 | } 13 | }; 14 | 15 | module.exports = { 16 | rejectPrebuild, 17 | }; 18 | -------------------------------------------------------------------------------- /lib/utils/fs.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs-promise'); 2 | 3 | module.exports = fs; 4 | -------------------------------------------------------------------------------- /lib/utils/handlebars.js: -------------------------------------------------------------------------------- 1 | const hbs = require("handlebars"); 2 | const logger = require('../utils/logger'); 3 | 4 | const createFilter = (conditions, context, options) => { 5 | let s = ''; 6 | const [filter_key, filter_value] = conditions; 7 | 8 | const filtered = context.reverse().filter((a) => { 9 | return a.item.attrs[filter_key] === filter_value; 10 | }); 11 | 12 | filtered.forEach((item) => { 13 | s += options.fn(item); 14 | }); 15 | 16 | return s; 17 | }; 18 | 19 | /** 20 | * @param {array} items the pages attribute after the prebuild step 21 | * 22 | * @returns {string} a list of blog posts in creation order html with the interpolated block 23 | */ 24 | hbs.registerHelper('blog_posts', (context, options) => { 25 | return createFilter(['type', 'post'], context, options); 26 | }); 27 | 28 | /** 29 | * @param {array} items the pages attribute after the prebuild step 30 | * 31 | * @returns {string} a list of blog posts in creation order html with the interpolated block 32 | */ 33 | hbs.registerHelper('pages_in_nav', (context, options) => { 34 | return createFilter(['nav', true], context, options); 35 | }); 36 | 37 | module.exports = hbs; 38 | -------------------------------------------------------------------------------- /lib/utils/logger.js: -------------------------------------------------------------------------------- 1 | const defaultLoggers = { 2 | log: console.log, 3 | info: console.info, 4 | warn: console.warn, 5 | error: console.error, 6 | time: console.time, 7 | timeEnd: console.timeEnd, 8 | profile: () => '', 9 | }; 10 | 11 | const logger = defaultLoggers; 12 | 13 | logger.verbose = Object.assign({}, defaultLoggers); 14 | 15 | logger.silent = function () { 16 | logger.log = () => ''; 17 | logger.info = () => ''; 18 | }; 19 | 20 | module.exports = logger; 21 | -------------------------------------------------------------------------------- /lib/utils/meta.js: -------------------------------------------------------------------------------- 1 | const fm = require('front-matter'); 2 | 3 | /** 4 | * Returns the front matter metadata included in the file's content 5 | * 6 | * @param {string} content string with the file content to read front matter attributes 7 | * 8 | * @returns {object} that has placeholders for all the attributes combined with the extracted ones 9 | */ 10 | const read = (content) => { 11 | const attrs = { 12 | links_from: [], 13 | links_to: [], 14 | title: '', 15 | name: '', 16 | layout: '', 17 | description: '', 18 | tags: [], 19 | category: '', 20 | url: '', 21 | raw: {}, 22 | }; 23 | 24 | const data = fm(content); 25 | 26 | /** Good place to extract tags, categories and other things, so they can be classified later */ 27 | return { 28 | ...attrs, 29 | ...data.attributes, 30 | raw: data, 31 | url: data.attributes.permalink, 32 | }; 33 | }; 34 | 35 | module.exports = { 36 | read, 37 | }; 38 | -------------------------------------------------------------------------------- /lib/version.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../package.json').version; 2 | -------------------------------------------------------------------------------- /logs/.gitkeep: -------------------------------------------------------------------------------- 1 | i -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gloriajs", 3 | "version": "2.2.3", 4 | "description": "Gloria is a NodeJS simple static website generator.", 5 | "main": "cli.js", 6 | "bin": "cli.js", 7 | "scripts": { 8 | "test": "touch .", 9 | "tsc": "tsc", 10 | "lint": "eslint '**/*.{js,ts}' --fix", 11 | "prettier": "prettier --write .", 12 | "build": "tsc ", 13 | "gloria": "./bin/gloria" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/gloriajs/gloria.git" 18 | }, 19 | "author": "dvidsilva", 20 | "license": "SEE LICENSE IN LICENSE", 21 | "bugs": { 22 | "url": "https://github.com/gloriajs/gloria/issues" 23 | }, 24 | "homepage": "https://gloriajs.github.io.com/", 25 | "dependencies": { 26 | "@types/yargs": "^17.0.19", 27 | "async": "^2.1.2", 28 | "autoprefixer": "^10.4.13", 29 | "chalk": "^5.2.0", 30 | "configstore": "^2.1.0", 31 | "express": "^4.14.0", 32 | "front-matter": "^2.1.1", 33 | "fs-extra": "^1.0.0", 34 | "fs-promise": "^1.0.0", 35 | "handlebars": "^4.0.6", 36 | "klaw-sync": "^6.0.0", 37 | "liquid-to-handlebars": "^0.3.2", 38 | "marked": "^0.3.6", 39 | "moment": "^2.17.1", 40 | "mz": "^2.5.0", 41 | "node-watch": "^0.4.1", 42 | "open": "0.0.5", 43 | "postcss": "^8.4.21", 44 | "prompt-sync": "^4.1.4", 45 | "q": "^1.4.1", 46 | "request": "^2.79.0", 47 | "request-promise": "^4.1.1", 48 | "semver": "^5.3.0", 49 | "shelljs": "^0.7.5", 50 | "string": "^3.3.3", 51 | "stylus": "^0.54.5", 52 | "tailwindcss": "^3.2.7", 53 | "upath": "^2.0.1", 54 | "uuid": "^9.0.0", 55 | "yaml": "^0.3.0", 56 | "yamljs": "^0.2.8", 57 | "yargs": "^17.6.2" 58 | }, 59 | "devDependencies": { 60 | "@types/node": "^18.11.18", 61 | "@typescript-eslint/eslint-plugin": "^5.48.1", 62 | "@typescript-eslint/parser": "^5.48.1", 63 | "babel-eslint": "^6.1.2", 64 | "chai": "^3.5.0", 65 | "eslint": "^8.31.0", 66 | "eslint-config-prettier": "^8.6.0", 67 | "eslint-plugin-prettier": "^4.2.1", 68 | "mocha": "^3.1.2", 69 | "mocha-eslint": "^3.0.1", 70 | "pkg": "^5.8.0", 71 | "pre-commit": "^1.2.2", 72 | "prettier": "2.8.2", 73 | "typescript": "^4.9.4" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /public/CNAME: -------------------------------------------------------------------------------- 1 | gloriajs.github.io -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gloriajs/gloria/be7d9cbce14c36a191047f4cc0a0622a6b86e19a/public/logo.png -------------------------------------------------------------------------------- /test/commands/build.spec.js: -------------------------------------------------------------------------------- 1 | var should = require('chai').should(); 2 | var expect = require('chai').expect; 3 | 4 | var build = require('../../lib/commands/build'); 5 | 6 | describe('build command is a valid module', function () { 7 | it('will always pass unless build is undefined', function () {}); 8 | }); 9 | 10 | describe('build will refuse certain arguments', function () { 11 | it('will refuse to build if a parent directory is used as destination', function () { 12 | // var attempt = build.handler({ dest: '../' }); 13 | // expect(attempt).to.equal(null); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/commands/index.spec.js: -------------------------------------------------------------------------------- 1 | var should = require('chai').should(); 2 | var gloria = require('../../bin/gloria'); 3 | 4 | describe('version', function () {}); 5 | -------------------------------------------------------------------------------- /test/commands/init.spec.js: -------------------------------------------------------------------------------- 1 | // Look into testing user input 2 | var should = require('chai').should; 3 | var expect = require('chai').expect; 4 | var assert = require('chai').assert; 5 | 6 | var init = require('../../lib/commands/init'); 7 | var fs = require(`../../lib/utils/fs`); 8 | var Project = require('../../lib/core/project'); 9 | 10 | const name = 'sample'; 11 | 12 | describe('init command is a valid module', function () { 13 | it('will always pass unless init is undefined', function () { 14 | expect(true).to.equal(true); 15 | }); 16 | }); 17 | 18 | describe('will run git init with a sample folder', function () { 19 | it(`will create a new project in the specified folder`, function () { 20 | fs.rmdir(name); 21 | init.handler({ name: name, interactive: false, force: true }); 22 | const dir = fs.statSync(name); 23 | expect(dir.isDirectory()).to.equal(true); 24 | }); 25 | }); 26 | 27 | describe('init', function () { 28 | describe('optionsBuilder', function () { 29 | it('should exist', function () { 30 | assert.isFunction(init.optionsBuilder); 31 | }); 32 | it('should have valid keys', function () { 33 | const option = { name: '' }; 34 | expect(init.optionsBuilder(option)).to.have.all.keys( 35 | 'name', 36 | 'location', 37 | 'longname', 38 | 'layout', 39 | 'author', 40 | ); 41 | }); 42 | }); 43 | describe('showSiteInfo', function () { 44 | it('should exist', function () { 45 | assert.isFunction(init.showSiteInfo); 46 | }); 47 | }); 48 | describe('confirmSite', function () { 49 | it('should exist', function () { 50 | assert.isFunction(init.confirmSite); 51 | }); 52 | it('should return true on default', function () { 53 | const interact = { interactive: false }; 54 | assert.equal(init.confirmSite(interact), true); 55 | }); 56 | }); 57 | describe('inputNameAndLocation', function () { 58 | let argv = { interactive: true }; 59 | let project = new Project(); 60 | let options = { 61 | name: 'test', 62 | location: 'test', 63 | }; 64 | it('should exist', function () { 65 | assert.isFunction(init.inputNameAndLocation); 66 | }); 67 | it('should return true if argv input', function () { 68 | assert.equal(init.inputNameAndLocation(argv, project, options), true); 69 | }); 70 | it('should return false if argv.interactive false', function () { 71 | argv = { interactive: false }; 72 | options = { name: '', locations: '' }; 73 | project = new Project(options); 74 | assert.equal(init.inputNameAndLocation(argv, project, options), false); 75 | }); 76 | }); 77 | describe('inputRest', function () { 78 | const argv = { interactive: true, name: 'test' }; 79 | let project = new Project(); 80 | const options = { 81 | author: 'test', 82 | longname: 'test', 83 | description: 'test', 84 | layout: 'bootstrap', 85 | }; 86 | it('should exist', function () { 87 | assert.isFunction(init.inputRest); 88 | }); 89 | it('should return true with all input', function () { 90 | assert.equal(init.inputRest(argv, project, options), true); 91 | }); 92 | it('project.config should have all keys', function () { 93 | Object.assign(options, init.optionsBuilder(argv), { author: 'test' }); 94 | project = new Project(options); 95 | init.inputNameAndLocation(argv, project, options); 96 | init.inputRest(argv, project, options); 97 | expect(project.config).to.have.all.keys( 98 | 'name', 99 | 'location', 100 | 'longname', 101 | 'layout', 102 | 'author', 103 | 'description', 104 | 'version', 105 | ); 106 | }); 107 | }); 108 | describe('fileLocationNotAvailable', function () { 109 | it('should exist', function () { 110 | assert.isFunction(init.fileLocationNotAvailable); 111 | }); 112 | }); 113 | describe('createSite', function () { 114 | it('should exist', function () { 115 | assert.isFunction(init.createSite); 116 | }); 117 | it('should throw error with empty project', function () { 118 | const project = new Project(); 119 | expect(() => init.createSite(project)).to.throw(/invalid location/); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /test/commands/new.spec.js: -------------------------------------------------------------------------------- 1 | const should = require('chai').should; 2 | const expect = require('chai').expect; 3 | const path = require('path'); 4 | 5 | const command = require('../../lib/commands/new'); 6 | const fs = require(`../../lib/utils/fs`); 7 | 8 | const options = { 9 | title: 'hello World', 10 | folder: 'sample', 11 | verbose: false, 12 | type: 'post', 13 | category: 'sample', 14 | }; 15 | 16 | describe('New command is a valid module', function () { 17 | it('will always pass unless command `new` is undefined', function () { 18 | expect(command).to.be.ok; 19 | }); 20 | }); 21 | 22 | describe('will run gloria new with sample options to create sample files:', function () { 23 | const result = command.handler(options); 24 | it(`will ensure the destination folder exists`, function () { 25 | const dir = fs.statSync(result.path); 26 | expect(dir.isDirectory()).to.equal(true); 27 | }); 28 | 29 | it(`will ensure the destination file exists`, function () { 30 | const file = fs.statSync(result.path); 31 | expect(file).to.be.ok; 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/commands/serve.spec.js: -------------------------------------------------------------------------------- 1 | var should = require('chai').should(); 2 | var expect = require('chai').expect; 3 | var assert = require('chai').assert; 4 | 5 | var serve = require('../../lib/commands/serve'); 6 | var Project = require('../../lib/core/project'); 7 | 8 | describe('serve command is a valid module', function () { 9 | it('will always pass unless serve is undefined', () => null); 10 | }); 11 | 12 | describe('Serve', function () { 13 | describe('serveAndBuildSite', function () { 14 | it('exists', function () { 15 | assert.isFunction(serve.serveAndBuildSite); 16 | }); 17 | }); 18 | describe('initializeSite', function () { 19 | it('exists', function () { 20 | assert.isFunction(serve.initializeSite); 21 | }); 22 | it('returns a function', function () { 23 | const dest = ''; 24 | assert.isFunction(serve.initializeSite(dest)); 25 | }); 26 | }); 27 | describe('serveStaticAssets', function () { 28 | it('exists', function () { 29 | assert.isFunction(serve.serveStaticAssets); 30 | }); 31 | }); 32 | describe('setRoutes', function () { 33 | it('exists', function () { 34 | assert.isFunction(serve.setRoutes); 35 | }); 36 | }); 37 | describe('watchSourceFiles', function () { 38 | it('exists', function () { 39 | assert.isFunction(serve.watchSourceFiles); 40 | }); 41 | }); 42 | describe('launchSite', function () { 43 | it('exists', function () { 44 | assert.isFunction(serve.launchSite); 45 | }); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/test.spec.js: -------------------------------------------------------------------------------- 1 | const gloria = require(`../bin/gloria`); 2 | const expect = require(`chai`).expect; 3 | const lint = require(`mocha-eslint`); 4 | 5 | lint([`lib/`, 'test/'], {}); 6 | 7 | describe('gloria is not undefined', function () { 8 | it('will always pass unless gloria is undefined', function () { 9 | expect(gloria).to.not.equal(undefined); 10 | }); 11 | }); 12 | 13 | require('./commands/index.spec'); 14 | require('./commands/init.spec'); 15 | require('./commands/new.spec'); 16 | require('./commands/build.spec'); 17 | require('./commands/serve.spec'); 18 | require('./utils/logger.spec'); 19 | -------------------------------------------------------------------------------- /test/utils/logger.spec.js: -------------------------------------------------------------------------------- 1 | const expect = require('chai').expect; 2 | const $log = require('../../lib/utils/logger'); 3 | 4 | describe('$log', () => { 5 | it('should report back when log function is called on mock logger', () => { 6 | expect(typeof $log.log).to.equal('function'); 7 | }); 8 | 9 | it('should report back when info function is called on mock logger', () => { 10 | expect(typeof $log.info).to.equal('function'); 11 | }); 12 | 13 | it('should report back when error function is called on mock logger', () => { 14 | expect(typeof $log.error).to.equal('function'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2015", 4 | "module": "CommonJS", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "resolveJsonModule": true, 8 | "moduleResolution": "node" 9 | }, 10 | "include": ["cli.ts"] 11 | } 12 | --------------------------------------------------------------------------------