Skip to content

Plugins ​

Gitit supports a plugin system that allows you to extend its functionality. Plugins can add new template sources, modify the behavior of existing commands, add new commands, or hook into various stages of the template cloning process.

Plugin Architecture ​

Gitit plugins are JavaScript or TypeScript modules that export a specific interface. Plugins can be distributed as npm packages or local files.

Plugin Interface ​

A basic Gitit plugin follows this structure:

typescript
// my-gitit-plugin.ts
import type { GitItPlugin } from '@stacksjs/gitit'

const myPlugin: GitItPlugin = {
  name: 'my-gitit-plugin',
  version: '1.0.0',

  // Plugin functionality
  hooks: {
    // Hook implementations
  },

  providers: {
    // Custom template providers
  },

  commands: {
    // Custom commands
  }
}

export default myPlugin

Installing Plugins ​

From npm ​

To use a plugin published on npm:

bash
# Install the plugin
npm install gitit-plugin-example --save-dev

# Add to your gitit.config.ts
typescript
// gitit.config.ts
import examplePlugin from 'gitit-plugin-example'

export default {
  plugins: [
    examplePlugin,
    // More plugins...
  ],
  // Other configuration...
}

Local Plugins ​

You can also use local plugins:

typescript
// gitit.config.ts
import myLocalPlugin from './plugins/my-local-plugin.js'

export default {
  plugins: [
    myLocalPlugin,
    // More plugins...
  ],
  // Other configuration...
}

Developing Plugins ​

Creating a Basic Plugin ​

To create a Gitit plugin, start with this template:

typescript
// my-gitit-plugin.ts
import type { GitItPlugin, TemplateInfo } from '@stacksjs/gitit'

const myPlugin: GitItPlugin = {
  name: 'my-gitit-plugin',
  version: '1.0.0',
  description: 'A plugin for Gitit that adds custom functionality',

  // Hook into various lifecycle events
  hooks: {
    beforeDownload: (template, options) => {
      console.log(`Downloading template: ${template}`)
      return { template, options }
    },

    afterDownload: (result) => {
      console.log(`Template downloaded to: ${result.dir}`)
      return result
    }
  },

  // Add custom template providers
  providers: {
    myCustomSource: (input, options) => {
      // Handle custom template source
      const info: TemplateInfo = {
        name: 'custom-template',
        tar: `https://example.com/templates/${input}.tar.gz`,
        // Other TemplateInfo properties...
      }
      return info
    }
  }
}

export default myPlugin

Available Hooks ​

Plugins can hook into various stages of the Gitit lifecycle:

  • beforeDownload: Called before template download starts
  • afterDownload: Called after template download completes
  • beforeExtract: Called before template extraction
  • afterExtract: Called after template extraction
  • beforeInstall: Called before dependencies are installed
  • afterInstall: Called after dependencies are installed

Each hook receives relevant context and can modify the behavior of Gitit.

Custom Template Providers ​

Plugins can add new template sources by implementing custom providers:

typescript
providers: {
  myCompany: (input, options) => {
    // Parse input and generate template info
    return {
      name: `company-template-${input}`,
      tar: `https://internal.mycompany.com/templates/${input}.tar.gz`,
      // Other properties...
    }
  }
}

This would allow users to use templates with:

bash
gitit myCompany:template-name my-project

Plugin Configuration ​

Plugins can accept configuration options:

typescript
// gitit.config.ts
import myPlugin from 'my-gitit-plugin'

export default {
  plugins: [
    [myPlugin, {
      option1: 'value1',
      option2: true
    }]
  ],
  // Other configuration...
}

In your plugin, access these options:

typescript
// my-gitit-plugin.ts
import type { GitItPlugin } from '@stacksjs/gitit'

function createPlugin(options = {}) {
  return {
    name: 'my-gitit-plugin',
    // Access options.option1, options.option2, etc.

    hooks: {
      beforeDownload: (template, downloadOptions) => {
        if (options.option2) {
          // Do something based on configuration
        }
        return { template, options: downloadOptions }
      }
    }
  } as GitItPlugin
}

export default createPlugin

Examples ​

Template Transformer Plugin ​

This example plugin transforms templates by replacing variables:

typescript
// gitit-template-transformer-plugin.ts
import type { GitItPlugin } from '@stacksjs/gitit'
import { readdir, readFile, writeFile } from 'node:fs/promises'
import { extname, join } from 'node:path'

const transformerPlugin: GitItPlugin = {
  name: 'gitit-template-transformer',
  version: '1.0.0',

  hooks: {
    afterExtract: async (result) => {
      const variables = {
        PROJECT_NAME: result.dir.split('/').pop(),
        TIMESTAMP: new Date().toISOString(),
        AUTHOR: process.env.USER || 'unknown'
      }

      // Find and process files with variables
      await processDirectory(result.dir, variables)

      return result
    }
  }
}

async function processDirectory(dir, variables) {
  const entries = await readdir(dir, { withFileTypes: true })

  for (const entry of entries) {
    const fullPath = join(dir, entry.name)

    if (entry.isDirectory()) {
      await processDirectory(fullPath, variables)
    }
    else if (entry.isFile()) {
      await processFile(fullPath, variables)
    }
  }
}

async function processFile(file, variables) {
  // Skip binary files
  if (isBinaryPath(file))
    return

  try {
    let content = await readFile(file, 'utf8')
    let changed = false

    // Replace variables in format {{ VARIABLE_NAME }}
    for (const [name, value] of Object.entries(variables)) {
      const regex = new RegExp(`\\{\\{\\s*${name}\\s*\\}\\}`, 'g')
      if (regex.test(content)) {
        content = content.replace(regex, value)
        changed = true
      }
    }

    if (changed) {
      await writeFile(file, content, 'utf8')
    }
  }
  catch (error) {
    console.error(`Error processing file ${file}:`, error)
  }
}

function isBinaryPath(filePath) {
  // Simple check for binary files
  const ext = extname(filePath).toLowerCase()
  return ['.png', '.jpg', '.jpeg', '.gif', '.pdf', '.zip', '.gz'].includes(ext)
}

export default transformerPlugin

Publishing Plugins ​

To publish your plugin to npm:

  1. Create a package with an appropriate name (e.g., gitit-plugin-*)
  2. Set up your package.json file:
json
{
  "name": "gitit-plugin-example",
  "version": "1.0.0",
  "description": "Example plugin for Gitit",
  "type": "module",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "keywords": ["gitit", "gitit-plugin"],
  "peerDependencies": {
    "@stacksjs/gitit": "^1.0.0"
  }
}
  1. Build your plugin (transpile TypeScript if needed)
  2. Publish to npm with npm publish (or bun publish)

Best Practices ​

When developing Gitit plugins:

  1. Follow naming conventions: Use gitit-plugin-* for npm packages
  2. Document your plugin: Include clear documentation on usage and options
  3. Handle errors gracefully: Don't let your plugin crash the main process
  4. Keep it focused: Each plugin should have a clear purpose
  5. Use TypeScript: Leverage type checking for more robust plugins
  6. Test thoroughly: Ensure your plugin works with different Gitit workflows

Released under the MIT License.