Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
contentful-hugo
Advanced tools
Node module that pulls data from Contentful and turns it into markdown files for Hugo. Can be used with other Static Site Generators, but has some Hugo specific features.
This is a simple Node.js CLI tool that pulls data from Contentful CMS and turns it into Markdown or YAML files for use with a static site generator. It can be used with any static site generator that uses Markdown with YAML frontmatter, but it has some features that are specific to Hugo.
Install Node.js
with NPM
npm install contentful-hugo
with Yarn
yarn add contentful-hugo
Complete configuration then run the following command(s) in the terminal
## initialize the directory
contentful-hugo --init
## fetch from contentful
contentful-hugo
npx contentful-hugo --init
npx contentful-hugo
flag | aliases | description |
---|---|---|
--init | Initialize the directory. Generates a config file and default shortcodes for Contentful rich text fields | |
--preview | -P | Runs in preview mode, which pulls both published and unpublished entries from Contentful |
--wait | -W | Wait for the specified number of milliseconds before pulling data from Contentful. |
--config | -C | Specify the path to a config file. |
--help | Show help | |
--version | Show version number |
contentful-hugo --preview
contentful-hugo --wait=2000 --preview --config="my_custom_config.js"
# or
contentful-hugo --wait 2000 --preview --config my_custom_config.js
{
"name": "my-hugo-project",
"scripts": {
"prestart": "contentful-hugo",
"start": "hugo server",
"prebuild": "contentful-hugo",
"build": "hugo --minify"
}
}
In this example when you run npm start
it will first use contentful-hugo to pull Contentful data then start hugo server. In the same way when you do the command npm run build
it will first use contentful-hugo to pull Contentful data then run hugo --minify
to build a minified version of your hugo site.
Trying to use this package before completing configuration will return an error in the console
By default this library will look for the following environment variables. You can also override these values with the config file. (See config)
.env File:
To declare these environment variables create a .env
file in the root directory of your project.
CONTENTFUL_SPACE = '<space-id>'
CONTENTFUL_TOKEN = '<content-accessToken>'
# optional but required for preview mode
CONTENTFUL_PREVIEW_TOKEN = '<preview-accessToken>'
You can also declare the environment variables in the command line
Powershell:
$env:CONTENTFUL_SPACE="<contentful_space_id>"
$env:CONTENTFUL_TOKEN="<contentful_acessToken>"
$env:CONTENTFUL_PREVIEW_TOKEN="<contentful_preview_accessToken>"
Bash:
export CONTENTFUL_SPACE="<contentful_space_id>"
export CONTENTFUL_TOKEN="<contentful_accessToken>"
export CONTENTFUL_PREVIEW_TOKEN="<contentful_preview_accessToken>"
Before getting started, you will need to create a config file in the root of your repository. Contentful-hugo by default will search for the following files as a config.
contentful-hugo.config.js
contentful-hugo.config.yaml
contentful-hugo.yaml
contentful-settings.yaml
You can also specify a custom config file using the --config
flag. (Javascript or YAML config files are the only currently accepted filetypes)
// contentful-hugo.config.js
module.exports = {
contentful: {
// defaults to CONTENTFUL_SPACE env variable
space: 'space-id',
// defaults to CONTENTFUL_TOKEN env variable
token: 'content-deliver-token',
// defaults to CONTENTFUL_PREVIEW_TOKEN env variable
previewToken: 'content-preview-token'
// defaults to "master"
environment: 'master'
},
singleTypes: [
{
id: 'homepage',
directory: 'content',
fileName: '_index',
fileExtension: 'md',
},
{
id: 'siteSettings',
directory: 'data',
fileName: 'settings',
fileExtension: 'yaml',
},
],
repeatableTypes: [
{
id: 'posts',
directory: 'content/posts',
fileExtension: 'md',
mainContent: 'content',
resolveEntries: [
{
field: 'categories',
resolveTo: 'fields.slug',
},
{
field: 'author',
resolveTo: 'fields.name',
},
{
field: 'relatedPosts',
resolveTo: 'sys.id',
},
],
},
{
id: 'seoFields',
isHeadless: true,
directory: 'content/seo-fields',
},
{
id: 'reviews',
directory: 'content/reviews',
mainContent: 'reviewBody',
},
{
id: 'category',
directory: 'content/categories',
isTaxonomy: true, // Experimental Feature
},
],
};
# contentful-hugo.config.yaml
contentful:
space: 'space-id' # defaults to CONTENTFUL_SPACE env variable
token: 'content-deliver-token' # defaults to CONTENTFUL_TOKEN env variable
previewToken: 'content-preview-token' # defaults to CONTENTFUL_PREVIEW_TOKEN env variable
environment: 'master' # defaults to "master"
singleTypes:
# fetches only the most recently updated entry in a particular content type
# Generated file will be named after the fileName setting
- id: homepage
directory: content
fileName: _index
fileExtension: md
- id: siteSettings
directory: data
fileName: settings
fileExtension: yaml
repeatableTypes:
# fetches all the entries of a content type and places them in a directory.
# Generated files will be named after their Entry ID in Contentful.
- id: posts
directory: content/posts
fileExtension: md
mainContent: content
resolveEntries: # resolves a reference or asset field to a specific property
- field: categories
resolveTo: fields.slug
- field: author
resolveTo: fields.name
- field: relatedPosts
resolveTo: sys.id
- id: seoFields
isHeadless: true
directory: content/seo-fields
- id: reviews
directory: content/reviews
mainContent: reviewBody
- id: staff
isHeadless: true
directory: content/staff
- id: category
directory: content/categories
isTaxonomy: true # Experimental Feature
field | required | description |
---|---|---|
space | optional | Contentful Space ID (Defaults to CONTENTFUL_SPACE environment variable if not set) |
token | optional | Content delivery token (Defaults to CONTENTFUL_TOKEN environment variable if not set) |
previewToken | optional | Content preview token (Defaults to CONTENTFUL_PREVIEW_TOKEN environment variable if not set) |
environment | optional | Contentful environment ID (Defaults to "master" if not set) |
field | required | description |
---|---|---|
id | required | Contentful content type ID |
directory | required | Directory where you want the file(s) to be generated |
fileName | required | Name of the file generated |
fileExtension | optional | Can be "md", "yml", or "yaml" (defaults to "md") |
mainContent | optional | Field ID for field you want to be the main Markdown content. (Can be a markdown, richtext, or string field) |
type | optional | Manually set value for "type" field in the frontmatter (see hugo docs) |
resolveEntries | optional | Resolve the specified reference fields and/or asset fields to one of it's properties specified with the resolveTo parameter |
field | required | description |
---|---|---|
id | required | Contentful content type ID |
directory | required | Directory where you want the files to be generated |
fileExtension | optional | Can be "md", "yml", or "yaml" (defaults to "md") |
isHeadless | optional | Turns all entries in a content type into headless leaf bundles (see hugo docs). Cannot be set to true when isTaxonomy is set to true. |
isTaxonomy (Experimental) | optional | Organize entries in file structure allowing for custom taxonomy metadata (see hugo docs). Cannot be set to true when isHeadless is set to true. |
mainContent | optional | Field ID for field you want to be the main markdown content. (Can be a markdown, richtext, or string field) |
type | optional | Manually set value for "type" field in the frontmatter (see hugo docs) |
resolveEntries | optional | Resolve the specified reference fields and/or asset fields to one of it's properties specified with the resolveTo parameter |
Here is an example of dynamically change the token
, previewToken
, and environment
options depending on any arbitrary condition.
// contentful-hugo.config.js
require('dotenv').config(); // assuming you have "dotenv" in your dependencies
const myMasterToken = process.env.CONTENTFUL_MASTER_TOKEN;
const myMasterPreviewToken = process.env.CONTENTFUL_MASTER_PREVIEW_TOKEN;
const myStagingToken = process.env.CONTENTFUL_STAGING_TOKEN;
const myStagingPreviewToken = process.env.CONTENTFUL_STAGING_PREVIEW_TOKEN;
// set some condition
const isStaging = true || false;
module.exports = {
contentful: {
space: 'my-space-id',
token: isStaging ? myStagingToken : myMasterToken,
preview: isStaging ? myStagingPreviewToken : myMasterPreviewToken,
environment: isStaging ? 'staging' : 'master',
},
// rest of config
};
Files will be generated in the directory specified in the contentful-settings.yaml file. Front matter will be in YAML format. Files of single types will be named after fileName specified in the config file. Files of repeatable types will be named after their entry ID in Contenful, which makes it easy to link files together.
The following fields will always appear in your frontmatter:
updated: # the last time this entry was update in Contentful
createdAt: # when the entry was created in Contentful
date: # defaults to creation date unless you have a field with the id "date" then it get's overwritten
Assets like images and videos come with some extra information that makes it easy to implement things like alt text or layouts that rely on knowing the image dimensions. The fields are as follows:
assetFieldName:
assetType: # indicates the asset type such as "image" "video" "audio" ect.
url: # url of the asset
title: # title of the asset written in Contentful
description: # description of the asset written in Contentful
width: # width of the asset (images only)
height: # height of the asset (images only )
If you're using Hugo you can access the information like below:
<img
src="{{ .Params.assetFieldName.url }}"
width="{{ .Params.assetFieldName.width }}"
/>
This same information will also appear in asset arrays like a gallery:
myGallery:
- assetType: 'image/jpg'
url: '//link-to-image.jpg'
title: 'Image 1'
description: 'Image 1 Description'
width: 500
height: 500
- assetType: 'image/jpg'
url: '//link-to-image-2.jpg'
title: 'Image 2'
description: 'Image 2 Description'
width: 1920
height: 1080
Linked entries will include fields for it's id and it's content type id.
linkedEntry:
id: <contentful-entry-id>
typeId: <content-type-ID>
#example with array of linked entries
relatedArticles:
- id: '41UFfIhszbS1kh95bomMj7'
typeId: 'articles'
- id: '85UFfIhsacS1kh71bpqMj7'
typeId: 'articles'
All files are named after their entry id in Contentful making it easy to retrieve it using .Site.GetPage in Hugo
{{ with .Site.GetPage "<path-to-file>/<entry-id>.md" }}
{{ .Title }}
{{ end }}
A rich text field that is set as the "mainContent" for a content type will be rendered as markdown for Hugo.
Dynamic content such as embedded-entry-blocks
are rendered as shortcodes with parameters included that can be used to fetch the necessary data.
<!-- example embedded entry -->
<!-- you can use the id, contentType, and parentContentType parameters to fetch the desired data -->
{{< contentful-hugo/embedded-entry id="nTLo2ffSJJp5QrnrO5IU9" contentType="gallery" parentContentType="post" >}}
Before fetching rich text data make sure you have run contentful-hugo --init
so that you will have all the rich text shortcodes. Once you have these shortcodes you can extend and modify them to suit your needs.
The list of rich text short codes includes:
By default the richtext short codes will show a notification for an unconfigured item.
You can customize them by navigating to layouts/shortcodes/contentful-hugo/{shortcode-name}.html
A Rich text field will produce nested arrays mirroring the JSON structure that they have in the API. Each node will need to be looped through and produce HTML depending on the nodeType field.
richTextField:
- nodeType: 'paragraph'
data: {}
content:
- data: {}
marks: []
value: 'This is a simple paragraph.'
nodeType: 'text'
- nodeType: 'paragraph'
data: {}
content:
- data: {}
marks: []
value: 'This is a paragraph with '
nodeType: 'text'
- data: {}
marks:
- type: 'italic'
value: 'italicized text.'
nodeType: 'text'
- nodeType: 'embedded-asset-block'
data:
assetType: 'image/jpeg'
url: '//images.ctfassets.net/some-image-url.jpg'
title: 'Image title will appear here'
description: 'Image description will appear here'
width: 1920
height: 1080
content: []
In addition a plaintext version of the field will be generated using the field ID appended with "_plaintext". This allows you to quickly fetch the text by itself without any of the other data. A simple use case would be using the plaintext output to automatically generate a meta description for a webpage.
richTextField_plaintext: 'This is a simple paragraph. This is a paragraph with italicized text.'
The resolve entries option let's you specify a field from a referenced entry or asset to resolve that field value you. For example say you have a category
content type that is referenced in posts
. Normally contentful-hugo will give the following result
category:
id: some-entry-id
contentType: category
While this makes it easy to find the category, this format does not allow you to use Hugo's built in taxonomy features. With the resolveEntries
parameter you can remedy this.
// from the config file
module.exports = {
repeatableTypes: [
{
id: 'post',
directory: 'content/posts',
resolveEntries: [
{
field: 'category',
resolveTo: 'fields.slug',
},
],
},
],
};
Now the category field will only display the slug as the value.
category: my-category-slug
The resolve entries feature works with both reference fields and asset fields. (As well as multiple reference and multiple asset fields)
These are some known issues.
--wait
flag to your script. Here's an example where we wait an additional 6 seconds contentful-hugo --wait=6000
.FAQs
Node module that pulls data from Contentful and turns it into markdown files for Hugo. Can be used with other Static Site Generators, but has some Hugo specific features.
The npm package contentful-hugo receives a total of 52 weekly downloads. As such, contentful-hugo popularity was classified as not popular.
We found that contentful-hugo demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers 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.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.