Research
Security News
Malicious npm Package Targets Solana Developers and Hijacks Funds
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
An elegant and easy-to-use, file-based content management system for static pages
An elegant and easy-to-use, file-based content management system for static pages
cms
provides great flexibility without having to handle complicated installation steps or fighting with databases. It provides you all essentials, so you can focus on your contents. Regardless of whether you want to publish articles, galleries, simple pages, or big sized pages.
npm install --save-dev cms
const path = require('path');
const cms = require('cms');
cms({
paths: {
output: path.resolve(__dirname, 'build')
}
}).render().then(() => {
console.log('Done.');
}).catch((error) => {
console.error(error);
});
cms(…).render()
Renders the page(s) based on your configuration
Returns: Promise
cms(…).get()
Returns the genesis page based on your configuration for further processing (e.g. headless use)
Returns: Page
cms(…).config()
Returns the merged configuration (= defaults + your custom configuration)
Returns: Object
npm install -g cms
cms
Content creation can be done very quick and easy. For each page, a folder containing a text file is placed within the content
folder. While the folder names form the URLs, the name of the text file determines which template is used by the system. Using numbers as prefixes in front of the folder name, you’re able to order/sort the pages. In addition, those prefixes decide whether a page is 'visible' or 'invisible' – this may be used to control which sub pages are listed in menus, for example (see 'Visible and invisible pages' below for more details).
All your site’s content is located in the content
folder. The structure of your site will be identical to the structure inside this folder. So, if you have a projects
folder inside your content folder, your site will automatically have a http://example.com/projects
page.
The content text files are divisible by a YAML-ish syntax into any number of fields. Those fields allow you an unlimited use of the API in templates to display or to control the output of the content. The data structure is modifiable at any time. In addition, the flexibility of the data structure allows creating pages that require a variety of different content types and templates.
You can put as many subfolders inside of folders as you like to build the structure of your site.
You may recognize that some of the folders in the content folder have numbers prepended to their names…
1-projects
2-about-us
3-contact
…while others don’t…
imprint
error
The idea behind this: folders with numbers are 'visible' pages, folders without numbers are 'invisible' pages. This may sound a bit weird at first glance, but the difference between those page types is pretty easy: only 'visible' pages will appear in your site’s menu later, while you can still link to 'invisible' pages (but they won’t appear in your menu).
In addition, those numbers in front of visible pages are used by cms
to sort pages. This makes it easy to setup a site’s menu at the same time as setting up the general structure. All numbers are automatically stripped in URLs, thus the folder 1-projects
will nevertheless have the URL http://example.com/projects
in the end.
Each folder inside the content folder has a text file in it, which holds all the content for that page. This file may be called page.md
(or post.txt
or …). Those text files are very easy to read/edit and still offer amazing possibilities to add content. Have a look at the following example (i.e. an example for a blog post).
Title: Hello world
-----
Text: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
-----
Date: 2017-04-01T13:37:00
As you can see, each field has a name (which needs to consist of characters from A-Z and 0-9 without whitespaces or other fancy characters as it will be casted to camelCase anyways) followed by its content. You have to add five dashes after each field and that’s it.
To structure things a bit more clear, you may want to use additional line breaks (any line breaks at the start and/or end will be trimmed automatically anyways).
Title: Hello world
----
Text:
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
----
Date: 2017-04-01T13:37:00
By the way: if you want to do so, you can change the default file’s name as well as the separator characters (colon, five dashes) in your site’s configuration. See 'Configuration' below for details.
To cut a long story short: You should always make sure to enter your text as UTF-8. This makes everything a lot easier and every decent text editor has support for UTF-8.
cms
makes it super easy to add images, videos, sounds, or documents to your pages. Simply drop them into the folder for each page.
(You can sort files by prepending those numbers, just like pages.)
Just like page content files, you may also create file content files, containing stuff like titles or captions. Simply add a text file for each file matching the full name of the file, followed by your content file extension (i.e. test.jpg.md
for the file test.jpg
). Inside those text files you can define your own fields and content, just like regular content files.
Title: Very nice image
----
Caption: This is a very nice image with loads of colors and stuff.
(cms
will automatically fetch this data from the matching text files and add them to your file object, which you can access in the templates later.)
The following object dump represents the properties that are passed into the template engine next to globals and addons for every page.
Page {
file: '/Users/johndoe/Repositories/my-fancy-website/content/home.md',
parent: undefined,
genesis: Page,
index: 0,
visible: false,
invisible: true,
identifier: '',
url: '/',
output: '/Users/johndoe/Repositories/my-fancy-website/build/index.html',
template: 'home',
children: [
Page,
Page,
…
],
hasChildren: true,
files: [
File,
File,
…
],
hasFiles: true,
images: [
Image,
Image,
…
],
hasImages: true,
videos: [
File,
File,
…
],
hasVideos: true,
sounds: [
File,
File,
…
],
hasSounds: true,
documents: [
File,
File,
…
],
hasDocuments: true,
title: 'Lorem ipsum dolor sit amet',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'
}
parent
may include a reference to the current page’s parent page. This may be useful for stuff like breadcrumb navigations. In case the page is a first level page, parent
is simply undefined
.
genesis
may include a reference to the root page. This may be useful for stuff like navigations (i.e. show all first level pages). In case the page is the first level page itself, genesis
is simply a reference to this
(= the page instance itself).
children
may contain any direct child pages of the current page. This may be useful for stuff like navigations. In case the page has no children, children
will be an empty array.
images
, videos
, sounds
, and documents
may contain any matching files of the current page. files
contains all files combined. In case the page does not contain any files, these properties will be empty arrays.
title
and text
are exemplary custom properties taken from the content file.
Using virtual pages, you can combine the file-based architecture with content from any other data source like APIs or databases. Any page that doesn’t exist in the content folder is called a »virtual page«. You can add virtual pages to any existing page (even the genesis page) using the addVirtualPage
method which accepts properties
containing all necessarry page properties.
The following properties are expected by the system and can be extended using any amount of custom properties (e.g. title
, text
, …).
index
(Default: 0
)While number prefixes in front of folder names are used for ordering/sorting pages and defining their visibility for file-based pages, you may want to define the index explicitly for virtual pages.
identifier
(required)While the nested folder names of its text file determine the identifier of a page (and hence its URL), you have to define the full identifier explicitly for virtual pages.
template
(required)While the name of its text file determines which template is used by the system for file-based pages, you have to define the template name explicitly for virtual pages.
const instance = cms({
…
});
const genesis = instance.get();
const projects = genesis.findPageByUrl('/projects');
const virtualProject = projects.addVirtualPage({
identifier: 'projects/a',
template: 'project',
title: 'This is a virtual project',
text: 'Lorem ipsum',
});
const somethingElse = virtualProject.addVirtualPage({
identifier: 'projects/a/a',
template: 'project',
title: 'This is a virtual project within another virtual project',
someWeirdFieldName: 'Test'
});
basepath(url)
Callback function which prepends the base
option (see below) to the specified url
. This may be useful if you’re planning to run your static site within a sub directory.
Example:
<link href="<%= basepath('/css/style.css') %>" rel="stylesheet">
has(key)
Returns a boolean indicating whether the current page has the specified property. This may be useful if you’re working with dynamic page properties.
Example:
<% if (has('description')) { %>
<p><%= description %></p>
<% } %>
get(key, defaultValue)
Returns the specified property (or the specified default value if it does not exist) for the current page. This may be useful if you’re working with dynamic page properties.
Example:
<% ['title', 'description'].forEach((prop) => { %>
<p><%= get(prop, 'Something') %></p>
<% }) %>
findPageByUrl(url, context)
Searches the page tree (starting at context
, which equals the genesis
page by default) recursively for the the page that has the URL url
. This may be useful if you need to use properties of other pages located somewhere else in the page tree.
Example:
<% const contactPage = findPageByUrl('/contact') %>
<a href="<%= contactPage.url %>"><%= contactPage.title %></a>
Shortcodes let you do nifty things with very little effort by allowing you to create macros to be used in your page contents. A trivial shortcode example may look like this.
(youtube: jNQXAC9IVRw width: 480 height: 360)
The example above shows a basic shortcode to embed a YouTube video. The actual embedment is done by an appropiate handler, called everytime the shortcode is used.
{
youtube: (attrs) => {
return `<iframe src="https://www.youtube.com/embed/${attrs.youtube}"${attrs.width ? ` width="${attrs.width}"`: ''}${attrs.height ? ` height="${attrs.height}"`: ''}></iframe>`;
}
}
In addition, the page object of the current page (which includes/invokes the shortcode) gets passed into the shortcode handler function as its (optional) second parameter. This allows shortcodes to interact with the page context.
{
photo: (attrs, page) => {
const photo = page.images.find((item) => item.identifier === attrs.photo);
if (photo) {
return `
<figure>
<img src="${photo.url}" alt="${photo.title}">
${photo.caption ? `<figcaption>${photo.caption}</figcaption>` : ''}
</figure>
`;
}
return '';
}
}
CMS uses a sane configuration by default that should cover most use cases. However, if you would like to adjust/extend the configuration, you can either create a file called cms.js
within the root directory (where you’re running cms
in) which exports the configuration object or pass the configuration object into your cms
function call. In both cases, cms
expects to receive a proper JavaScript object containing some of the following properties.
template
Function which is called using the parameters file
(i.e. the path of the template) and data
(i.e. locals) and which is supposed to return a Promise that resolves with the compiled template. This is the place where you would implement your preferred/custom template engine.
(file, data) => Promise.resolve(template(fs.readFileSync(file, 'utf8'))(data))
You may want to use consolidate, a template engine consolidation library.
Default: A Promise-ified version of Lodash’s template function
permalink
Callback function which is called using the parameters permalink
(i.e. the plain page permalink) and which is supposed to decorate and return the permalink for a page. This may be useful in case you’re not able to rewrite URLs.
Default:
(permalink) => `${permalink}`
Example:
(permalink) => `${permalink}/index.html`
base
Prefix that is prepended to all links (e.g. pages, files, …). This may be useful if you’re planning to run your static site within a sub directory.
Default: ∅
Example: /wiki
paths.content
Path to the directory containing all your content.
Default: path.resolve(process.cwd(), 'content')
paths.templates
Path to the directory containing all your templates.
Default: path.resolve(process.cwd(), 'templates')
paths.output
Path to the directory where cms
is supposed to save the static build to.
Default: path.resolve(process.cwd(), 'output')
separators.line
Pattern that is used to separate lines/blocks within your content files.
Default: -----
separators.values
Pattern that is used to separate keys and values within your content files blocks.
Default: :
extensions.content
Array of extensions your content files may use.
Default:
[
'md'
]
extensions.templates
Array of extensions your template files may use. In case you’re using a custom template engine, you most certainly will need to set the appropriate extensions here.
Default:
[
'tpl'
]
extensions.images
Array of extensions, your images within page content directories may use. These extensions are used to find matching images for each page (i.e. the images
property).
Default:
[
'jpg',
'jpeg',
'gif',
'png',
'webp'
]
extensions.videos
Array of extensions, your videos within page content directories may use. These extensions are used to find matching videos for each page (i.e. the videos
property).
Default:
[
'mpg',
'mpeg',
'mp4',
'mov',
'avi',
'flv',
'ogv',
'webm'
]
extensions.sounds
Array of extensions, your sounds within page content directories may use. These extensions are used to find matching sounds for each page (i.e. the sounds
property).
Default:
[
'mp3',
'wav',
'm4a',
'ogg',
'oga'
]
extensions.documents
Array of extensions, your documents within page content directories may use. These extensions are used to find matching documents for each page (i.e. the documents
property).
Default:
[
'pdf',
'doc',
'xls',
'ppt',
'docx',
'xlsx',
'pptx'
]
extensions.output
Extension of static output files.
Default: html
globals
Object containing globals that will be passed into the template
function next to the regular page data. This may be useful if you need to make data available to all pages. The object is deep-merged into the regular page data.
Default: {}
addons
Object containing globals that will be passed into the template
function next to the regular page data. This shall be used for all custom functions you would like to provide within your templates.
Default: {}
Example:
{
markdown: (input) => marked(input),
reverse: (input) => input.split('').reverse().join('')
}
shortcodes
Object containing shortcode handlers that will be applied to page data. This shall be used to register any custom shortcode you would like to provide within your content.
Default: {}
Example:
{
youtube: (attrs) => {
return `<iframe src="https://www.youtube.com/embed/${attrs.youtube}"${attrs.width ? ` width="${attrs.width}"`: ''}${attrs.height ? ` height="${attrs.height}"`: ''}></iframe>`;
}
}
cms
is quite stable now. Most changes are new features, minor bug fixes, or performance improvements. Currently I’m tinkering with the following features.
has
and get
render
method and add get
/config
methods to allow headless usecontext
parameter for shortcode helperbase
prefix and permalink
callback to findPageByUrl
helper queries automaticallybasepath
to only prepend the base path to an URL in case it isn’t there yetbasepath
and findPageByUrl
genesis
is now a reference to this
(= the page instance itself) instead of undefined
Special thanks to Thom Blake for handing over the cms
package name on npm to me. Please check out thomblake/cms in case you’re looking for the code of versions <1.0.0.
Copyright (c) 2019 Thomas Rasshofer
Licensed under the MIT license.
See LICENSE for more info.
FAQs
An elegant and easy-to-use, file-based content management system for static pages
The npm package cms receives a total of 103 weekly downloads. As such, cms popularity was classified as not popular.
We found that cms demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.