{"id":13424093,"url":"https://github.com/fenwick67/bumbler","last_synced_at":"2025-05-07T23:48:04.376Z","repository":{"id":21354690,"uuid":"89747007","full_name":"fenwick67/bumbler","owner":"fenwick67","description":"A pragmatic static site CMS","archived":false,"fork":false,"pushed_at":"2023-01-11T02:26:11.000Z","size":15874,"stargazers_count":8,"open_issues_count":9,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-20T04:34:15.519Z","etag":null,"topics":["cms","static-site-generator"],"latest_commit_sha":null,"homepage":"http://bumblerexample.pw","language":"CSS","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fenwick67.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-28T21:44:32.000Z","updated_at":"2023-08-06T06:29:56.000Z","dependencies_parsed_at":"2023-01-11T21:10:45.309Z","dependency_job_id":null,"html_url":"https://github.com/fenwick67/bumbler","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenwick67%2Fbumbler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenwick67%2Fbumbler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenwick67%2Fbumbler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fenwick67%2Fbumbler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fenwick67","download_url":"https://codeload.github.com/fenwick67/bumbler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252973624,"owners_count":21834105,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cms","static-site-generator"],"created_at":"2024-07-31T00:00:48.451Z","updated_at":"2025-05-07T23:48:04.358Z","avatar_url":"https://github.com/fenwick67.png","language":"CSS","funding_links":[],"categories":["CSS"],"sub_categories":[],"readme":"# Bumbler\r\n\r\n\u003cp align=\"center\"\u003e\r\n  \u003cimg src=\"https://github.com/fenwick67/bumbler/raw/master/doc/bumbler-text.png\"\u003e\u003c/img\u003e\r\n\u003c/p\u003e\r\n\r\nA straightforward, file-based CMS, optimized for chronological content like podcasts, blogging, comics etc. Currently in alpha state, but I use it for my homepage [fenwick.pizza](https://fenwick.pizza).\r\n\r\nLook at the example deployment [here](https://bumblerexample.pw/), and check out some example themes [here](http://fenwick67.github.io/bumbler-themes).\r\n\r\n# Philosophy\r\n\r\nBumbler tries to take the ease-of-use of a CMS and combines it with the performance and elegance of a static site generator.\r\n\r\nHistorically CMSes are easy for users to use, but complex. This complexity can lead to performance issues, security problems, and maintenance headaches. Static site generators are powerful, and performant at runtime, but you have to be a developer to use them, and require a lot of \"fiddling\". Maintaining your own front matter boilerplate scheme, worrying about files being referenced correctly, and generating an atom feed can be a pain.\r\n\r\n# Screenshots\r\n\r\n![admin screenshot](https://github.com/fenwick67/bumbler/raw/master/doc/admin-screenshot.png)\r\n\r\n![screenshot](https://github.com/fenwick67/bumbler/raw/master/doc/screenshot.png)\r\n\r\n# Features\r\n\r\n* Builds to static files for ＭＡＸＳＰＥＥＤ, portability, and easy administration\r\n* No arcane front-matter magic, directory structure, or commandline wizardry required\r\n* Batteries included. Includes support for\r\n  * tags\r\n  * custom pages\r\n  * post attachments and featured images\r\n  * static file linking\r\n  * zero configuration Atom feed\r\n  * [easy theming](http://fenwick67.github.io/bumbler-themes)\r\n  * favicon generation\r\n* Two ways to publish:\r\n  1. Self-host and instantly publish from anywhere, anytime\r\n  2. Write locally and push to github-pages, Neocities, or any other system\r\n\r\n## Non-Features\r\n\r\nThese are things that Bumbler *intentionally lacks* and will likely **never** support:\r\n\r\n* compiling stylesheets for you\r\n* multi-user login\r\n* nested page templates\r\n* in-page editing tools\r\n* built-in social features (comments etc)\r\n* built-in analytics\r\n* file conversion (video transcoding, image resizing etc. Favicons are an exception because they're a huge pain.)\r\n* built-in search\r\n\r\n# User guide\r\n\r\n## Installation\r\n\r\nstep 0: install Node.js and NPM\r\n\r\n1. Create a new folder for your site and open a terminal inside it\r\n2. `npm install -g bumbler`\r\n    * Windows Users: You will need to either enable \"Developer Mode\" (windows 10 only), or run the following commands as administrator, so that the symlinks can be created.\r\n3. Run `bumbler init` and follow the directions\r\n4. Run `bumbler` to start up your site\r\n5. Go to `localhost:3000/admin` to start making posts and customizing your site.\r\n\r\n\u0026hellip;\r\n\r\n6. Try running bumbler --help to see other options, like changing the port. Bumbler comes with some utility commands for generating an nginx config etc.\r\n\r\n## Directories\r\n\r\n\r\n```bash\r\nassets/        # a symlink to ./_bumblersrc/assets, where uploads go\r\natom/          # your atom feed and its paginated pages will go here\r\npage/          # your site's paginated pages get built to here\r\npost/          # each post gets its own page in here\r\ntag/           # each tag gets an index and paginated pages in here\r\nindex.html\r\nfavicon.ico\r\n_bumblersrc/   # where the site source data is held\r\n  ║\r\n  ║  #     Things the UI will manage for you:\r\n  ║  # ┌───────────────────┴──────────────────┐\r\n  ╠═bumbler.json  # site settings             │\r\n  ╠═posts/        # Your post data lives here │\r\n  ╠═pages/        # custom pages              │\r\n  ╠═assets/       # file uploads go here      │\r\n  ╠═layout.pug    # the site template         |\r\n  ║  # └──────────────────────────────────────┘\r\n  ║   \r\n  ║  #         Modify these manually if you want:\r\n  ║  # ┌─────────────────────┴────────────────────────────────────────────────┐\r\n  ╠═static/      # files \u0026 directories here will be mirrored to the site root │\r\n  ╠═favicon.png  # Put an icon here and favicon.ico will be built             |\r\n  ╠═plugins/     # plugins go in here                                         |\r\n  ╚═scripts/     # you can put executable scripts in here and bumbler will    |\r\n       |         #                          let you run them from the UI      |\r\n     # └──────────────────────────────────────────────────────────────────────┘\r\n```\r\n\r\n# Publishing Guide\r\n\r\n## Self-Hosting\r\n\r\nIf you have an extra $7/month, you can host it on DigitalOcean ($5), with backups ($1), and get a domain ($1).  Here are some tips:\r\n\r\n* Bumbler will **not** work on Heroku or other SAAS platforms with an ephemeral filesystem, because of Bumbler's filesystem-based nature.  All of your files and settings will get blown away every 24 hours or so.\r\n* Install a newer nodejs version using the instructions here:  https://github.com/nodesource/distributions#deb\r\n* Use HTTPS\r\n* Set up backups in case your site explodes, or hackers get in and ruin everything.\r\n\r\n## Static Hosting\r\n\r\nSince the site builds to static files, you can easily build your site on your PC, then drop the files on a static host using SSH or SFTP. I use github pages for [fenwick.pizza](https://fenwick.pizza). One caveat is that bumbler relies on symlinks, so make sure your host supports those.\r\n\r\n# Developer Guide\r\n\r\n## Developing Plugins\r\n\r\nBumbler has a plugin system!\r\n\r\nPlugins should go in `_bumblersrc/plugins/`.\r\n\r\nYour plugin gets passed two parameters: the plugin library and a ready callback.  \r\n\r\nYou must call the ready callback with any errors and any hooks you want to handle.  \r\n\r\nHere's a simple example with hooks:\r\n\r\n```javascript\r\n// _bumblersrc/plugins/last-updated.js\r\n\r\n// Put a generationDate value on every page when the site builds\r\n\r\nmodule.exports = function(pluginLib,pluginReady){\r\n\r\n  var hooks = {\r\n    beforePageRender:function(oldPage,done){\r\n      var page = JSON.parse(JSON.stringify(oldPage));\r\n      page.generationDate = new Date().toISOString();\r\n      done(null,page);\r\n    }\r\n  };\r\n\r\n  // we're ready right away\r\n  pluginReady(null,{hooks});\r\n}\r\n```\r\n\r\nHere's a more complex example of how you could use Bumbler to publish some other feed's data:\r\n\r\n```javascript\r\n// _bumblersrc/plugins/feed-subscriber/index.js\r\n\r\nconst subscribe = require('theoretical-atom-subscription-library');\r\n\r\nmodule.exports = function(pluginLib,pluginReady){\r\n\r\n  subscribe('www.myotherwebsite.com/atomfeed.xml').on('new-post',item=\u003e{\r\n    var postData = {title:item.title,caption:item.caption};\r\n    pluginLib.putPost(postData,(er)=\u003e{      \r\n      pluginLib.buildPages();\r\n    });\r\n  });\r\n\r\n  // this plugin uses no hooks\r\n  var hooks = {};\r\n\r\n  // we're ready right away\r\n  pluginReady(null,{hooks});\r\n}\r\n\r\n```\r\n\r\n### Available hooks\r\n\r\n```\r\nbeforePageRender: function(existingPageData,done)\r\n  =\u003e you must call done(error,modifiedData)\r\n```\r\n\r\n### `pluginLib` methods\r\n\r\n```\r\npluginLib.putPost(postObject,done)\r\n  =\u003e add a post, or modify it by its ID.  Use this to create new posts from another source.\r\n\r\npluginLib.buildPages(done)\r\n  =\u003e rebuild the site pages if you add a new post, or any other reason.\r\n```\r\n\r\n## Developing Bumbler itself\r\n\r\nThe easiest way to develop is in one terminal run `gulp watch` and in another terminal run `cli.js --dir test/kitties`.\r\n\r\nBuilding is handled by Gulp.  There are a few commands you can run:\r\n* `gulp` builds bumbler for development\r\n* `gulp dist` builds bumbler for distribution / production\r\n* `gulp watch` builds bumbler for development and will build it again whenever files change\r\n\r\nThis project compiles pug to html, scss to css, and js to js via browserify + babel.  All built source files are located in `dev-src/`\r\n\r\n## Theming Guide\r\n\r\nEach page gets the following passed to the template:\r\n\r\n```js\r\n{\r\n  _:\"the Lodash library\",\r\n  linkTo:function(){},\r\n  site:{\r\n    authorName:\"Your Name\",\r\n    title:\"Site Title\",\r\n    description: 'Site Description goes here.',\r\n    avatar: '../../your/avatar.png',\r\n    pageCount:10, // total number of pages (if this is a tag page, this will be set to the number of pages for this tag)\r\n    postsPerPage:10, // as configured in settings\r\n    tags:[ // all tags used on the site\r\n      {name:\"apples\",count:25},\r\n      {name:\"pears\",count:14},\r\n      {name:\"peaches\",count:5}\r\n    ]\r\n  },\r\n  page:{\r\n    number:1|2|3|...|null,\r\n    tag:null|'this',\r\n    isCustom:true|false,\r\n    isIndex:true|false,\r\n    customPage:null|{\r\n      title:\"Custom\",\r\n      content:\"\u003cp\u003eThis is some custom content\u003c/p\u003e\"\r\n    },\r\n    links:null|{\r\n      nextPage:URL|null,\r\n      previousPage:URL|null,\r\n      firstPage:URL|null,\r\n      lastPage:URL|null\r\n    },\r\n    posts:null|[\r\n      {\r\n        id:\"ABCDEFG\",\r\n        caption:'\u003cp\u003etesting my new camera\u003c/p\u003e',\r\n        tags:['apples','pears'],\r\n        permalink:\"/posts/ABCDEFG.html\",\r\n        date:new Date('October 30, 2017'),\r\n        englishDate:'October 30, 2017',\r\n        title:'fruit photos',\r\n        assets:[\r\n          {\r\n            href:'/assets/photo.jpeg'\r\n            widget:'\u003cimg src=\"/assets/photo.jpeg\"\u003e\u003c/img\u003e',\r\n            type:'image'|'video'|'audio'|'unknown',\r\n            featured:true|false, // the UI has an option to \"feature\" assets, which sets this flag to true.\r\n            inine:true|false // whether the asset was included in post caption\r\n          },\r\n          '...'\r\n        ],\r\n\r\n      },\r\n      '...'\r\n    ]\r\n  }  \r\n}\r\n```\r\n\r\n`linkTo` is a utility function that will give you links to the various URLs of the site. These are the valid uses of the linkTo function:\r\n\r\n```js\r\nlinkTo('feed')// the atom feed\r\nlinkTo('page',3)// third page of the whole site\r\nlinkTo('tag','apples')// apples tag\r\nlinkTo('tag','apples',2)// second page of the apples tag\r\nlinkTo('root')\r\nlinkTo('admin')// admin login page\r\n```\r\n\r\n_ is Lodash.\r\n\r\n\r\nThe **landing page** will have `page.isIndex == true`.\r\n\r\n**Custom pages** have `page.isCustom === true` and a `page.customPage` object.\r\n\r\n**Individual post pages** have `!page.number`, `page.posts` and `page.posts.length === 1`.\r\n\r\n**Pages with multiple posts** have a `page.number`, 0 or more `page.posts`, and some `page.links`.\r\n\r\n**Tag pages** have a `page.tag` and a `page.number`.\r\n\r\n### Basic Theme\r\n\r\nThe most barebones, functional theme will look like this. Notice how you need to parse the `page` and `site` objects defensively:\r\n\r\n```pug\r\nhtml\r\n  head\r\n    title= site.title\r\n    link(type=\"application/atom+xml\", rel=\"alternate\", href=linkTo(\"feed\"))\r\n    meta(name=\"description\" content=site.description)\r\n    meta(charset=\"utf-8\")\r\n  body\r\n\r\n    if page.isCustom\r\n      h1= page.customPage.title\r\n      div!= page.customPage.content\r\n\r\n    if page.posts\r\n      each post in page.posts\r\n        hr\r\n        h1= post.title\r\n        div Posted in these tags:\r\n          if post.tags\r\n            each tagName in post.tags\r\n              a(href=linkTo('tag',tagName))= tagName\r\n        each asset in post.assets\r\n          div!= asset.widget\r\n        div!= post.caption\r\n\r\n    hr\r\n\r\n    if page.links\r\n      if page.links.previousPage\r\n        a(href=page.links.previousPage) view previous page\r\n      if page.links.nextPage\r\n        a(href=page.links.nextPage) view next page\r\n\r\n    div Copyright #{new Date().getFullYear()} #{site.authorName}\r\n\r\n```\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffenwick67%2Fbumbler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffenwick67%2Fbumbler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffenwick67%2Fbumbler/lists"}