hashup()
ts
function hashup(entryFile: string, options?: HashupOptions): Promise<HashupResult>;Resolves every import reachable from entryFile and returns a deterministic SHA-256 hash covering the entry, its transitive dependencies, and any extras. Each unique file contributes exactly once; the final digest is sha256(sorted concat of each reachable file's content hash).
Parameters
entryFile: string
Path to the entry file. May be relative (resolved against options.baseDir) or absolute.
options?: HashupOptions
ts
interface HashupOptions {
/**
* Additional files to include in the hash calculation
* (e.g. configuration files like package.json, tsconfig.json,
* or a lockfile to pin installed dependency versions).
*/
extras?: string[];
/**
* Base directory for resolving relative paths.
* @default process.cwd()
*/
baseDir?: string;
/**
* Verbosity of diagnostic messages written to stderr.
* One of `"silent"`, `"warn"`, `"info"`, `"debug"`.
* @default "silent"
*/
logLevel?: "silent" | "warn" | "info" | "debug";
/**
* Optional shared cache. Pass the same cache across multiple calls
* to dedupe work — a file visited by entry A is reused by entry B.
* Create with `createHashupCache()`.
*/
cache?: HashupCache;
/**
* Optional shared `enhanced-resolve` resolver. Pass a shared
* instance to reuse its internal filesystem cache across calls.
* Create with `createResolver()`.
*/
resolver?: Resolver;
/**
* Honour the nearest `tsconfig.json` (walking up from each source
* file, following `extends`) when resolving bare specifiers:
* `compilerOptions.paths` and `baseUrl` are applied with TypeScript's
* longest-prefix semantics before falling back to Node resolution.
* @default true
*/
tsconfig?: boolean;
}Returns
ts
interface HashupResult {
/** The final deterministic hash (SHA-256, hex encoded). */
hash: string;
/** All absolute file paths included in the hash calculation. */
files: string[];
/**
* Import edges reachable from this entry that did not produce a
* hashed file. Sorted by `from`, then `specifier`. Empty means the
* file set is complete.
*/
unresolved: UnresolvedImport[];
}
interface UnresolvedImport {
/** Absolute path of the importing file. */
from: string;
/** The specifier as written (query/fragment included). */
specifier: string;
reason: "unresolved" | "unreadable" | "non-literal-glob" | "unsupported-glob";
}unresolved never lists bare specifiers that resolve into node_modules, Node builtins or URL-schemed virtual modules (virtual:…) — those are intentionally opaque. See Unresolved imports.
Example
ts
import { hashup } from "@maastrich/hashup";
const result = await hashup("./src/index.ts", {
extras: ["./package.json"],
});
console.log(result.hash);
console.log(result.files);Sharing a cache across entries
ts
import { createHashupCache, createResolver, hashup } from "@maastrich/hashup";
const cache = createHashupCache();
const resolver = createResolver();
const app = await hashup("./src/app.ts", { cache, resolver });
const worker = await hashup("./src/worker.ts", { cache, resolver });
// Files imported by both app and worker are read + hashed exactly once.result.files still returns only the files reachable from each call's own roots (entry + extras), not the full cache contents.
Notes
- The
filesarray contains every file that was hashed, including files reached throughextras. - Paths in
filesare absolute. - The
hashis stable for a given graph and set of file contents — it does not depend on timestamps or on which absolute directory the project lives in (assumingbaseDiris set consistently). - Imports that resolve into
node_modulesare treated as opaque: they contribute nothing to the hash and their own imports are never walked. To pin installed dependency versions, add your lockfile (pnpm-lock.yaml,package-lock.json, oryarn.lock) toextras. ?queryand#fragmentsuffixes on specifiers are stripped; the target file is hashed by its real path exactly once.import.meta.glob(...)calls with literal patterns are expanded and every match is walked like a regular import.- By default nothing is written to stderr. Pass
logLevel: "warn"(or higher) to surface parse failures and skipped dependencies while debugging.