{"id":51119015,"url":"https://github.com/sulthonzh/hashdir","last_synced_at":"2026-06-25T00:30:37.941Z","repository":{"id":363203469,"uuid":"1256685124","full_name":"sulthonzh/hashdir","owner":"sulthonzh","description":"Content-hash entire directory trees. Zero deps. Perfect for CI cache keys, build verification, and change detection.","archived":false,"fork":false,"pushed_at":"2026-06-16T08:55:58.000Z","size":22,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T09:24:49.887Z","etag":null,"topics":["build-verification","cache-key","content-hash","directory","hash","integrity"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sulthonzh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-02T02:14:22.000Z","updated_at":"2026-06-16T08:47:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sulthonzh/hashdir","commit_stats":null,"previous_names":["sulthonzh/hashdir"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sulthonzh/hashdir","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulthonzh%2Fhashdir","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulthonzh%2Fhashdir/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulthonzh%2Fhashdir/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulthonzh%2Fhashdir/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sulthonzh","download_url":"https://codeload.github.com/sulthonzh/hashdir/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sulthonzh%2Fhashdir/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34755061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["build-verification","cache-key","content-hash","directory","hash","integrity"],"created_at":"2026-06-25T00:30:37.883Z","updated_at":"2026-06-25T00:30:37.934Z","avatar_url":"https://github.com/sulthonzh.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hashdir\n\nContent-hash entire directory trees. Zero dependencies.\n\nPerfect for **CI cache keys**, **build verification**, and **change detection**.\n\n## Why\n\nYou need to know if a directory actually changed — not just timestamps, but real content. Whether it's busting a CI cache, verifying a build output matches, or watching a source tree for edits, you need a content-addressable hash of the whole tree.\n\nExisting solutions either pull in heavy dependencies, don't handle cross-platform edge cases (CRLF vs LF), or can't diff two trees.\n\nhashdir does one thing well: hash a directory deterministically by content.\n\n## Install\n\n```bash\nnpm install -g hashdir\n```\n\n## Usage\n\n### Hash a directory\n\n```bash\n$ hashdir ./src\nHash:       a3f8c2e1d4b5...\nAlgorithm:  sha256\nFiles:      23\nTotal size: 45.2 KB\n```\n\n### Compare two directories\n\n```bash\n$ hashdir diff ./build ./dist\nDirectories differ.\n\nModified (1):\n  ~ bundle.js\n\nAdded (2):\n  + sourcemap.js.map\n  + manifest.json\n```\n\nExit code is `1` when directories differ — perfect for CI:\n\n```bash\nhashdir diff ./expected ./actual || echo \"Build output changed!\"\n```\n\n### Watch for changes\n\n```bash\n$ hashdir watch ./src --interval 1000\nWatching: /path/to/src (interval: 1000ms, algo: sha256)\n[2026-06-02T02:00:00.000Z] CHANGED → b7e3f1a2... (24 files, 47.1 KB)\n```\n\n### List files that would be hashed\n\n```bash\n$ hashdir list ./src --ext .js,.ts\n```\n\n### As CI cache key\n\n```yaml\n# GitHub Actions\n- name: Get source hash\n  run: echo \"HASH=$(hashdir ./src --json | jq -r .hash)\" \u003e\u003e $GITHUB_ENV\n\n- name: Cache\n  uses: actions/cache@v3\n  with:\n    path: ./build\n    key: build-${{ env.HASH }}\n```\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `hash \u003cdir\u003e` | Hash a directory tree (default) |\n| `diff \u003cdirA\u003e \u003cdirB\u003e` | Compare two directories by content |\n| `watch \u003cdir\u003e` | Poll for content changes |\n| `list \u003cdir\u003e` | List files that would be hashed |\n\n## Options\n\n| Flag | Description |\n|------|-------------|\n| `-a, --algo \u003calgo\u003e` | Hash algorithm: sha256, sha1, md5, sha512 (default: sha256) |\n| `-i, --ignore \u003cnames\u003e` | Comma-separated file/directory names to skip |\n| `-e, --ext \u003cexts\u003e` | Only hash these extensions (e.g. `.js,.ts`) |\n| `-d, --depth \u003cn\u003e` | Max directory depth |\n| `-n, --normalize` | Normalize text files (CRLF→LF, strip trailing whitespace) |\n| `--dotfiles` | Include dotfiles (hidden files) |\n| `--follow-links` | Follow symbolic links |\n| `--files` | Show per-file hashes in output |\n| `--json` | JSON output |\n| `--markdown` | Markdown output |\n| `-w, --watch` | Watch mode |\n\n## Normalize Mode\n\nCross-platform builds often produce different files due to line endings. Use `--normalize` to treat CRLF and LF as identical:\n\n```bash\nhashdir diff ./build-mac ./build-windows --normalize\n```\n\nThis normalizes line endings, strips trailing whitespace, and ensures a trailing newline before hashing.\n\n## Programmatic API\n\n```js\nconst { hashDirectory, diffDirectories, watchDirectory } = require('hashdir');\n\n// Hash a directory\nconst result = hashDirectory('./src', {\n  algorithm: 'sha256',\n  ignore: ['node_modules', '.git'],\n  extensions: ['.js', '.ts'],\n  normalize: true,\n  includePerFile: true,\n});\n// → { hash: 'a3f8...', fileCount: 23, totalSize: 46284, files: [...] }\n\n// Compare directories\nconst diff = diffDirectories('./build', './dist');\n// → { identical: false, added: [...], removed: [...], modified: [...], unchanged: [...] }\n\n// Watch for changes\nconst watcher = watchDirectory('./src', (event) =\u003e {\n  if (event.changed) console.log('Content changed:', event.hash);\n}, { interval: 2000 });\n\nwatcher.stop(); // cleanup\n```\n\n## How It Works\n\n1. Walk the directory tree (sorted for determinism)\n2. Read each file's content\n3. Hash each file individually\n4. Concatenate `path\\0hash\\0` for all files\n5. Hash the combined string → final tree hash\n\nThis means:\n- Renaming a file changes the hash (path is part of the input)\n- Reordering files doesn't change the hash (sorted)\n- Two identical trees always produce the same hash\n\n## Zero Dependencies\n\nNo npm install black hole. Uses only Node.js built-ins: `fs`, `path`, `crypto`.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsulthonzh%2Fhashdir","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsulthonzh%2Fhashdir","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsulthonzh%2Fhashdir/lists"}