/** * npmglobalize - Transform file: dependencies to npm versions for publishing * * NOTE: Libraries for future refactoring (currently installed but not used): * - simple-git: For git operations instead of execSync * - pacote: For npm registry operations instead of spawnSync('npm', ['view', ...]) * - @types/pacote, @types/npm-package-arg: Type definitions * * Current approach uses synchronous child_process calls for stability and simplicity. * Consider library-based approach if async operations or cross-platform issues arise. */ import { type NpmCommonConfig } from '@bobfrankston/userconfig'; /** Issue recorded during a build/publish run for end-of-run summary */ export interface BuildIssue { module: string; severity: 'error' | 'warning'; message: string; } /** Record an issue for the end-of-run summary */ export declare function recordBuildIssue(module: string, severity: 'error' | 'warning', message: string): void; /** Get all accumulated build issues */ export declare function getBuildIssues(): readonly BuildIssue[]; /** Clear accumulated issues (call at start of run) */ export declare function clearBuildIssues(): void; /** Extract the first TypeScript error line from build output for the summary. * Returns a short string like "file.ts(42,5): error TS2339: Property 'foo' ..." */ export declare function extractFirstTscError(output: string): string | null; /** A TS7016 — "Could not find a declaration file for module 'X'" — that tsc blames * on the file being compiled is frequently not that file's fault: the copy of X in * node_modules carries no `.d.ts` whatsoever. That happens when X was published at * a moment its declaration output was not on disk, so the tarball ships JS only and * every consumer resolving X through the registry fails identically. tsc's stock * advice — `npm i --save-dev @types/bobfrankston__hlib` — then sends the user after * a types package that does not and will never exist, which is worse than no advice. * Recognize the shape and say what actually fixes it. * Returns one diagnosis line per untyped module; empty when TS7016 has another cause. */ export declare function diagnoseUntypedDeps(cwd: string, buildOutput: string): string[]; /** One package that depends on this one, recorded in this package's * .globalize.json5 when that package publishes. */ export interface UpstreamEntry { /** Absolute path of the consumer's checkout */ path: string; /** Consumer's version at the time it was recorded */ version: string; /** Date the entry was last written (YYYY-MM-DD) */ updated: string; } /** Options for the globalize operation */ export interface GlobalizeOptions { /** Bump type: patch (default), minor, major */ bump?: 'patch' | 'minor' | 'major'; /** Just transform, don't publish */ noPublish?: boolean; /** Restore from .dependencies */ cleanup?: boolean; /** Global install after publish (from registry) */ install?: boolean; /** Global install via symlink (npm install -g .) */ link?: boolean; /** Also install in WSL */ wsl?: boolean; /** Continue despite git errors */ force?: boolean; /** Keep file: paths after publish (default true) */ files?: boolean; /** Show what would happen */ dryRun?: boolean; /** Suppress npm warnings (default true) */ quiet?: boolean; /** Show verbose output */ verbose?: boolean; /** Initialize git/npm if needed */ init?: boolean; /** Git visibility: private (default) or public */ gitVisibility?: 'private' | 'public'; /** npm visibility: private (default) or public */ npmVisibility?: 'private' | 'public'; /** Allow .ts source files (and *.map, tsconfig.json) in the published npm tarball. * undefined: follow noEmit detection (noEmit → allow). true: always allow. * false: always exclude via .npmignore. Set via `-npm ts` / `-npm nts`. */ allowTs?: boolean; /** Custom commit message */ message?: string; /** Check and update ignore files to conform to best practices */ conform?: boolean; /** Keep ignore files as-is without checking */ asis?: boolean; /** Check and update existing npm dependencies to latest versions */ updateDeps?: boolean; /** Allow major version updates (breaking changes) */ updateMajor?: boolean; /** Publish file: dependencies before converting them */ publishDeps?: boolean; /** Auto-yes to dep-cascade prompts (add scope to make private, etc.); does NOT auto-yes public prompts */ publishDepsYes?: boolean; /** Cascade npmVisibility:"public" to all transitive workspace/file: deps without prompting. * In workspace mode: fixpoint-promotes every workspace member reachable from a public consumer. * In single-package mode: drops the "first publish only" guard so already-published deps also * get the parent's npmVisibility propagated. Use to fix a workspace where a public member's * deps weren't all marked public (and the published tarball would 404 on install). */ publicDeps?: boolean; /** Force republish dependencies even if version exists on npm */ forcePublish?: boolean; /** Run npm audit and fix vulnerabilities */ fix?: boolean; /** Automatically fix version/tag mismatches */ fixTags?: boolean; /** Automatically rebase if local is behind remote */ rebase?: boolean; /** Show package.json dependency changes */ show?: boolean; /** Filter to specific workspace packages (by name or dir name) */ workspaceFilter?: string[]; /** Disable workspace mode even at a workspace root */ noWorkspace?: boolean; /** Continue processing remaining packages if one fails (workspace mode) */ continueOnError?: boolean; /** Update package.json scripts to use npmglobalize */ package?: boolean; /** Don't persist CLI flags to .globalize.json5 */ once?: boolean; /** Run importgen to update import maps before publishing */ importgen?: boolean; /** Sub-directories with their own tsconfig.json that `build` must also compile * (e.g. a service worker in `Sw/`). undefined: auto-detect from * .vscode/tasks.json + nested tsconfigs. An array pins the list. * false: never ask about sub-projects here. */ subProjects?: string[] | false; /** Use filesystem paths for `file:` deps (default true). Set false to * mark a package as publishable/installable even when sibling checkouts * are absent. Currently declarative (recorded in config and displayed); * the actual conversion path is TODO. */ usePaths?: boolean; /** Local install only — skip transform/publish, just npm install -g . */ local?: boolean; /** Build every file: dep even when outputs look up to date (skips the freshness check) */ forceBuild?: boolean; /** Freeze node_modules: replace symlinks/junctions with real copies for network share use */ freeze?: boolean; /** Before `npm pack`, delete `node_modules/` inside each `file:` dep target. * Works around arborist crashes when siblings have nested node_modules. */ cleanNestedModules?: boolean; /** Internal: auto-initialize git repos without prompting (user chose "all") */ autoInit?: boolean; /** Strict adopt: require that a reachable git remote exists in * package.json.repository. Aborts (rather than creating a fresh repo) if * the probe fails. Skips the no-git prompt. */ adopt?: boolean; /** Run the undeclared-imports scan (`-strict-imports`). Default false — * the scan assumes a style where every import is declared in the * consuming package's package.json, which doesn't hold for monorepos * that rely on workspace cross-refs / -public-deps cascade. Opt in when * the assumption holds. */ importCheck?: boolean; /** Bookkeeping, not a CLI option: the packages that depend on this one, * written into this package's .globalize.json5 when each of them * publishes. Immediate consumers only - a full consumer tree is walked by * following each entry's own .globalize.json5. FYI for now: recorded and * displayed, nothing is updated automatically. */ upstream?: UpstreamEntry[]; /** Internal: signals this call is from workspace orchestrator */ /** Skip the upfront dep-graph prescan */ noPrescan?: boolean; /** Internal: recursive dep-cascade call (suppresses prescan + banner) */ _fromDep?: boolean; _fromWorkspace?: boolean; /** Internal: signals this call is from CLI (version already printed) */ _fromCli?: boolean; /** Internal: tracks which options were explicitly set on the CLI */ explicitKeys?: Set; } /** Result from a single package in workspace mode */ interface WorkspacePackageResult { name: string; dir: string; success: boolean; version?: string; error?: string; skipped?: boolean; } /** Aggregate result from workspace orchestration */ export interface WorkspaceResult { success: boolean; packages: WorkspacePackageResult[]; publishOrder: string[]; } /** Read and parse package.json from a directory */ export declare function readPackageJson(dir: string): any; /** Global npm config from %USERPROFILE%\.userconfig\npm.json5 — via @bobfrankston/userconfig */ export type UserNpmConfig = NpmCommonConfig; /** Get the .userconfig directory path (%USERPROFILE%\.userconfig) */ export declare function getUserConfigDir(): string; /** Read global npm config from %USERPROFILE%\.userconfig\npm.json5 */ export declare function readUserNpmConfig(): UserNpmConfig; /** Write global npm config to %USERPROFILE%\.userconfig\npm.json5 */ export declare function writeUserNpmConfig(config: UserNpmConfig): void; /** Read .globalize.json5 config file */ export declare function readConfig(dir: string): Partial; /** Write .globalize.json5 config file */ export declare function writeConfig(dir: string, config: Partial, explicitKeys?: Set): void; /** Write package.json to a directory */ export declare function writePackageJson(dir: string, pkg: any): void; /** Install signal/exit handlers that restore `.dependencies` backups on abnormal exit. */ export declare function installCleanupHandlers(): void; /** Resolve a file: path to absolute path */ export declare function resolveFilePath(fileRef: string, baseDir: string): string; /** Check if a dependency value is a file: reference */ export declare function isFileRef(value: string): boolean; /** Get the latest version of a package from npm */ export declare function getLatestVersion(packageName: string): string | null; /** Check if a specific version of a package exists on npm */ export declare function checkVersionExists(packageName: string, version: string): boolean; /** Check if a package exists on npm (any version) */ export declare function checkPackageExists(packageName: string): boolean; /** Check npm package access level (public/restricted/null if not published) */ export declare function checkNpmAccess(packageName: string): 'public' | 'restricted' | null; /** Walk `file:` deps transitively from a starting directory and ensure each is set * up to be installable from a public consumer: persists `npmVisibility:"public"` in * each dep's `.globalize.json5`, and flips npm access from `restricted` to `public` * for any dep that's already on npm. Returns blockers for deps that can't be made * public (private:true or noPublish:true). Used by single-package `-public-deps` * to handle deps that are unchanged locally — those would otherwise be skipped by * the cascade and stay private on npm. */ export declare function cascadePublicVisibility(cwd: string, opts?: { dryRun?: boolean; verbose?: boolean; }): Promise<{ promoted: string[]; configWritten: string[]; blockers: Array<{ name: string; reason: string; }>; }>; /** Check if public package has private/inaccessible dependencies */ export declare function checkPrivateDependencies(pkg: any, verbose?: boolean): { name: string; depType: string; }[]; /** Update existing npm dependencies to latest versions */ export declare function updateNpmDeps(pkg: any, verbose?: boolean, allowMajor?: boolean): { updated: boolean; changes: string[]; majorAvailable: Array<{ name: string; current: string; latest: string; }>; }; /** Get all file: dependencies from package.json */ export declare function getFileRefs(pkg: any): Map; /** Expand workspace entries (which may include globs like 'packages/*') to relative * dir paths from rootDir, normalized to forward slashes. Only `/*` is expanded; * more exotic globs (`**`, `?`, character classes) are passed through unchanged. */ export declare function expandWorkspaceEntries(rootDir: string, entries: string[]): string[]; /** Resolve workspace entries to package info. Skips dirs without package.json or with private:true. */ export declare function resolveWorkspacePackages(rootDir: string): Array<{ name: string; dir: string; pkg: any; }>; /** Like resolveWorkspacePackages but INCLUDES private packages and exposes the * workspaces[] entry (relative path) for each. Use for build ordering, where * a private workspace package is still part of the dep graph. */ export declare function resolveAllWorkspacePackages(rootDir: string): Array<{ name: string; dir: string; pkg: any; entry: string; }>; /** Build a dependency graph among workspace packages. Returns Map>. */ export declare function buildDependencyGraph(packages: Array<{ name: string; dir: string; pkg: any; }>): Map>; /** Topological sort with cycle detection. Returns package names in dependency order. */ export declare function topologicalSort(graph: Map>): string[]; /** Transform file: dependencies to npm versions */ export type UnpublishedDep = { name: string; version: string; path: string; reason: 'new' | 'update'; }; /** Vet the casing of every `file:` dependency path, in BOTH places it is recorded: * the spec in package.json and the junction npm created in node_modules. * * npm resolves a `file:` spec by string concatenation against the cwd and writes * the result as the link target — it never canonicalizes case, and never checks * that the target exists. On Windows that rots invisibly: `projects/nodejs/x` * resolves fine when the directory is really `projects/NodeJS/x`, so nothing * complains until the path reaches somewhere case matters. Two places it does: * WSL and Linux deploys can't resolve it at all, and npm's own arborist keys * nodes by path string, so one directory reached under two spellings becomes two * nodes and tree loading dies with "Cannot read properties of null". * * Both records must be repaired together. Fixing only the manifest leaves the * stale junction in place (npm will not recreate a link it thinks is satisfied); * fixing only the junction lets the next `npm install` write the bad case back. */ export declare function verifyDepPathCase(cwd: string, pkg: any): { specs: string[]; links: string[]; missing: string[]; }; export declare function transformDeps(pkg: any, baseDir: string, verbose?: boolean, forcePublish?: boolean): { transformed: boolean; unpublished: UnpublishedDep[]; }; /** A problem discovered by the prescan. */ export interface PrescanIssue { severity: 'error' | 'warning'; package: string; path: string; message: string; suggestion?: string; } export interface UndeclaredImport { name: string; file: string; line: number; } export declare function findUndeclaredImports(cwd: string, pkg: any): UndeclaredImport[]; /** Walk the full file: dep graph and collect issues up front — missing scopes, * unresolvable paths, missing package.json, unpublished transitives. Lets the * user resolve all problems before starting the publish cascade instead of * being interrupted mid-run. */ export declare function prescanDepGraph(baseDir: string, visited?: Set, verbose?: boolean): PrescanIssue[]; /** Restore file: dependencies from .dependencies using a three-way merge that * preserves any external modifications made since the last transform. */ export declare function restoreDeps(pkg: any, verbose?: boolean): boolean; /** Check if .dependencies exist (already transformed) */ export declare function hasBackup(pkg: any): boolean; /** Record this package in the `upstream` list of each package it depends on * via file:, so a library knows who consumes it. * * Immediate consumers only — each entry's own .globalize.json5 carries its * consumers, so the full tree is a walk rather than a copy. Nothing acts on * the list yet; it's FYI for a future "update my consumers" pass. * * Best-effort: a dep whose config can't be written is reported and skipped, * never fatal - this runs after a successful publish. */ export declare function recordUpstream(cwd: string, dryRun?: boolean, verbose?: boolean): Promise; /** Get the latest git tag (if any) */ export declare function getLatestGitTag(cwd: string): string | null; /** Check if a git tag exists */ export declare function gitTagExists(cwd: string, tag: string): boolean; /** Delete a git tag */ export declare function deleteGitTag(cwd: string, tag: string): boolean; /** Get all git tags */ export declare function getAllGitTags(cwd: string): string[]; /** Parse version from tag (e.g., 'v1.2.3' -> [1, 2, 3]) */ export declare function parseVersionTag(tag: string): number[] | null; /** Compare two version arrays (returns -1 if a < b, 0 if equal, 1 if a > b) */ export declare function compareVersions(a: number[], b: number[]): number; /** Fix version/tag mismatches */ export declare function fixVersionTagMismatch(cwd: string, pkg: any, verbose?: boolean): boolean; export declare function printPnpmSuggestionSummary(): void; /** Return declared deps (dependencies + devDependencies) that don't resolve from * `pkgDir`. Skips `workspace:`/`link:` specs (handled by workspace tooling / * rarely used). `file:` deps are checked: npm installs them as junctions * (Windows) or symlinks, and `fs.existsSync` traverses both, so a missing * file: dep is a real out-of-sync case worth flagging. * A declared dep that doesn't resolve means `package.json` and installed * `node_modules/` are out of sync (e.g. dep added but `npm install` not re-run). */ export declare function missingDeps(pkgDir: string, pkg: any): string[]; /** Walk `file:` deps transitively and run `npm install` in any target whose * declared deps aren't all resolvable from its directory. Covers both * "fresh clone, no `node_modules/`" and "dep added but `npm install` not * re-run" (partial-sync) cases. Also covers `cwd` itself on the first call. * Cycle-safe via the shared `visited` set. */ export declare function ensureFileDepModules(cwd: string, verbose?: boolean, visited?: Set): Promise; /** Cheap freshness check so the build cascade can skip packages whose output is * already current. Returns true only when provably up to date: * - outDir projects: newest source (.ts/.tsx/.mts/.cts + tsconfig*.json) must * not be newer than the newest file under outDir. * - side-by-side projects: every source must have an emitted sibling * (.js/.jsx/.mjs/.cjs or declaration) at least as new. * Conservative: project references, allowJs, missing outputs, or unreadable * tsconfig all report stale (→ build). package.json mtime is deliberately * ignored — npmglobalize itself rewrites it around every publish, which would * otherwise force a rebuild on every run. */ export declare function isBuildUpToDate(cwd: string): boolean; /** Detect whether a package is an importgen project — a browser app whose HTML * carries a generated `