Skip to content

refresh — differ

Differ

Functions

getChangedFiles

function getChangedFiles(since: string, cwd?: string): string[]
TypeScript

Use getChangedFiles to get a list of files modified since a given Git ref, so you can scope documentation regeneration to only what's changed.

This is the function to reach for when you want incremental doc updates — instead of re-scanning your entire codebase on every run, call this first to identify which source files have changed since your last release tag, commit SHA, or branch point, then pass only those files to your generation pipeline.

It runs git diff --name-only <since>..HEAD under the hood and returns the result as a plain array of relative file paths. The 10-second timeout prevents hangs in large repos or slow environments.

Parameters

NameTypeRequiredDescription
sincestringYesAny Git ref — commit SHA, tag, or branch name — to diff against HEAD. Files changed after this point are returned.
cwdstringNoAbsolute or relative path to the Git repository root. Defaults to the process working directory if omitted.

Returns

An array of relative file path strings (e.g. ["src/auth/login.ts", "src/utils/hash.ts"]). The paths are relative to the repository root. Feed these directly into your scanner to limit doc generation to changed files only — if the array is empty, your docs are already up to date.

Heads up

  • The since ref must exist in the repository's history. Passing a tag or SHA that doesn't exist will cause git diff to fail and return an empty array rather than throwing — check for an empty result if you suspect the ref is invalid.
  • Paths are returned as the OS reports them from Git. On Windows, Git may return forward-slash paths regardless of platform conventions.

Example:

import { spawnSync } from "child_process";

function getChangedFiles(since: string, cwd?: string): string[] {
  const result = spawnSync("git", ["diff", "--name-only", `${since}..HEAD`], {
    stdio: "pipe",
    cwd,
    timeout: 10000,
  });

  if (result.status !== 0 || result.error) {
    return [];
  }

  const output = result.stdout.toString().trim();
  if (!output) return [];

  return output.split("\n");
}

// Scenario: only regenerate docs for files changed since the last release tag
const LAST_RELEASE_TAG = "v2.4.0";
const REPO_ROOT = "/Users/dana/projects/payments-sdk";

try {
  const changedFiles = getChangedFiles(LAST_RELEASE_TAG, REPO_ROOT);

  if (changedFiles.length === 0) {
    console.log("No files changed since last release — docs are up to date.");
  } else {
    console.log(`Regenerating docs for ${changedFiles.length} changed file(s):`);
    console.log(changedFiles);
    // Pass changedFiles to your scanner here
    // e.g. scanDirectory(changedFiles, { output: "./content/docs" })
  }
} catch (err) {
  console.error("Failed to determine changed files:", err);
}

// Expected output:
// Regenerating docs for 3 changed file(s):
// [ 'src/auth/login.ts', 'src/payments/charge.ts', 'src/utils/retry.ts' ]
TypeScript

findStaleElements

async function findStaleElements(manifest: DocManifest, changedFiles: string[]): Promise<StaleElement[]>
TypeScript

Use findStaleElements to identify which documented API elements need their docs regenerated after source files change.

Call this as part of an incremental update workflow — after detecting which files changed (via git diff, a file watcher, or CI), pass those paths here to get back only the elements whose signatures have drifted from what the manifest recorded. This avoids regenerating docs for your entire codebase when only a few functions changed.

It works by rescanning the changed files, hashing their current signatures, and comparing them against the hashes stored in the manifest. Elements whose hashes no longer match are returned as stale.

Parameters

NameTypeRequiredDescription
manifestDocManifestYesThe previously generated manifest snapshot — contains the last-known signatures and hashes for every documented element. Load this from your output directory before calling.
changedFilesstring[]YesAbsolute or relative paths to source files that have been modified since the manifest was written. Pass an empty array to skip the check entirely and receive no stale elements.

Returns

Returns a Promise<StaleElement[]> — an array of elements whose current signatures differ from what the manifest recorded. Each StaleElement includes the element name, file path, and old vs. new signature so you can pass the array directly to your doc generation step to regenerate only what's out of date. If nothing has changed, resolves to an empty array.

Heads up

  • Only files listed in changedFiles are rescanned — if a file was deleted and removed from the list, its elements won't appear as stale. Handle deletions separately by diffing manifest entries against the filesystem.
  • Signature hashing is structural, not textual — reformatting a function without changing its parameters or return type won't produce a stale result.

Example:

// Inline types matching the real signatures
type DocElement = {
  name: string
  filePath: string
  signatureHash: string
  signature: string
}

type DocManifest = {
  version: string
  generatedAt: string
  elements: Record<string, DocElement>
}

type StaleElement = {
  name: string
  filePath: string
  oldSignature: string
  newSignature: string
}

// Mock implementation of findStaleElements
async function findStaleElements(
  manifest: DocManifest,
  changedFiles: string[]
): Promise<StaleElement[]> {
  // Simulate rescanning changed files and comparing signature hashes
  const stale: StaleElement[] = []

  for (const element of Object.values(manifest.elements)) {
    if (!changedFiles.includes(element.filePath)) continue

    // Simulate detecting a changed signature in the rescanned file
    const rescanHash = element.signatureHash + "_changed"
    if (rescanHash !== element.signatureHash) {
      stale.push({
        name: element.name,
        filePath: element.filePath,
        oldSignature: element.signature,
        newSignature: element.signature.replace("userId: string", "userId: string, options?: RequestOptions"),
      })
    }
  }

  return stale
}

// A manifest snapshot from the last doc generation run
const manifest: DocManifest = {
  version: "1.0.0",
  generatedAt: "2024-11-01T09:00:00Z",
  elements: {
    createUser: {
      name: "createUser",
      filePath: "src/users/create.ts",
      signatureHash: "a3f8c2d1e9b74056",
      signature: "async function createUser(userId: string): Promise<User>",
    },
    deleteUser: {
      name: "deleteUser",
      filePath: "src/users/delete.ts",
      signatureHash: "b7e1a4c9d2f30817",
      signature: "async function deleteUser(userId: string): Promise<void>",
    },
  },
}

// Only src/users/create.ts was modified in this commit
const changedFiles = ["src/users/create.ts"]

async function main() {
  try {
    const staleElements = await findStaleElements(manifest, changedFiles)

    if (staleElements.length === 0) {
      console.log("All docs are up to date.")
      return
    }

    console.log(`Found ${staleElements.length} stale element(s) — regenerating docs:\n`)
    for (const el of staleElements) {
      console.log(`  ${el.name} (${el.filePath})`)
      console.log(`    Before: ${el.oldSignature}`)
      console.log(`    After:  ${el.newSignature}`)
    }

    // Expected output:
    // Found 1 stale element(s) — regenerating docs:
    //
    //   createUser (src/users/create.ts)
    //     Before: async function createUser(userId: string): Promise<User>
    //     After:  async function createUser(userId: string, options?: RequestOptions): Promise<User>
  } catch (err) {
    console.error("Failed to check for stale elements:", err)
  }
}

main()
TypeScript
Was this helpful?