[WIP] get pages un-broken and loading

e3012a590b2a16944db35cbdd14fa48a19490f22

Tucker McKnight <tucker@pangolin.lan> | Sun Feb 22 2026

[WIP] get pages un-broken and loading

Needs code cleanup here. Tags are not displayed, but pages
that expected to have branches available to them now have branches
available.
README.md:35
Before
34
35
36
37
38
39
+       "My Cool Project": {
+         location: "/home/me/code/cool-project",
+         defaultBranch: 'main',
+         branchesToPull: ['main', 'develop', 'release/**'],
+       },
+     },
+     path: "/repos",
After
34
35
36
37
38
39
+       "My Cool Project": {
+         location: "/home/me/code/cool-project",
+         defaultBranch: 'main',
+         branches: ['main', 'develop', 'release/**'],
+       },
+     },
+     path: "/repos",
main.ts:1
Before
0
1
2
3
4
5
import fsImport from 'fs'
import util from 'util'
import childProcess from 'child_process'
import {repos, getBranchNames} from './src/repos.ts'
import branches from './src/branches.ts'
import flatFiles from './src/flatFiles.ts'
import flatPatches from './src/flatPatches.ts'
After
0
1
2
3
4
5
import fsImport from 'fs'
import util from 'util'
import childProcess from 'child_process'
import {repos, getBranchesAndTags} from './src/repos.ts'
import branches from './src/branches.ts'
import flatFiles from './src/flatFiles.ts'
import flatPatches from './src/flatPatches.ts'
main.ts:26
Before
25
26
27
28






29
30
const exec = util.promisify(childProcess.exec)

// 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 || ""
After
25
26
27
28
29
30
31
32
33
34
35
36
const exec = util.promisify(childProcess.exec)

// TODO document how people need to do this and why. And maybe file 11ty bug?
export function beforeHook(eleventyConfig: any, reposConfiguration: ReposConfiguration) {
  const validator = ajv.compile(ConfigSchema)
  const valid = validator(reposConfiguration)
  if (!valid) {
    throw new Error(validator.errors.map(error => `config object at ${error.instancePath.replaceAll("/", ".")}: ${error.message}`).join("\n"))
  }

  return async ({directories}) => {
    const cwd = process.cwd()
    const reposPath = reposConfiguration.path || ""
main.ts:41
Before
40
41
42



43
44
45
        // 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 = (await getBranchNames(repoConfig, repoName)).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
After
40
41
42
43
44
45
46
47
48
        // 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 {branches, tags} = await getBranchesAndTags(repoConfig, repoName)
        const branchNames = branches.map(branch => branch.name)
        const tagNames = branches.map(tag => tag.name)
        const fetchCommands = branchNames.concat(tagNames).map(ref => `git -C ${location} fetch origin ${ref}:${ref}`).join('; ')
        await exec(`${fetchCommands} && git -C ${location} update-server-info`)
      } else {
        // If it is not there, do git clone
main.ts:109
Before
108
109
110



111
112
113
          const tempDirRepoPath = `${tempDir}/${eleventyConfig.getFilter("slugify")(repoName)}`
          await exec(`mkdir ${tempDir}`)
          await exec(`git clone -s ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}.git ${tempDirRepoPath}`)
⁣
⁣
⁣
          for (let branch of await getBranchNames(repoConfig, repoName)) {
            // TODO why doesn't git -C checkout work? Says that repo doesn't exist
            await exec(`(cd ${tempDirRepoPath} && git checkout ${branch})`)
            for (let buildStep of repoConfig.buildSteps) {
After
108
109
110
111
112
113
114
115
116
          const tempDirRepoPath = `${tempDir}/${eleventyConfig.getFilter("slugify")(repoName)}`
          await exec(`mkdir ${tempDir}`)
          await exec(`git clone -s ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}.git ${tempDirRepoPath}`)
          const branchesAndTags = await getBranchesAndTags(repoConfig, repoName)
          const branchNames = branchesAndTags.branches.map(branch => branch.name)
          const tagNames = branchesAndTags.tags.map(tag => tag.name)
          for (let branch of branchNames) {
            // TODO why doesn't git -C checkout work? Says that repo doesn't exist
            await exec(`(cd ${tempDirRepoPath} && git checkout ${branch})`)
            for (let buildStep of repoConfig.buildSteps) {
main.ts:119
Before
118
119
120









121
122
              await exec(`cp -r ${tempDirRepoPath}/${buildStep.copyFrom} ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branch)}/${buildStep.copyTo}`)
            }
          }
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
          // delete the temp dirs
          await exec(`rm -r ${tempDir}`)
        }
After
118
119
120
121
122
123
124
125
126
127
128
129
130
131
              await exec(`cp -r ${tempDirRepoPath}/${buildStep.copyFrom} ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}/branches/${eleventyConfig.getFilter("slugify")(branch)}/${buildStep.copyTo}`)
            }
          }
          for (let tag of tagNames) {
            await exec(`(cd ${tempDirRepoPath} && git checkout ${tag})`)
            for (let buildStep of repoConfig.buildSteps) {
              // Run the command for each step in each branch
              await exec(`(cd ${tempDirRepoPath} && ${buildStep.command})`)
              // Copy the specified folders from the "from" to the "to" dir
              await exec(`cp -r ${tempDirRepoPath}/${buildStep.copyFrom} ${directories.output}${eleventyConfig.getFilter("slugify")(repoName)}/tags/${eleventyConfig.getFilter("slugify")(tag)}/${buildStep.copyTo}`)
            }
          }
          // delete the temp dirs
          await exec(`rm -r ${tempDir}`)
        }
schemas/ReposConfiguration.json:5
Before
4
5
6
7
8
9
    "GitConfig": {
      "additionalProperties": false,
      "properties": {
        "branchesToPull": {
          "items": {
            "anyOf": [
              {
After
4
5
6
7
8
9
    "GitConfig": {
      "additionalProperties": false,
      "properties": {
        "branches": {
          "items": {
            "anyOf": [
              {
schemas/ReposConfiguration.json:114
Before
113
114
115
116
117
118
119
120
121
122
123
124
125
126
              {
                "additionalProperties": false,
                "properties": {
                  "glob": {
                    "type": "string"
                  },
                  "max": {
                    "type": "number"
                  }
                },
                "required": [
                  "glob",
                  "max"
                ],
                "type": "object"
After
113
114
115
116
117
118
119
120
121
122
123
124
125
126
              {
                "additionalProperties": false,
                "properties": {
                  "max": {
                    "type": "number"
                  },
                  "pattern": {
                    "type": "string"
                  }
                },
                "required": [
                  "pattern",
                  "max"
                ],
                "type": "object"
schemas/ReposConfiguration.json:135
Before
134
135
136
137
138
139
      "required": [
        "location",
        "defaultBranch",
        "branchesToPull"
      ],
      "type": "object"
    },
After
134
135
136
137
138
139
      "required": [
        "location",
        "defaultBranch",
        "branches"
      ],
      "type": "object"
    },
src/configTypes.ts:11
Before
10
11
12
13
14
15
 *   repos: {
 *     "My Git Project": {
 *       defaultBranch: 'main',
 *       branchesToPull: ['main', 'develop']
 *     },
 *   },
 * }
After
10
11
12
13
14
15
 *   repos: {
 *     "My Git Project": {
 *       defaultBranch: 'main',
 *       branches: ['main', 'develop']
 *     },
 *   },
 * }
src/configTypes.ts:53
Before
52
53
54
55

56
57
58
  location: string,
  description?: string,
  defaultBranch: string,
  branchesToPull: Array<string | {pattern: string, max?: number, compareTo?: string, description?: string}>,
⁣
  tags?: Array<string | {glob: string, max: number}>,
  languageExtensions?: {
    [fileExtension: string]: string
  },
After
52
53
54
55
56
57
58
59
  location: string,
  description?: string,
  defaultBranch: string,
  branches: Array<string | {pattern: string, max?: number, compareTo?: string, description?: string}>,
  // todo: make tags optional (and branches, too?) and auto-populate with ** if it's not filled in.
  tags: Array<string | {pattern: string, max: number}>,
  languageExtensions?: {
    [fileExtension: string]: string
  },
src/repos.ts:14
Before
After
src/repos.ts:47
Before
46
47
48














49
50

51
    }
  })

⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
  branchRules.sort((a, b) => b.pattern.length - a.pattern.length)

⁣
  allBranches.forEach((branchName) => {
    const matchingPatternIndex = branchRules.findIndex((branchRule) => {
After
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
    }
  })

  let tagRules: Array<{pattern: string, matches: Array<string>, rules: RulesObject}> = repoConfig.tags.map((tagDescription) => {
    const rules: RulesObject = {}

    if (typeof tagDescription !== 'string') {
      if (tagDescription.max) { rules.max = tagDescription.max }
    }

    return {
      pattern: (typeof tagDescription === 'string' ? tagDescription : tagDescription.pattern),
      matches: [],
      rules,
    }
  })

  branchRules.sort((a, b) => b.pattern.length - a.pattern.length)
  tagRules.sort((a, b) => b.pattern.length - a.pattern.length)

  allBranches.forEach((branchName) => {
    const matchingPatternIndex = branchRules.findIndex((branchRule) => {
src/repos.ts:66
Before
65
66
67

















68
69
    }
  })

⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
⁣
  const branches: Array<{
    name: string,
    description?: string,
After
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    }
  })

  allTags.forEach((tagName) => {
    const matchingPatternIndex = tagRules.findIndex((tagRule) => {
      return minimatch(tagName, tagRule.pattern)
    })

    if (matchingPatternIndex === -1) { return }

    const matchedRule = tagRules[matchingPatternIndex]
    matchedRule.matches.push(tagName)
    if (
      matchedRule.rules?.max
      && matchedRule.rules?.max < matchedRule.matches.length
    ) {
      matchedRule.matches.pop()
    }
  })

  const branches: Array<{
    name: string,
    description?: string,
src/repos.ts:79
Before
78
79
80







81
82
83
84
85
86
87

88

89
90
91
    }).flat()
  }).flat()

⁣
⁣
⁣
⁣
⁣
⁣
⁣
  if (branchesForReposMap.get(repoName) === undefined) {
    branchesForReposMap.set(repoName, branches)
  }

  return branches
}

⁣
const getBranchNames = async (repoConfig, repoName) => {
⁣
  return (await getBranches(repoConfig, repoName)).map(branch => branch.name)
}

let cachedRepos: Array<Repository> | null = null
After
78
79
80
81
82
83
84
85
86
87
88
89
90




91
92
93
94
95
96
    }).flat()
  }).flat()

  const tags: Array<{ name: string }> = tagRules.map((tagRule) => {
    return tagRule.matches.map((match) => {
      const result = {name: match}
      return result
    }).flat()
  }).flat()

  if (branchesForReposMap.get(repoName) === undefined) {
    branchesForReposMap.set(repoName, branches)
  }
⁣
⁣
⁣
⁣
  if (tagsForReposMap.get(repoName) === undefined) {
    tagsForReposMap.set(repoName, tags)
  }
  return { branches, tags }
}

let cachedRepos: Array<Repository> | null = null
src/repos.ts:100
Before
99
100
101
102
103


104
105
106
107
108
109
110

  for (const repoName of repoNames) {
    const repoLocation = getLocation(reposConfig, outputDir, repoName)
    const branchesToAdd = await getBranches(reposConfig.repos[repoName], repoName)
    const commits: Repository['commits'] = new Map()
⁣
⁣
    for (const branchName of await getBranchNames(reposConfig.repos[repoName], repoName)) {
      await addBranchToCommitsMap(branchName, repoLocation, commits)
    }

    const branches = await Promise.all(branchesToAdd.map(async (branch) => {
      const repoLocation = getLocation(reposConfig, outputDir, repoName)
      const branchHeadRes = await exec(`git -C ${repoLocation} show-ref refs/heads/${branch.name}`)
      const branchHead = branchHeadRes.stdout.split(" ")[0]
After
99
100
101

102
103
104
105
106
107
108
109
110
111

  for (const repoName of repoNames) {
    const repoLocation = getLocation(reposConfig, outputDir, repoName)
⁣
    const commits: Repository['commits'] = new Map()
    const branchesAndTags = await getBranchesAndTags(reposConfig.repos[repoName], repoName)
    const branchNames = branchesAndTags.branches.map(branch => branch.name)
    for (const branchName of branchNames) {
      await addBranchToCommitsMap(branchName, repoLocation, commits)
    }

    const branches = await Promise.all(branchesAndTags.branches.map(async (branch) => {
      const repoLocation = getLocation(reposConfig, outputDir, repoName)
      const branchHeadRes = await exec(`git -C ${repoLocation} show-ref refs/heads/${branch.name}`)
      const branchHead = branchHeadRes.stdout.split(" ")[0]
src/repos.ts:122
Before
121
122
123
124
125
126
    }))

    const branchesWithCompareToInfo: Repository['branches'] = branches.map((branch) => {
      const branchDescription = branchesToAdd.find(branchToAdd => branchToAdd.name === branch.name)
      const compareTo = branchDescription.compareTo || reposConfig.repos[repoName].defaultBranch
      const compareToBranch = branches.find((test) => test.name === compareTo)
After
121
122
123
124
125
126
    }))

    const branchesWithCompareToInfo: Repository['branches'] = branches.map((branch) => {
      const branchDescription = branchesAndTags.branches.find(branchToAdd => branchToAdd.name === branch.name)
      const compareTo = branchDescription.compareTo || reposConfig.repos[repoName].defaultBranch
      const compareToBranch = branches.find((test) => test.name === compareTo)
src/repos.ts:172
Before
171
172
173
  return cachedRepos
}

export {repos, getBranchNames}
After
171
172
173
  return cachedRepos
}

export {repos, getBranchesAndTags}