npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@dinghystudio/gatsby-source-cockpit-generic

v1.7.3

Published

GatsbyJS source plugin for fetchting collections from a Cockpit CMS API.

Downloads

5

Readme

Gatsby Source Cockpit Generic

GatsbyJS source plugin for fetchting collections and singletons from a Cockpit CMS API.

Current Featureset

  • Asset loading and linking
  • Collection relations
  • i18n
  • (naive) Repeater fields
  • Black- / Whitelisting of Cockpit Collections, Singletons, Media Assets

Usage

  • Installation, configuration options & defaults
  • Fetching contents
  • Creating pages
  • Generating slugs
  • Multilingual setup
  • Serving remote files / documents
  • Black- / Whitelisting

Installation, configuration & defaults

Install via npm:

#!/bin/bash
npm install --save @dinghystudio/gatsby-source-cockpit-generic

Add @dinghystudio/gatsby-source-cockpit-generic to gatsby-config.js:

module.exports = {
  plugins: [
    {
      resolve: '@dinghystudio/gatsby-source-cockpit-generic',
      options: {
        host: `${process.env.COCKPIT_HOST}`,
        accessToken: `${process.env.COCKPIT_ACCESS_TOKEN}`,
        l10n: {
          default: "en",
        },
      },
    },
  ],
}

Add l10n and define a default language code that may be used to access field’s default values. Additionally the plugin will create an entry alternates with relations to the node in all available languages.

Fetching contents

Use environment variables to configure access to the Cockpit API:

#!/bin/bash
COCKPIT_HOST="https://www.url-of-your-gatsby-site.com" COCKPIT_ACCESS_TOKEN="[access token as configured in Cockpit]" gatsby develop

Creating pages

Create pages based on Cockpit data by hooking into createPages in gatsby-node.js:

// gatsby-node.js
const path = require(`path`)

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions

  const component = path.resolve('src/templates/page.js')
  const query = `
  {
    allCockpitGenericCollectionEntries {
      edges {
        node {
          slug
          meta {
            id
          }
        }
      }
    }
  }
  `

  return graphql(query).then(result => {
    if (result.errors) {
      throw result.errors
    }

    result.data.allCockpitGenericCollectionEntries.edges.forEach(
      ({ node }) => {
        const { slug, meta: { id } } = node
        createPage({
          path: slug,
          component,
          context: {
            id,
          },
        })
      }
    )
  })
}

The page template should then query for nodes using the id from context:

// src/templates/page.js
import React from 'react'
import { graphql } from 'gatsby'

const Entry = ({ data: { entry } }) => (
  <>
    <h1>Entry "{entry.title}"</h1>
    <pre>{JSON.stringify(entry, null, 2)}</pre>
  </>
)

export default Entry

export const query = graphql`
  query Entry($id: String!) {
    entry: cockpitGenericCollectionEntries(
      meta: { id: { eq: $id } }
    ) {
      title
    }
  }
`

Generating slugs

Add custom slugs to Cockpit sourced nodes by hooking into onCreateNode:

// gatsby-node.js

exports.onCreateNode = ({ node, actions }) => {
  const { createNodeField } = actions

  if (node.internal.type === 'CockpitGenericCollectionEntries') {
    const value = slugify(node.title)
    createNodeField({ node, name: 'slug', value })
  }
}

// minimal slugify taken from https://gist.github.com/mathewbyrne/1280286
// for production probably better to use https://www.npmjs.com/package/slugify
function slugify(text) {
  return text.toString().toLowerCase().trim()
    .replace(/\s+/g, '-')         // Replace spaces with -
    .replace(/&/g, '-and-')       // Replace & with 'and'
    .replace(/[^\w\-]+/g, '')     // Remove all non-word chars
    .replace(/\--+/g, '-')        // Replace multiple - with single -
    .replace(/^-+/, '')           // Trim - from start of text
    .replace(/-+$/, '')           // Trim - from end of text
}

The slug field can then be used in page creation:

    const query = `
    {
      allCockpitGenericCollectionEntries {
        edges {
          node {
-           slug
+           fields {
+             slug
+           }
            meta {
              id
            }
          }
        }
      }
    }
    `
    // …
    result.data.allCockpitGenericCollectionEntries.edges.forEach(
      ({ node }) => {
-       const { slug, meta: { id } } = node
+       const { fields: { slug }, meta: { id } } = node
        createPage({
          path: slug,
          component,
          context: {
            id,
          },
        })
      }
    )

Multilingual setup

If Cockpit is configured for multiple languages, alternates can be created by updating configuration and sourcing like this:

// gatsby-config.js
module.exports = {
  plugins: [
    {
      resolve: '@dinghystudio/gatsby-source-cockpit-generic',
      options: {
        host: `${process.env.COCKPIT_HOST}`,
        accessToken: `${process.env.COCKPIT_ACCESS_TOKEN}`,
        l10n: {
          default: "en",
+         languages: ['de', 'en'],
        },
      },
    },
  ],
}
// gatsby-node.js
+ const { isObject, merge } = require('lodash')
+
+ const PATH_LOCALIZATIONS = {
+   exceptions: ['/404/', '/404.html', '/dev-404-page/'],
+ }
+
+ function getLanguageConfig(config) {
+   // return the l10n configuration from gatsby-source-cockpit-generic
+   const {
+     options: { l10n },
+   } = config.plugins.find(
+     p => isObject(p) && p.resolve === '@dinghystudio/gatsby-source-cockpit-generic'
+   )
+   return l10n
+ }

// …

    result.data.allCockpitGenericCollectionArtworks.edges.forEach(
      ({ node }) => {
-       const { fields: { slug }, meta: { id } } = node
+       const { language, fields: { slug }, meta: { id } } = node

        createPage({
          path: slug,
          component,
          context: {
            id,
+           language,
          },
        })
      }
    )

// …

+ exports.onCreatePage = ({ page, actions, store }) => {
+   // for every page that is not yet localized, remove existing path, create
+   // new path and page for every language specified in config
+   const { exceptions } = PATH_LOCALIZATIONS
+   if (exceptions.includes(page.path)) return
+
+   const { config } = store.getState()
+   const l10n = getLanguageConfig(config)
+
+   console.log('l10n', l10n)
+   if (l10n && l10n.languages) {
+     const { createPage, deletePage } = actions
+     const { languages, default: defaultLanguage } = l10n
+
+     deletePage(page)
+
+     let updatedPage, localePath
+     languages.forEach(language => {
+       localePath = language === defaultLanguage && page.path === '/'
+         ? page.path
+         : `/${language}${page.path}`
+
+       updatedPage = merge({}, page, {
+         path: localePath,
+         context: { language },
+       })
+
+       createPage(updatedPage)
+     })
+   }
+ }

Afterwards language is available through page context (for further usage in react-i18next, linguiJS and other i18n libraries). Query for alternates (to be added to html head) via graphql:

// src/templates/page.js
import React from 'react'
import { graphql } from 'gatsby'

- const Entry = ({ data: { entry } }) => (
+ const Entry = ({ data: { entry }, pageContext }) => (
  <>
    <h1>Entry "{entry.title}"</h1>
+   <p>language: <code>{pageContext.language}</code></p>
    <pre>{JSON.stringify(entry, null, 2)}</pre>
  </>
)

export default Entry

export const query = graphql`
  query Entry($id: String!) {
    entry: cockpitGenericCollectionEntries(
      meta: { id: { eq: $id } }
    ) {
      title
+     language
+
+     alternates {
+       title
+       language
+
+       fields {
+         slug
+       }
+     }
    }
  }
`

Serving remote files / documents (that are not images)

To serve assets that are not of an image mime type that Gatsby image recognizes, you need to configure a separate gatsby-source-filesystem before the @dinghystudio/gatsby-source-cockpit-generic and create a placeholder file (e.g. ${__dirname}/gatsby-filesystem-placeholder.txt) which apparently is needed for the publicURL attribute to be available:

// gatsby-config.js
module.exports = {
  siteMetadata: {
    title: '…',
  },
  plugins: [
    {
      // We need filesystem source plugin to add publicURL function to File nodes
      resolve: 'gatsby-source-filesystem',
      options: {
        name: 'placeholder',
        path: `${__dirname}/gatsby-filesystem-placeholder.txt`,
      },
    },
    {
      resolve: '@dinghystudio/gatsby-source-cockpit-generic',
      options: {
        '//': '…',
      },
    },
  ],
}

Then you can query the remote file with a publicURL field. See Issue #4993 for details.

Black- / Whitelisting

To only fetch certain Collections, Singletons or Assets it is possible to configure blacklists, whitelists and filetypes:

    {
      resolve: '@dinghystudio/gatsby-source-cockpit-generic',
      options: {
        '//': '…',
+       contents: [
+         {
+           type: 'collection',
+           blacklist: ['news'],
+           whitelist: ['entries', 'news', 'tags'],
+         },
+         { type: 'singleton', whitelist: ['contact'], blacklist: [] },
+         {
+           type: 'asset',
+           filetypes: {
+             image: true,
+             video: true,
+             audio: true,
+             archive: true,
+             document: true,
+             code: true,
+           },
+           tags: {
+             whitelist: [],
+             blacklist: [],
+           },
+         },
+       ],
      },
    },

If no white- or blacklists are specified, the plugin will fetch a complete list via Cockpit’s API. Blacklists precede whitelists. Entries should be the lowercase names as given per Cockpit’s API at /api/collections/listCollections and /api/singletons/listSingletons.

Note blacklisting a collection, that has relations to other collections will currently break the setup.

Development Status

This plugin is in production use on at least two public sites. However there are currently no automated tests. Also the current usage is highly specific and contents are heavily controlled.

Please see the source repositories at GitHub for issues and current status. Issues and pull requests welcome!

Todos

  • Add tests
  • Add full example
  • Enhance console output using Gatsby’s reporter

Reasoning

Why yet another Cockpit Source Plugin?

At the time of writing, plenty other plugins were available but heavily in development, not officially working with GatsbyJS v2, did not handle i18n, repeater fields or put emphasis on things we didn’t need in a source plugin (layout fields). The features needed in our project were straight forward and writing a custom plugin seemed easy enough. Also the featureset grew with our needs.

Previous work

Thanks

Thanks to @mpartipilo for interesting exchange on Gatsby-Source-Plugin development and oddities of the Cockpit API via the Gatsby Discord channel.