Skip to content

Hooks ​

Gitit provides a hooks system that allows you to execute custom logic at specific points in the template cloning process. Hooks are powerful extension points that can be used to customize the behavior of Gitit without modifying its core functionality.

Understanding Hooks ​

Hooks are functions that are called at specific stages of the Gitit workflow. They can be used to:

  • Modify template content before or after extraction
  • Run custom operations after cloning is complete
  • Perform cleanup tasks
  • Add custom analytics or logging
  • Interact with external systems

Available Hooks ​

Gitit provides the following hooks that you can tap into:

Lifecycle Hooks ​

Hook NameTimingDescription
beforeDownloadBefore template downloadCalled before downloading the template
afterDownloadAfter template downloadCalled after the template has been downloaded
beforeExtractBefore template extractionCalled before extracting the template archive
afterExtractAfter template extractionCalled after the template has been extracted
beforeInstallBefore dependency installationCalled before installing dependencies
afterInstallAfter dependency installationCalled after dependencies have been installed

Configuring Hooks ​

Hooks can be configured in your gitit.config.ts file:

typescript
// gitit.config.ts
export default {
  hooks: {
    beforeDownload: (template, options) => {
      console.log(`About to download template: ${template}`)
      return { template, options }
    },

    afterExtract: (result) => {
      console.log(`Template extracted to: ${result.dir}`)
      // You can modify files here if needed
      return result
    },

    afterInstall: async (result) => {
      console.log('Dependencies installed, running custom logic...')
      // Run custom commands, etc.
      return result
    }
  },

  // Other configuration options...
}

Hook Function Signatures ​

Each hook has a specific signature that provides relevant context information:

beforeDownload ​

typescript
type BeforeDownloadHook = (
  template: string,
  options: DownloadTemplateOptions
) => Promise<{ template: string, options: DownloadTemplateOptions }> | { template: string, options: DownloadTemplateOptions }

This hook allows you to modify the template source and download options before the download begins.

afterDownload ​

typescript
type AfterDownloadHook = (
  result: DownloadTemplateResult
) => Promise<DownloadTemplateResult> | DownloadTemplateResult

This hook is called after the template has been downloaded but before it's extracted. The result object contains information about the downloaded template.

beforeExtract ​

typescript
type BeforeExtractHook = (
  result: DownloadTemplateResult,
  extractOptions: ExtractOptions
) => Promise<{ result: DownloadTemplateResult, extractOptions: ExtractOptions }> | { result: DownloadTemplateResult, extractOptions: ExtractOptions }

This hook is called before extracting the template archive, allowing you to modify extraction options.

afterExtract ​

typescript
type AfterExtractHook = (
  result: DownloadTemplateResult
) => Promise<DownloadTemplateResult> | DownloadTemplateResult

This hook is called after the template has been extracted to the target directory. This is a good place to modify template files.

beforeInstall ​

typescript
type BeforeInstallHook = (
  result: DownloadTemplateResult,
  installOptions: InstallOptions
) => Promise<{ result: DownloadTemplateResult, installOptions: InstallOptions }> | { result: DownloadTemplateResult, installOptions: InstallOptions }

This hook is called before installing dependencies, if the install option is enabled.

afterInstall ​

typescript
type AfterInstallHook = (
  result: DownloadTemplateResult
) => Promise<DownloadTemplateResult> | DownloadTemplateResult

This hook is called after dependencies have been installed. This is a good place to run post-install tasks.

Hook Examples ​

Here are some practical examples of how to use hooks:

Custom License Replacement ​

This hook replaces license information with your own details:

typescript
// gitit.config.ts
import { access, readFile, writeFile } from 'node:fs/promises'
import { join } from 'node:path'

export default {
  hooks: {
    afterExtract: async (result) => {
      // Replace LICENSE file if it exists
      const licensePath = join(result.dir, 'LICENSE')
      try {
        const fileExists = await access(licensePath)
          .then(() => true)
          .catch(() => false)

        if (fileExists) {
          await writeFile(
            licensePath,
            `Copyright (c) ${new Date().getFullYear()} Your Name\n\nYour license text here...`
          )
        }
      }
      catch (error) {
        console.error('Error modifying license:', error)
      }

      return result
    }
  }
}

Custom Git Initialization ​

Initialize a Git repository after cloning a template:

typescript
// gitit.config.ts
import { execSync } from 'node:child_process'

export default {
  hooks: {
    afterExtract: async (result) => {
      try {
        console.log('Initializing Git repository...')
        execSync('git init', { cwd: result.dir, stdio: 'inherit' })
        execSync('git add .', { cwd: result.dir, stdio: 'inherit' })
        execSync('git commit -m "Initial commit from template"', {
          cwd: result.dir,
          stdio: 'inherit'
        })
      }
      catch (error) {
        console.error('Error initializing Git repository:', error)
      }

      return result
    }
  }
}

Template Customization ​

Customize template files with project-specific information:

typescript
// gitit.config.ts
import { readFile, writeFile } from 'node:fs/promises'
import { basename, join } from 'node:path'
import { prompt } from 'enquirer'

export default {
  hooks: {
    afterExtract: async (result) => {
      // Get project information
      const { projectName, author, description } = await prompt([
        {
          type: 'input',
          name: 'projectName',
          message: 'Project name',
          initial: basename(result.dir)
        },
        {
          type: 'input',
          name: 'author',
          message: 'Author name'
        },
        {
          type: 'input',
          name: 'description',
          message: 'Project description'
        }
      ])

      // Replace in package.json
      const packageJsonPath = join(result.dir, 'package.json')
      try {
        const packageData = JSON.parse(
          await readFile(packageJsonPath, 'utf8')
        )

        packageData.name = projectName
        packageData.author = author
        packageData.description = description

        await writeFile(
          packageJsonPath,
          JSON.stringify(packageData, null, 2)
        )
      }
      catch (error) {
        console.error('Error updating package.json:', error)
      }

      return result
    }
  }
}

Accessing External Services ​

Hooks can interact with external services for various purposes:

typescript
// gitit.config.ts
import { basename } from 'node:path'

export default {
  hooks: {
    afterExtract: async (result) => {
      // Send a notification that a new project was created
      try {
        await fetch('https://api.example.com/project-created', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            projectName: basename(result.dir),
            template: result.name,
            timestamp: new Date().toISOString()
          })
        })
      }
      catch (error) {
        console.error('Failed to send notification:', error)
      }

      return result
    }
  }
}

Best Practices ​

When working with hooks, follow these best practices:

  1. Keep hooks focused: Each hook should do one thing well
  2. Handle errors gracefully: Always wrap hook logic in try/catch blocks
  3. Return the expected values: Make sure to return the correct result from each hook
  4. Use async/await: For operations that may take time, use async functions
  5. Respect user choices: Don't override user configuration without good reason
  6. Provide feedback: Log important information about what your hooks are doing
  7. Be idempotent: Hooks should work correctly even if run multiple times

Advanced: Hooks in Plugins ​

For more complex scenarios, consider creating a plugin that provides hooks. This allows for better organization and reuse:

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

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

  hooks: {
    afterExtract: async (result) => {
      // Plugin hook implementation
      return result
    }
  }
}

export default myPlugin

Then in your configuration:

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

export default {
  plugins: [myPlugin],

  // You can still define hooks directly
  hooks: {
    beforeDownload: (template, options) => {
      // Hook implementation
      return { template, options }
    }
  }
}

See the Plugins documentation for more information on creating plugins.

Released under the MIT License.