diff --git a/index.ts b/index.ts index 9089f8f9..21ed4723 100644 --- a/index.ts +++ b/index.ts @@ -28,9 +28,9 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -export {Graph} from './lib/graph'; -export {version} from './lib/version'; -export * as json from './lib/json'; -export * as alg from './lib/alg/index'; +export {Graph} from './lib/graph.js'; +export {version} from './lib/version.js'; +export * as json from './lib/json.js'; +export * as alg from './lib/alg/index.js'; export type {GraphOptions, Edge, Path, WeightFunction, EdgeFunction, Label} from './lib/types.js'; diff --git a/lib/alg/bellman-ford.ts b/lib/alg/bellman-ford.ts index 8b4273f7..37c9086b 100644 --- a/lib/alg/bellman-ford.ts +++ b/lib/alg/bellman-ford.ts @@ -1,5 +1,5 @@ -import {Graph} from '../graph'; -import type {Edge, EdgeFunction, Path, WeightFunction} from '../types'; +import {Graph} from '../graph.js'; +import type {Edge, EdgeFunction, Path, WeightFunction} from '../types.js'; const DEFAULT_WEIGHT_FUNC: WeightFunction = () => 1; diff --git a/lib/alg/components.ts b/lib/alg/components.ts index 9f2fd447..d3f897cf 100644 --- a/lib/alg/components.ts +++ b/lib/alg/components.ts @@ -1,4 +1,4 @@ -import {Graph} from '../graph'; +import {Graph} from '../graph.js'; /** * Finds all connected components in a graph and returns an array of these components. diff --git a/lib/alg/dfs.ts b/lib/alg/dfs.ts index d3721497..0a1c6c93 100644 --- a/lib/alg/dfs.ts +++ b/lib/alg/dfs.ts @@ -1,5 +1,5 @@ -import {Graph} from '../graph'; -import {reduce} from './reduce'; +import {Graph} from '../graph.js'; +import {reduce} from './reduce.js'; /* * Pre- or post-order traversal on the input graph. diff --git a/lib/alg/dijkstra-all.ts b/lib/alg/dijkstra-all.ts index fde0a17c..8f6e7ddb 100644 --- a/lib/alg/dijkstra-all.ts +++ b/lib/alg/dijkstra-all.ts @@ -1,6 +1,6 @@ -import {Graph} from '../graph'; -import type {EdgeFunction, Path, WeightFunction} from '../types'; -import {dijkstra} from './dijkstra'; +import {Graph} from '../graph.js'; +import type {EdgeFunction, Path, WeightFunction} from '../types.js'; +import {dijkstra} from './dijkstra.js'; /** * This function finds the shortest path from each node to every other reachable node in diff --git a/lib/alg/dijkstra.ts b/lib/alg/dijkstra.ts index f78be251..3d97274e 100644 --- a/lib/alg/dijkstra.ts +++ b/lib/alg/dijkstra.ts @@ -1,6 +1,6 @@ -import {Graph} from '../graph'; -import {PriorityQueue} from '../data/priority-queue'; -import type {Edge, EdgeFunction, Path, WeightFunction} from '../types'; +import {Graph} from '../graph.js'; +import {PriorityQueue} from '../data/priority-queue.js'; +import type {Edge, EdgeFunction, Path, WeightFunction} from '../types.js'; const DEFAULT_WEIGHT_FUNC: WeightFunction = () => 1; diff --git a/lib/alg/extract-path.ts b/lib/alg/extract-path.ts index fba4b938..fe488c3c 100644 --- a/lib/alg/extract-path.ts +++ b/lib/alg/extract-path.ts @@ -1,4 +1,4 @@ -import type {Path} from '../types'; +import type {Path} from '../types.js'; interface ExtractedPath { weight: number; diff --git a/lib/alg/find-cycles.ts b/lib/alg/find-cycles.ts index 56549700..af2bbe6e 100644 --- a/lib/alg/find-cycles.ts +++ b/lib/alg/find-cycles.ts @@ -1,5 +1,5 @@ -import {Graph} from '../graph'; -import {tarjan} from './tarjan'; +import {Graph} from '../graph.js'; +import {tarjan} from './tarjan.js'; /** * Given a Graph, graph, this function returns all nodes that are part of a cycle. As there diff --git a/lib/alg/floyd-warshall.ts b/lib/alg/floyd-warshall.ts index 038f0b0e..1781c875 100644 --- a/lib/alg/floyd-warshall.ts +++ b/lib/alg/floyd-warshall.ts @@ -1,5 +1,5 @@ -import {Graph} from '../graph'; -import type {EdgeFunction, Path, WeightFunction} from '../types'; +import {Graph} from '../graph.js'; +import type {EdgeFunction, Path, WeightFunction} from '../types.js'; const DEFAULT_WEIGHT_FUNC: WeightFunction = () => 1; diff --git a/lib/alg/index.ts b/lib/alg/index.ts index a09a0a27..c5ae5afe 100644 --- a/lib/alg/index.ts +++ b/lib/alg/index.ts @@ -1,13 +1,13 @@ -export {bellmanFord} from './bellman-ford'; -export {components} from './components'; -export {dijkstra} from './dijkstra'; -export {dijkstraAll} from './dijkstra-all'; -export {findCycles} from './find-cycles'; -export {floydWarshall} from './floyd-warshall'; -export {isAcyclic} from './is-acyclic'; -export {postorder} from './postorder'; -export {preorder} from './preorder'; -export {prim} from './prim'; -export {shortestPaths} from './shortest-paths'; -export {tarjan} from './tarjan'; -export {topsort, CycleException} from './topsort'; +export {bellmanFord} from './bellman-ford.js'; +export {components} from './components.js'; +export {dijkstra} from './dijkstra.js'; +export {dijkstraAll} from './dijkstra-all.js'; +export {findCycles} from './find-cycles.js'; +export {floydWarshall} from './floyd-warshall.js'; +export {isAcyclic} from './is-acyclic.js'; +export {postorder} from './postorder.js'; +export {preorder} from './preorder.js'; +export {prim} from './prim.js'; +export {shortestPaths} from './shortest-paths.js'; +export {tarjan} from './tarjan.js'; +export {topsort, CycleException} from './topsort.js'; diff --git a/lib/alg/is-acyclic.ts b/lib/alg/is-acyclic.ts index e6f149b2..ee013ba9 100644 --- a/lib/alg/is-acyclic.ts +++ b/lib/alg/is-acyclic.ts @@ -1,5 +1,5 @@ -import {Graph} from '../graph'; -import {CycleException, topsort} from './topsort'; +import {Graph} from '../graph.js'; +import {CycleException, topsort} from './topsort.js'; /** * Given a Graph, graph, this function returns true if the graph has no cycles and returns false if it diff --git a/lib/alg/postorder.ts b/lib/alg/postorder.ts index 19cdbcc9..c03d11db 100644 --- a/lib/alg/postorder.ts +++ b/lib/alg/postorder.ts @@ -1,5 +1,5 @@ -import {Graph} from '../graph'; -import {dfs} from './dfs'; +import {Graph} from '../graph.js'; +import {dfs} from './dfs.js'; /** * Performs post-order depth first traversal on the input graph. If the graph is diff --git a/lib/alg/preorder.ts b/lib/alg/preorder.ts index 38265b3e..267b71b5 100644 --- a/lib/alg/preorder.ts +++ b/lib/alg/preorder.ts @@ -1,5 +1,5 @@ -import {Graph} from '../graph'; -import {dfs} from './dfs'; +import {Graph} from '../graph.js'; +import {dfs} from './dfs.js'; /** * Performs pre-order depth first traversal on the input graph. If the graph is diff --git a/lib/alg/prim.ts b/lib/alg/prim.ts index 887059b9..8c3f1720 100644 --- a/lib/alg/prim.ts +++ b/lib/alg/prim.ts @@ -1,6 +1,6 @@ -import {Graph} from '../graph'; -import {PriorityQueue} from '../data/priority-queue'; -import type {Edge, WeightFunction} from '../types'; +import {Graph} from '../graph.js'; +import {PriorityQueue} from '../data/priority-queue.js'; +import type {Edge, WeightFunction} from '../types.js'; /** * Prim's algorithm takes a connected undirected graph and generates a minimum spanning tree. This diff --git a/lib/alg/reduce.ts b/lib/alg/reduce.ts index a2930373..e3f60c0a 100644 --- a/lib/alg/reduce.ts +++ b/lib/alg/reduce.ts @@ -1,4 +1,4 @@ -import {Graph} from '../graph'; +import {Graph} from '../graph.js'; /* * A helper that preforms a pre- or post-order traversal on the input graph diff --git a/lib/alg/shortest-paths.ts b/lib/alg/shortest-paths.ts index 5e31690b..00b0ba91 100644 --- a/lib/alg/shortest-paths.ts +++ b/lib/alg/shortest-paths.ts @@ -1,7 +1,7 @@ -import {dijkstra} from './dijkstra'; -import {bellmanFord} from './bellman-ford'; -import {Graph} from '../graph'; -import type {EdgeFunction, Path, WeightFunction} from '../types'; +import {dijkstra} from './dijkstra.js'; +import {bellmanFord} from './bellman-ford.js'; +import {Graph} from '../graph.js'; +import type {EdgeFunction, Path, WeightFunction} from '../types.js'; export function shortestPaths( g: Graph, diff --git a/lib/alg/tarjan.ts b/lib/alg/tarjan.ts index d041c966..e943c1b3 100644 --- a/lib/alg/tarjan.ts +++ b/lib/alg/tarjan.ts @@ -1,4 +1,4 @@ -import {Graph} from '../graph'; +import {Graph} from '../graph.js'; interface VisitedEntry { onStack: boolean; diff --git a/lib/alg/topsort.ts b/lib/alg/topsort.ts index 1e601b84..9182fc4e 100644 --- a/lib/alg/topsort.ts +++ b/lib/alg/topsort.ts @@ -1,4 +1,4 @@ -import {Graph} from '../graph'; +import {Graph} from '../graph.js'; export class CycleException extends Error { constructor(message?: string) { diff --git a/lib/graph.ts b/lib/graph.ts index 60067ac0..71a2784b 100644 --- a/lib/graph.ts +++ b/lib/graph.ts @@ -1,4 +1,4 @@ -import type {Edge, EdgeLabelFactory, GraphOptions, Label, NodeLabelFactory} from './types'; +import type {Edge, EdgeLabelFactory, GraphOptions, Label, NodeLabelFactory} from './types.js'; const DEFAULT_EDGE_NAME = "\x00"; const GRAPH_NODE = "\x00"; diff --git a/lib/index.ts b/lib/index.ts index b21cadb5..162b801c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,2 +1,2 @@ -export {Graph} from './graph'; -export {version} from './version'; +export {Graph} from './graph.js'; +export {version} from './version.js'; diff --git a/lib/json.ts b/lib/json.ts index 34af61a4..fda515ea 100644 --- a/lib/json.ts +++ b/lib/json.ts @@ -1,5 +1,5 @@ -import {Graph} from './graph'; -import type {GraphOptions, Label} from './types'; +import {Graph} from './graph.js'; +import type {GraphOptions, Label} from './types.js'; interface JsonGraph { options: GraphOptions; diff --git a/test/dist-types.test.ts b/test/dist-types.test.ts new file mode 100644 index 00000000..c9b1fb82 --- /dev/null +++ b/test/dist-types.test.ts @@ -0,0 +1,103 @@ +// Regression test for the published type declarations (dagrejs/graphlib #238). +// +// tsconfig.build.json compiles with moduleResolution "nodenext", so the build +// itself now rejects an extensionless relative specifier in lib/** or index.ts. +// This test covers the half that a compile of this repo cannot: that a real +// dependent resolving @dagrejs/graphlib through node_modules and the package +// "exports" map gets working types out of the generated declarations. That +// wiring (package.json "exports" -> "types") can regress on its own, as #233 +// showed. +// +// The failure mode has two faces, and the test catches both. With skipLibCheck +// off, tsc reports TS2834 against dist/types/index.d.ts. With it on -- the +// common default -- that error is silenced while every graphlib export quietly +// degrades to `any`; the @ts-expect-error in the consumer below is what catches +// that quieter case, since it only holds if the types resolved. +// +// Declarations are built fresh into a temp package rather than read from the +// committed dist/, so this tests the current source and leaves the working tree +// alone. The real package.json is copied in unmodified, so the "exports" map +// under test is the one that ships. + +import {execFileSync} from 'child_process'; +import {copyFileSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync} from 'fs'; +import {tmpdir} from 'os'; +import {join} from 'path'; + +const repoRoot = process.cwd(); +// Resolved rather than hardcoded so this survives a hoisted or pnpm layout. +const tsc = require.resolve('typescript/bin/tsc'); + +const consumerTsconfig = { + compilerOptions: { + module: 'nodenext', + moduleResolution: 'nodenext', + target: 'es2022', + strict: true, + skipLibCheck: false, + noEmit: true, + // The consumer lives under the OS temp dir, so tsc's automatic @types + // lookup would walk up into directories we do not control and check + // whatever it found. Pin it to nothing; graphlib needs no ambient types. + types: [], + }, + include: ['src'], +}; + +// Mirrors the #238 repro: a plain consumer calling into the public API. +const consumerSource = `import {Graph, alg, json} from '@dagrejs/graphlib'; +import type {Edge} from '@dagrejs/graphlib'; + +const g = new Graph(); +g.setEdge('a', 'b'); +const edges: Edge[] = g.edges(); +alg.isAcyclic(g); +json.write(g); + +// If the declarations failed to resolve, Graph is \`any\`, this assignment stops +// erroring, and tsc flags the directive as unused (TS2578). +// @ts-expect-error nodes() returns string[], not number +const nodes: number = g.nodes(); +`; + +// tsc exits non-zero on any diagnostic, so execFileSync throws and fails the +// test; 'inherit' puts the diagnostics themselves in the jest output. +function runTsc(args: string[], cwd: string): void { + execFileSync(process.execPath, [tsc, ...args], {cwd, stdio: ['ignore', 'inherit', 'inherit']}); +} + +describe('dist type declarations', () => { + let tmpDir: string; + let consumerDir: string; + + beforeAll(() => { + tmpDir = mkdtempSync(join(tmpdir(), 'graphlib-dist-types-')); + const packageDir = join(tmpDir, 'package'); + consumerDir = join(tmpDir, 'consumer'); + + // A stand-in for the published package: the real package.json plus + // declarations emitted from the current source. + mkdirSync(packageDir); + copyFileSync(join(repoRoot, 'package.json'), join(packageDir, 'package.json')); + runTsc(['-p', 'tsconfig.build.json', '--outDir', join(packageDir, 'dist', 'types')], repoRoot); + + // Resolve that package the way a dependent does, through node_modules + // and the "exports" map, rather than by pointing tsc at the .d.ts. + mkdirSync(join(consumerDir, 'node_modules', '@dagrejs'), {recursive: true}); + mkdirSync(join(consumerDir, 'src')); + symlinkSync(packageDir, join(consumerDir, 'node_modules', '@dagrejs', 'graphlib'), 'junction'); + writeFileSync(join(consumerDir, 'package.json'), JSON.stringify({name: 'consumer', private: true, type: 'module'})); + writeFileSync(join(consumerDir, 'tsconfig.json'), JSON.stringify(consumerTsconfig)); + writeFileSync(join(consumerDir, 'src', 'index.ts'), consumerSource); + }, 30000); + + afterAll(() => { + if (tmpDir) { + rmSync(tmpDir, {recursive: true, force: true}); + } + }); + + it('type-check cleanly in a NodeNext consumer', () => { + runTsc(['-p', 'tsconfig.json'], consumerDir); + }, 30000); +}); diff --git a/tsconfig.build.json b/tsconfig.build.json index 0a1e4701..88d9770c 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,6 +1,8 @@ { "extends": "./tsconfig.json", "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", "declaration": true, "declarationMap": true, "emitDeclarationOnly": true,