Change the before hook to an exported function

2ecee51488bef605eff051f8a03644ef2b74e81d

Tucker McKnight | Sun Jan 11 2026

Change the before hook to an exported function

Also pass in the site output directory to the getLocation function,
since all calls to git now check in the cloned copy of the repo
in the _site dir (or whatever the output dir is) instead of checking
the user's original, local copy of the repo.

This also means that remote URLs can be used in your eleventy config.

An explanation for the before hook change is in wiki/projects/clone-early.md.
Basically, it seems like a plugin can't actually have a before hook run
before the site generation happens. So instead, I'm exporting the before hook
function so that the user can manually add it as a before hook in the
eleventy config for their main site.
main.ts:26
Before
26
After
26
// TODO document how people need to do this and why. And maybe file 11ty bug?
export function beforeHook(eleventyConfig, reposConfiguration) {
  return async ({directories}) => {
    const cwd = process.cwd()
    const reposPath = reposConfiguration.path || ""
    // Check to see if there is already a repo in all of the locations
    // that should have one.
    for (let repoName in reposConfiguration.repos) {
      const repoConfig = reposConfiguration.repos[repoName]
      const repoPath = directories.output + reposPath + "/" + eleventyConfig.getFilter("slugify")(repoName)
      const gitRepoName = eleventyConfig.getFilter("slugify")(repoName) + ".git"
      // If it is there, do git pull
      if (fsImport.existsSync(repoPath + ".git")) {
        // git repos are just in the repos folder, not in their subdir
        // create string of commands saying 'git fetch origin branch:branch' for each branch
        const location = directories.output + reposPath + "/" + gitRepoName
        const fetchCommands = repoConfig.branchesToPull.map(branch => `git -C ${location} fetch origin ${branch}:${branch}`).join('; ')
        await exec(`${fetchCommands} && git -C ${location} update-server-info`)
      } else {
        // If it is not there, do git clone
        // todo: does this work if the latest branch is not checked
        // out locally?
        let originalLocation = cwd + "/" + repoConfig.location
        if (repoConfig.location.startsWith("https://") || repoConfig.location.startsWith("ssh://")) {
          originalLocation = repoConfig.location
        }
        await exec(`git clone ${originalLocation} ${directories.output + reposPath + "/" + gitRepoName} --bare`)
        await exec(`git -C ${directories.output + reposPath + "/" + gitRepoName} update-server-info`)
      }
      const location = directories.output + reposPath + "/" + gitRepoName
      await exec(`git -C ${location} symbolic-ref HEAD refs/heads/${repoConfig.defaultBranch}`)
    }
  }
}
main.ts:35
Before
35
  const reposData = await repos(reposConfiguration)
After
35
  const reposData = await repos(reposConfiguration, eleventyConfig.dir.output)
main.ts:62
Before
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
  eleventyConfig.on(
    "eleventy.before",
    async ({ directories }) => {
      const cwd = process.cwd()
      // Check to see if there is already a repo in all of the locations
      // that should have one.
      for (let repoName in reposConfiguration.repos) {
        const repoConfig = reposConfiguration.repos[repoName]
        const repoPath = eleventyConfig.dir.output + reposPath + "/" + eleventyConfig.getFilter("slugify")(repoName)
        const gitRepoName = eleventyConfig.getFilter("slugify")(repoName) + ".git"
        // If it is there, do git pull
        if (fsImport.existsSync(repoPath + ".git")) {
          // git repos are just in the repos folder, not in their subdir
          // create string of commands saying 'git fetch origin branch:branch' for each branch
          const location = eleventyConfig.dir.output + reposPath + "/" + gitRepoName
          const fetchCommands = repoConfig.branchesToPull.map(branch => `git -C ${location} fetch origin ${branch}:${branch}`).join('; ')
          await exec(`${fetchCommands} && git -C ${location} update-server-info`)
        } else {
          // If it is not there, do git clone
          // todo: does this work if the latest branch is not checked
          // out locally?
          const originalLocation = cwd + "/" + repoConfig.location
          await exec(`git clone ${originalLocation} ${eleventyConfig.dir.output + reposPath + "/" + gitRepoName} --bare`)
          await exec(`git -C ${eleventyConfig.dir.output + reposPath + "/" + gitRepoName} update-server-info`)
        }

        const location = eleventyConfig.dir.output + reposPath + "/" + gitRepoName
        await exec(`git -C ${location} symbolic-ref HEAD refs/heads/${repoConfig.defaultBranch}`)
      }
    }
  )
After
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
main.ts:222
Before
222
    const location = getLocation(reposConfiguration, repo)
After
222
    const location = getLocation(reposConfiguration, eleventyConfig.dir.output, repo)
main.ts:236
Before
236
    const location = getLocation(reposConfiguration, repo)
After
236
    const location = getLocation(reposConfiguration, eleventyConfig.dir.output, repo)
main.ts:255
Before
255
    const location = getLocation(reposConfiguration, repoName)
After
255
    const location = getLocation(reposConfiguration, eleventyConfig.dir.output, repoName)
src/helpers.ts:70
Before
70
71
72
const getLocation = (reposConfig: ReposConfiguration, repoName: string): string => {
  const config = reposConfig.repos[repoName]
  return config.location
After
70
71
72
const getLocation = (reposConfig: ReposConfiguration, outputDir: string, repoName: string): string => {
  return outputDir + (reposConfig.path || "") + "/" + repoName + ".git"
src/repos.ts:26
Before
26
const repos: (reposConfig: ReposConfiguration) => Promise<Array<Repository>> = async (reposConfig) => {
After
26
const repos: (reposConfig: ReposConfiguration, outputDir: string) => Promise<Array<Repository>> = async (reposConfig, outputDir) => {
src/repos.ts:36
Before
36
37
      const repoLocation = getLocation(reposConfig, repoName)
      const repoLocation = getLocation(reposConfig, repoName)
After
36
37
      const repoLocation = getLocation(reposConfig, outputDir, repoName)
      const repoLocation = getLocation(reposConfig, outputDir, repoName)
wiki/projects/clone-early.md:13
Before
13
After
13
    - this was surprisingly easy; just changing eleventy.before to eleventy.after
      seems to have worked for doing the clone before the site generation.
    - the build steps happen in their own, separate eleventy.after callback
      now. Which also works, and might have fixed a race condition where it was
      sometimes running the build steps before the cloned repo was available.
  - Next step: do not reference the original repository after the clone happens.
    Always reference the cloned repo instead.

Places that mention `git -C`. Check if these need to be replaced:

- [x] main.ts:          const fetchCommands = repoConfig.branchesToPull.map(branch => `git -C ${location} fetch origin ${branch}:${branch}`).join('; ')
- [x] main.ts:          await exec(`${fetchCommands} && git -C ${location} update-server-info`)
- [x] main.ts:          await exec(`git -C ${eleventyConfig.dir.output + reposPath + "/" + gitRepoName} update-server-info`)
- [x] main.ts:        await exec(`git -C ${location} symbolic-ref HEAD refs/heads/${repoConfig.defaultBranch}`)
- [x] main.ts:            await exec(`git -C ${tempDirRepoPath} checkout ${branch}`)
- [x] main.ts:    const command = `git -C ${location} show ${branch}:${filename}`
- [x] main.ts:    const command = `git -C ${location} show ${branchName}:README.md`
- [x] src/repos.ts:      const branchHeadRes = await exec(`git -C ${repoLocation} show-ref refs/heads/${branchName}`)
- [x] src/vcses/git/operations.ts:  const command = `git -C ${repoLocation} ls-tree -r --name-only ${branchName}`
- [x] src/vcses/git/operations.ts:  const totalPatchesCountRes = await exec(`git -C ${repoLocation} rev-list --count ${branchName}`)
- [x] src/vcses/git/operations.ts:    const gitLogSubsetRes = await exec(`git -C ${repoLocation} log ${branchName} -p -n 10 --skip ${i}`)
- [x] src/vcses/git/operations.ts:  const command = `git -C ${repoLocation} blame --porcelain ${branch} ${filename}`

^ Most of those were fixed by changing what repo location is found in
`getLocation`.

### Before hook issue

Seems like a plugin actually can't make its before hook get triggered before the site generation happens.

Because the plugin is now exclusively reading things from the _cloned_ repository, and not the source repo
on the computer, the before hook really does need to happen before the site generation happens. Apparently
it wasn't happening before, but this wasn't an issue because the plugin was just reading directly from the
source repo.

I've worked around this by not calling the `eleventyConfig.on("eleventy.before")` _in_ the plugin, but rather,
exporting a function that the user will need to call as a before hook in their own eleventy config. Also, it needs
to be `"beforeConfig"` instead of just `"before"` -- seems like plugins are executed as part of the config step.