Differ
Functions
getChangedFiles
function getChangedFiles(since: string, cwd?: string): string[]
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
| Name | Type | Required | Description |
|---|---|---|---|
since | string | Yes | Any Git ref — commit SHA, tag, or branch name — to diff against HEAD. Files changed after this point are returned. |
cwd | string | No | Absolute 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
sinceref must exist in the repository's history. Passing a tag or SHA that doesn't exist will causegit diffto 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' ]
findStaleElements
async function findStaleElements(manifest: DocManifest, changedFiles: string[]): Promise<StaleElement[]>
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
| Name | Type | Required | Description |
|---|---|---|---|
manifest | DocManifest | Yes | The previously generated manifest snapshot — contains the last-known signatures and hashes for every documented element. Load this from your output directory before calling. |
changedFiles | string[] | Yes | Absolute 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
changedFilesare 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()