importree 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # importree
2
+
3
+ [![npm version](https://img.shields.io/npm/v/importree)](https://www.npmjs.com/package/importree)
4
+ [![npm downloads](https://img.shields.io/npm/dm/importree)](https://www.npmjs.com/package/importree)
5
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/importree)](https://bundlephobia.com/package/importree)
6
+ [![license](https://img.shields.io/npm/l/importree)](https://github.com/alexgrozav/importree/blob/main/LICENSE)
7
+
8
+ Build import dependency trees for TypeScript and JavaScript files. Fast, zero-dependency static analysis for dependency detection and cache invalidation.
9
+
10
+ When a file changes, you need to know what else is affected. importree builds the full import dependency tree for any TypeScript or JavaScript entry point — with zero dependencies and zero AST overhead.
11
+
12
+ Built for CI pipelines, build tools, monorepo task runners, and test selectors.
13
+
14
+ [Website](https://alexgrozav.github.io/importree/) · [GitHub](https://github.com/alexgrozav/importree) · [npm](https://www.npmjs.com/package/importree)
15
+
16
+ ## Highlights
17
+
18
+ - **Zero dependencies** — Built entirely on Node.js built-ins. No native binaries, no WASM.
19
+ - **Fast scanning** — Regex-based import extraction with concurrent async file traversal. No AST parsing overhead.
20
+ - **Path alias support** — Resolve `@/components`, `~/utils`, or any custom alias with longest-prefix matching and automatic extension probing.
21
+ - **Cache invalidation** — Pre-computed reverse dependency graph answers "what needs rebuilding?" instantly.
22
+ - **Dual output** — Ships both ESM and CJS with full TypeScript declarations.
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ npm install importree
28
+ ```
29
+
30
+ Requires Node.js >= 18.
31
+
32
+ ## Quick Start
33
+
34
+ ### Build the tree
35
+
36
+ ```ts
37
+ import { importree } from 'importree';
38
+
39
+ const tree = await importree('./src/index.ts', {
40
+ aliases: { '@': './src' },
41
+ });
42
+
43
+ console.log(tree.files);
44
+ // ['/abs/src/index.ts', '/abs/src/app.ts', ...]
45
+
46
+ console.log(tree.externals);
47
+ // ['react', 'lodash', 'node:path']
48
+
49
+ console.log(tree.graph);
50
+ // { '/abs/src/index.ts': ['/abs/src/app.ts', ...] }
51
+ ```
52
+
53
+ ### Find affected files
54
+
55
+ ```ts
56
+ import { importree, getAffectedFiles } from 'importree';
57
+
58
+ const tree = await importree('./src/index.ts');
59
+
60
+ // When utils.ts changes, what needs rebuilding?
61
+ const affected = getAffectedFiles(tree, './src/utils.ts');
62
+
63
+ console.log(affected);
64
+ // ['/abs/src/app.ts', '/abs/src/index.ts']
65
+ // ^ every file that transitively depends on utils.ts
66
+ ```
67
+
68
+ ## API
69
+
70
+ ### `importree(entry, options?)`
71
+
72
+ Recursively resolves all static imports, dynamic imports, `require()` calls, and re-exports starting from the entry file. Returns the full dependency graph.
73
+
74
+ ```ts
75
+ importree(entry: string, options?: ImportreeOptions): Promise<ImportTree>
76
+ ```
77
+
78
+ #### Parameters
79
+
80
+ | Parameter | Type | Required | Description |
81
+ |-----------|------|----------|-------------|
82
+ | `entry` | `string` | Yes | Path to the entry file (resolved against `cwd`) |
83
+ | `options` | `ImportreeOptions` | No | Configuration for resolution behavior |
84
+
85
+ #### `ImportreeOptions`
86
+
87
+ | Option | Type | Default | Description |
88
+ |--------|------|---------|-------------|
89
+ | `rootDir` | `string` | `process.cwd()` | Root directory for resolving relative alias paths |
90
+ | `aliases` | `Record<string, string>` | `{}` | Path alias mappings (e.g., `{ '@': './src' }`) |
91
+ | `extensions` | `string[]` | `['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']` | File extensions to try when resolving extensionless imports |
92
+
93
+ #### Returns
94
+
95
+ `Promise<ImportTree>` — the resolved dependency tree.
96
+
97
+ ---
98
+
99
+ ### `getAffectedFiles(tree, changedFile)`
100
+
101
+ BFS traversal of the reverse dependency graph. Returns all files that transitively depend on the changed file — sorted, deterministic, and without the changed file itself.
102
+
103
+ ```ts
104
+ getAffectedFiles(tree: ImportTree, changedFile: string): string[]
105
+ ```
106
+
107
+ #### Parameters
108
+
109
+ | Parameter | Type | Required | Description |
110
+ |-----------|------|----------|-------------|
111
+ | `tree` | `ImportTree` | Yes | A tree previously returned by `importree()` |
112
+ | `changedFile` | `string` | Yes | Path to the file that changed (resolved to absolute) |
113
+
114
+ #### Returns
115
+
116
+ `string[]` — sorted absolute paths of all files that transitively depend on the changed file. The changed file itself is excluded. Returns an empty array if the file is not in the graph.
117
+
118
+ ---
119
+
120
+ ### `ImportTree`
121
+
122
+ The result object returned by `importree()`.
123
+
124
+ | Field | Type | Description |
125
+ |-------|------|-------------|
126
+ | `entrypoint` | `string` | Absolute path of the entry file |
127
+ | `files` | `string[]` | Sorted absolute paths of all local files in the dependency tree |
128
+ | `externals` | `string[]` | Sorted unique bare import specifiers — packages like `react`, `lodash`, `node:fs` |
129
+ | `graph` | `Record<string, string[]>` | Forward adjacency list. Each file maps to its direct local imports. |
130
+ | `reverseGraph` | `Record<string, string[]>` | Reverse adjacency list. Each file maps to files that import it. |
131
+
132
+ ## What gets detected
133
+
134
+ importree extracts specifiers from all standard import patterns:
135
+
136
+ - Static imports — `import { foo } from './bar'`
137
+ - Default imports — `import foo from './bar'`
138
+ - Namespace imports — `import * as foo from './bar'`
139
+ - Side-effect imports — `import './bar'`
140
+ - Type imports — `import type { Foo } from './bar'`
141
+ - Dynamic imports — `import('./bar')`
142
+ - CommonJS require — `require('./bar')`
143
+ - Re-exports — `export { foo } from './bar'`, `export * from './bar'`
144
+
145
+ Imports inside comments and string literals are ignored.
146
+
147
+ Circular dependencies are handled — each file is visited once.
148
+
149
+ ## Resolution
150
+
151
+ 1. **Relative imports** (`./` or `../`) resolve against the importing file's directory.
152
+ 2. **Alias imports** match against the configured `aliases` using longest-prefix matching, then resolve as relative paths from `rootDir`.
153
+ 3. **Bare specifiers** (e.g., `react`, `@scope/pkg`, `node:fs`) are classified as external and collected in `externals`.
154
+
155
+ For each resolved path, importree probes in order:
156
+
157
+ 1. Exact path
158
+ 2. Path + each extension (`.ts`, `.tsx`, `.js`, `.jsx`, `.mjs`, `.cjs`)
159
+ 3. Path as directory + `index` + each extension
160
+
161
+ ## License
162
+
163
+ ISC — [Alex Grozav](https://github.com/alexgrozav)
package/dist/index.cjs ADDED
@@ -0,0 +1,284 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let node_path = require("node:path");
3
+ let node_fs_promises = require("node:fs/promises");
4
+ let node_fs = require("node:fs");
5
+ //#region src/scanner.ts
6
+ /**
7
+ * Strips comments from source code while preserving string literals.
8
+ *
9
+ * Comments are replaced with spaces (preserving newlines). Strings and
10
+ * template literals are left intact so that import specifiers inside
11
+ * `from 'specifier'` remain extractable. The function correctly handles
12
+ * comment-like sequences inside strings (e.g., `'//'` won't start a comment).
13
+ */
14
+ function stripComments(code) {
15
+ const len = code.length;
16
+ const result = new Array(len);
17
+ let i = 0;
18
+ while (i < len) {
19
+ const ch = code[i];
20
+ const next = i + 1 < len ? code[i + 1] : "";
21
+ if (ch === "/" && next === "/") {
22
+ result[i++] = " ";
23
+ result[i++] = " ";
24
+ while (i < len && code[i] !== "\n") result[i++] = " ";
25
+ continue;
26
+ }
27
+ if (ch === "/" && next === "*") {
28
+ result[i++] = " ";
29
+ result[i++] = " ";
30
+ while (i < len && !(code[i] === "*" && i + 1 < len && code[i + 1] === "/")) {
31
+ result[i] = code[i] === "\n" ? "\n" : " ";
32
+ i++;
33
+ }
34
+ if (i < len) {
35
+ result[i++] = " ";
36
+ result[i++] = " ";
37
+ }
38
+ continue;
39
+ }
40
+ if (ch === "'" || ch === "\"") {
41
+ const quote = ch;
42
+ result[i] = code[i];
43
+ i++;
44
+ while (i < len && code[i] !== quote) if (code[i] === "\\" && i + 1 < len) {
45
+ result[i] = code[i];
46
+ i++;
47
+ result[i] = code[i];
48
+ i++;
49
+ } else {
50
+ result[i] = code[i];
51
+ i++;
52
+ }
53
+ if (i < len) {
54
+ result[i] = code[i];
55
+ i++;
56
+ }
57
+ continue;
58
+ }
59
+ if (ch === "`") {
60
+ result[i] = code[i];
61
+ i++;
62
+ let depth = 0;
63
+ while (i < len) if (code[i] === "\\" && i + 1 < len) {
64
+ result[i] = code[i];
65
+ i++;
66
+ result[i] = code[i];
67
+ i++;
68
+ } else if (code[i] === "$" && i + 1 < len && code[i + 1] === "{") {
69
+ result[i] = code[i];
70
+ i++;
71
+ result[i] = code[i];
72
+ i++;
73
+ depth++;
74
+ } else if (code[i] === "}" && depth > 0) {
75
+ result[i] = code[i];
76
+ i++;
77
+ depth--;
78
+ } else if (code[i] === "`" && depth === 0) {
79
+ result[i] = code[i];
80
+ i++;
81
+ break;
82
+ } else {
83
+ result[i] = code[i];
84
+ i++;
85
+ }
86
+ continue;
87
+ }
88
+ result[i] = ch;
89
+ i++;
90
+ }
91
+ return result.join("");
92
+ }
93
+ const fromRe = /\bfrom\s+['"]([^'"]+)['"]/g;
94
+ const sideEffectRe = /\bimport\s+['"]([^'"]+)['"]/g;
95
+ const dynamicRe = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
96
+ const requireRe = /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
97
+ /**
98
+ * Scans source code and extracts all import/require specifiers.
99
+ *
100
+ * Handles: static imports, dynamic imports, require(), re-exports.
101
+ * Ignores imports inside comments. Imports inside string literals may
102
+ * produce false positives, but unresolvable paths are silently skipped
103
+ * by the resolver.
104
+ */
105
+ function scanImports(code) {
106
+ const stripped = stripComments(code);
107
+ const specifiers = /* @__PURE__ */ new Set();
108
+ for (const m of stripped.matchAll(fromRe)) specifiers.add(m[1]);
109
+ for (const m of stripped.matchAll(sideEffectRe)) specifiers.add(m[1]);
110
+ for (const m of stripped.matchAll(dynamicRe)) specifiers.add(m[1]);
111
+ for (const m of stripped.matchAll(requireRe)) specifiers.add(m[1]);
112
+ return [...specifiers];
113
+ }
114
+ //#endregion
115
+ //#region src/resolver.ts
116
+ const DEFAULT_EXTENSIONS = [
117
+ ".ts",
118
+ ".tsx",
119
+ ".js",
120
+ ".jsx",
121
+ ".mjs",
122
+ ".cjs"
123
+ ];
124
+ function fileExists(filePath) {
125
+ const stat = (0, node_fs.statSync)(filePath, { throwIfNoEntry: false });
126
+ return stat !== void 0 && stat.isFile();
127
+ }
128
+ function dirExists(filePath) {
129
+ const stat = (0, node_fs.statSync)(filePath, { throwIfNoEntry: false });
130
+ return stat !== void 0 && stat.isDirectory();
131
+ }
132
+ /**
133
+ * Extract the bare package name from an import specifier.
134
+ * - Scoped: `@scope/pkg/path` → `@scope/pkg`
135
+ * - Unscoped: `pkg/path` → `pkg`
136
+ */
137
+ function getBareSpecifier(specifier) {
138
+ if (specifier.startsWith("@")) {
139
+ const parts = specifier.split("/");
140
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
141
+ }
142
+ return specifier.split("/")[0];
143
+ }
144
+ function resolveFile(filePath, extensions) {
145
+ if (fileExists(filePath)) return filePath;
146
+ for (const ext of extensions) {
147
+ const withExt = filePath + ext;
148
+ if (fileExists(withExt)) return withExt;
149
+ }
150
+ if (dirExists(filePath)) for (const ext of extensions) {
151
+ const indexPath = (0, node_path.join)(filePath, `index${ext}`);
152
+ if (fileExists(indexPath)) return indexPath;
153
+ }
154
+ }
155
+ /**
156
+ * Creates a resolver function that resolves import specifiers to absolute
157
+ * file paths, with support for aliases and extension probing.
158
+ */
159
+ function createResolver(basedir, options) {
160
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
161
+ const resolvedAliasValues = (options.aliases ? Object.entries(options.aliases).sort((a, b) => b[0].length - a[0].length) : []).map(([key, value]) => [key, (0, node_path.isAbsolute)(value) ? value : (0, node_path.resolve)(basedir, value)]);
162
+ const cache = /* @__PURE__ */ new Map();
163
+ return function resolveSpecifier(specifier, fromFile) {
164
+ const fromDir = (0, node_path.dirname)(fromFile);
165
+ const cacheKey = `${specifier}\0${fromDir}`;
166
+ if (cache.has(cacheKey)) return cache.get(cacheKey);
167
+ let result;
168
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
169
+ const absolutePath = resolveFile((0, node_path.resolve)(fromDir, specifier), extensions);
170
+ if (absolutePath) result = {
171
+ type: "local",
172
+ absolutePath
173
+ };
174
+ } else {
175
+ let matched = false;
176
+ for (const [prefix, replacement] of resolvedAliasValues) if (specifier === prefix || specifier.startsWith(prefix + "/")) {
177
+ const absolutePath = resolveFile((0, node_path.join)(replacement, specifier === prefix ? "" : specifier.slice(prefix.length)), extensions);
178
+ if (absolutePath) result = {
179
+ type: "local",
180
+ absolutePath
181
+ };
182
+ matched = true;
183
+ break;
184
+ }
185
+ if (!matched) result = {
186
+ type: "external",
187
+ specifier: getBareSpecifier(specifier)
188
+ };
189
+ }
190
+ cache.set(cacheKey, result);
191
+ return result;
192
+ };
193
+ }
194
+ //#endregion
195
+ //#region src/walker.ts
196
+ /**
197
+ * Recursively walks imports starting from an entry file and builds
198
+ * the full dependency tree.
199
+ */
200
+ async function walk(entryFile, options) {
201
+ const entrypoint = (0, node_path.resolve)(entryFile);
202
+ const resolveSpecifier = createResolver(options.rootDir ? (0, node_path.resolve)(options.rootDir) : process.cwd(), options);
203
+ const graph = {};
204
+ const externals = /* @__PURE__ */ new Set();
205
+ const visited = /* @__PURE__ */ new Set();
206
+ async function visit(filePath) {
207
+ if (visited.has(filePath)) return;
208
+ visited.add(filePath);
209
+ const specifiers = scanImports(await (0, node_fs_promises.readFile)(filePath, "utf-8"));
210
+ const localDeps = [];
211
+ for (const spec of specifiers) {
212
+ const resolved = resolveSpecifier(spec, filePath);
213
+ if (!resolved) continue;
214
+ if (resolved.type === "external" && resolved.specifier) externals.add(resolved.specifier);
215
+ else if (resolved.type === "local" && resolved.absolutePath) localDeps.push(resolved.absolutePath);
216
+ }
217
+ graph[filePath] = localDeps;
218
+ await Promise.all(localDeps.map((dep) => visit(dep)));
219
+ }
220
+ await visit(entrypoint);
221
+ const reverseGraph = {};
222
+ for (const file of Object.keys(graph)) reverseGraph[file] = [];
223
+ for (const [file, deps] of Object.entries(graph)) for (const dep of deps) {
224
+ if (!reverseGraph[dep]) reverseGraph[dep] = [];
225
+ reverseGraph[dep].push(file);
226
+ }
227
+ return {
228
+ entrypoint,
229
+ files: Object.keys(graph).sort(),
230
+ externals: [...externals].sort(),
231
+ graph,
232
+ reverseGraph
233
+ };
234
+ }
235
+ //#endregion
236
+ //#region src/index.ts
237
+ /**
238
+ * Builds a full import dependency tree starting from an entry file.
239
+ *
240
+ * Recursively resolves all static imports, dynamic imports, require() calls,
241
+ * and re-exports. Supports path aliases for custom resolution.
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * const tree = await importree('./src/index.ts', {
246
+ * aliases: { '@': './src' },
247
+ * });
248
+ *
249
+ * console.log(tree.files); // all local dependency file paths
250
+ * console.log(tree.externals); // external package names
251
+ * console.log(tree.graph); // file → direct dependencies
252
+ * ```
253
+ */
254
+ async function importree(entry, options) {
255
+ return walk(entry, options ?? {});
256
+ }
257
+ /**
258
+ * Given an import tree and a changed file, returns all files that
259
+ * transitively depend on the changed file (i.e., files that would
260
+ * need to be re-evaluated if the changed file is modified).
261
+ *
262
+ * The changed file itself is NOT included in the result.
263
+ */
264
+ function getAffectedFiles(tree, changedFile) {
265
+ const absolute = (0, node_path.resolve)(changedFile);
266
+ if (!tree.reverseGraph[absolute]) return [];
267
+ const affected = /* @__PURE__ */ new Set();
268
+ const queue = [absolute];
269
+ while (queue.length > 0) {
270
+ const current = queue.shift();
271
+ const dependents = tree.reverseGraph[current];
272
+ if (!dependents) continue;
273
+ for (const parent of dependents) if (!affected.has(parent)) {
274
+ affected.add(parent);
275
+ queue.push(parent);
276
+ }
277
+ }
278
+ return [...affected].sort();
279
+ }
280
+ //#endregion
281
+ exports.getAffectedFiles = getAffectedFiles;
282
+ exports.importree = importree;
283
+
284
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/scanner.ts","../src/resolver.ts","../src/walker.ts","../src/index.ts"],"sourcesContent":["/**\n * Strips comments from source code while preserving string literals.\n *\n * Comments are replaced with spaces (preserving newlines). Strings and\n * template literals are left intact so that import specifiers inside\n * `from 'specifier'` remain extractable. The function correctly handles\n * comment-like sequences inside strings (e.g., `'//'` won't start a comment).\n */\nexport function stripComments(code: string): string {\n const len = code.length;\n const result: string[] = new Array(len);\n let i = 0;\n\n while (i < len) {\n const ch = code[i];\n const next = i + 1 < len ? code[i + 1] : '';\n\n // Line comment → blank to end of line\n if (ch === '/' && next === '/') {\n result[i++] = ' ';\n result[i++] = ' ';\n while (i < len && code[i] !== '\\n') {\n result[i++] = ' ';\n }\n continue;\n }\n\n // Block comment → blank to closing */\n if (ch === '/' && next === '*') {\n result[i++] = ' ';\n result[i++] = ' ';\n while (i < len && !(code[i] === '*' && i + 1 < len && code[i + 1] === '/')) {\n result[i] = code[i] === '\\n' ? '\\n' : ' ';\n i++;\n }\n if (i < len) {\n result[i++] = ' '; // *\n result[i++] = ' '; // /\n }\n continue;\n }\n\n // Single or double quoted string — copy verbatim (skip past to avoid\n // misidentifying comment markers inside strings)\n if (ch === \"'\" || ch === '\"') {\n const quote = ch;\n result[i] = code[i];\n i++;\n while (i < len && code[i] !== quote) {\n if (code[i] === '\\\\' && i + 1 < len) {\n result[i] = code[i];\n i++;\n result[i] = code[i];\n i++;\n } else {\n result[i] = code[i];\n i++;\n }\n }\n if (i < len) {\n result[i] = code[i];\n i++;\n }\n continue;\n }\n\n // Template literal — copy verbatim, handling ${} nesting\n if (ch === '`') {\n result[i] = code[i];\n i++;\n let depth = 0;\n while (i < len) {\n if (code[i] === '\\\\' && i + 1 < len) {\n result[i] = code[i];\n i++;\n result[i] = code[i];\n i++;\n } else if (code[i] === '$' && i + 1 < len && code[i + 1] === '{') {\n result[i] = code[i];\n i++;\n result[i] = code[i];\n i++;\n depth++;\n } else if (code[i] === '}' && depth > 0) {\n result[i] = code[i];\n i++;\n depth--;\n } else if (code[i] === '`' && depth === 0) {\n result[i] = code[i];\n i++;\n break;\n } else {\n result[i] = code[i];\n i++;\n }\n }\n continue;\n }\n\n // Regular character\n result[i] = ch;\n i++;\n }\n\n return result.join('');\n}\n\n// Static regex patterns — compiled once\nconst fromRe = /\\bfrom\\s+['\"]([^'\"]+)['\"]/g;\nconst sideEffectRe = /\\bimport\\s+['\"]([^'\"]+)['\"]/g;\nconst dynamicRe = /\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\nconst requireRe = /\\brequire\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\n/**\n * Scans source code and extracts all import/require specifiers.\n *\n * Handles: static imports, dynamic imports, require(), re-exports.\n * Ignores imports inside comments. Imports inside string literals may\n * produce false positives, but unresolvable paths are silently skipped\n * by the resolver.\n */\nexport function scanImports(code: string): string[] {\n const stripped = stripComments(code);\n const specifiers = new Set<string>();\n\n for (const m of stripped.matchAll(fromRe)) specifiers.add(m[1]);\n for (const m of stripped.matchAll(sideEffectRe)) specifiers.add(m[1]);\n for (const m of stripped.matchAll(dynamicRe)) specifiers.add(m[1]);\n for (const m of stripped.matchAll(requireRe)) specifiers.add(m[1]);\n\n return [...specifiers];\n}\n","import { statSync } from 'node:fs';\nimport { dirname, join, resolve, isAbsolute } from 'node:path';\nimport type { ImportreeOptions, ResolvedImport } from './types.js';\n\nconst DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];\n\nfunction fileExists(filePath: string): boolean {\n const stat = statSync(filePath, { throwIfNoEntry: false });\n return stat !== undefined && stat.isFile();\n}\n\nfunction dirExists(filePath: string): boolean {\n const stat = statSync(filePath, { throwIfNoEntry: false });\n return stat !== undefined && stat.isDirectory();\n}\n\n/**\n * Extract the bare package name from an import specifier.\n * - Scoped: `@scope/pkg/path` → `@scope/pkg`\n * - Unscoped: `pkg/path` → `pkg`\n */\nfunction getBareSpecifier(specifier: string): string {\n if (specifier.startsWith('@')) {\n const parts = specifier.split('/');\n return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;\n }\n return specifier.split('/')[0];\n}\n\nfunction resolveFile(\n filePath: string,\n extensions: string[],\n): string | undefined {\n // Try exact path\n if (fileExists(filePath)) return filePath;\n\n // Try with each extension\n for (const ext of extensions) {\n const withExt = filePath + ext;\n if (fileExists(withExt)) return withExt;\n }\n\n // Try as directory with index file\n if (dirExists(filePath)) {\n for (const ext of extensions) {\n const indexPath = join(filePath, `index${ext}`);\n if (fileExists(indexPath)) return indexPath;\n }\n }\n\n return undefined;\n}\n\nexport interface Resolver {\n (specifier: string, fromFile: string): ResolvedImport | undefined;\n}\n\n/**\n * Creates a resolver function that resolves import specifiers to absolute\n * file paths, with support for aliases and extension probing.\n */\nexport function createResolver(\n basedir: string,\n options: ImportreeOptions,\n): Resolver {\n const extensions = options.extensions ?? DEFAULT_EXTENSIONS;\n\n // Sort aliases by key length descending for longest-prefix matching\n const aliases = options.aliases\n ? Object.entries(options.aliases).sort((a, b) => b[0].length - a[0].length)\n : [];\n\n const resolvedAliasValues = aliases.map(([key, value]) => [\n key,\n isAbsolute(value) ? value : resolve(basedir, value),\n ] as const);\n\n const cache = new Map<string, ResolvedImport | undefined>();\n\n return function resolveSpecifier(\n specifier: string,\n fromFile: string,\n ): ResolvedImport | undefined {\n const fromDir = dirname(fromFile);\n const cacheKey = `${specifier}\\0${fromDir}`;\n\n if (cache.has(cacheKey)) return cache.get(cacheKey);\n\n let result: ResolvedImport | undefined;\n\n // Relative import\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const absolutePath = resolveFile(resolve(fromDir, specifier), extensions);\n if (absolutePath) {\n result = { type: 'local', absolutePath };\n }\n }\n // Check aliases\n else {\n let matched = false;\n for (const [prefix, replacement] of resolvedAliasValues) {\n if (specifier === prefix || specifier.startsWith(prefix + '/')) {\n const rest = specifier === prefix ? '' : specifier.slice(prefix.length);\n const absolutePath = resolveFile(join(replacement, rest), extensions);\n if (absolutePath) {\n result = { type: 'local', absolutePath };\n }\n matched = true;\n break;\n }\n }\n\n // Bare specifier → external\n if (!matched) {\n result = { type: 'external', specifier: getBareSpecifier(specifier) };\n }\n }\n\n cache.set(cacheKey, result);\n return result;\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport type { ImportreeOptions, ImportTree } from './types.js';\nimport { scanImports } from './scanner.js';\nimport { createResolver } from './resolver.js';\n\n/**\n * Recursively walks imports starting from an entry file and builds\n * the full dependency tree.\n */\nexport async function walk(\n entryFile: string,\n options: ImportreeOptions,\n): Promise<ImportTree> {\n const entrypoint = resolve(entryFile);\n const basedir = options.rootDir ? resolve(options.rootDir) : process.cwd();\n const resolveSpecifier = createResolver(basedir, options);\n\n const graph: Record<string, string[]> = {};\n const externals = new Set<string>();\n const visited = new Set<string>();\n\n async function visit(filePath: string): Promise<void> {\n if (visited.has(filePath)) return;\n visited.add(filePath);\n\n const content = await readFile(filePath, 'utf-8');\n const specifiers = scanImports(content);\n\n const localDeps: string[] = [];\n for (const spec of specifiers) {\n const resolved = resolveSpecifier(spec, filePath);\n if (!resolved) continue;\n\n if (resolved.type === 'external' && resolved.specifier) {\n externals.add(resolved.specifier);\n } else if (resolved.type === 'local' && resolved.absolutePath) {\n localDeps.push(resolved.absolutePath);\n }\n }\n\n graph[filePath] = localDeps;\n\n await Promise.all(localDeps.map((dep) => visit(dep)));\n }\n\n await visit(entrypoint);\n\n // Build reverse graph\n const reverseGraph: Record<string, string[]> = {};\n for (const file of Object.keys(graph)) {\n reverseGraph[file] = [];\n }\n for (const [file, deps] of Object.entries(graph)) {\n for (const dep of deps) {\n if (!reverseGraph[dep]) reverseGraph[dep] = [];\n reverseGraph[dep].push(file);\n }\n }\n\n return {\n entrypoint,\n files: Object.keys(graph).sort(),\n externals: [...externals].sort(),\n graph,\n reverseGraph,\n };\n}\n","import { resolve } from 'node:path';\nimport type { ImportreeOptions, ImportTree } from './types.js';\nimport { walk } from './walker.js';\n\nexport type { ImportreeOptions, ImportTree } from './types.js';\n\n/**\n * Builds a full import dependency tree starting from an entry file.\n *\n * Recursively resolves all static imports, dynamic imports, require() calls,\n * and re-exports. Supports path aliases for custom resolution.\n *\n * @example\n * ```ts\n * const tree = await importree('./src/index.ts', {\n * aliases: { '@': './src' },\n * });\n *\n * console.log(tree.files); // all local dependency file paths\n * console.log(tree.externals); // external package names\n * console.log(tree.graph); // file → direct dependencies\n * ```\n */\nexport async function importree(\n entry: string,\n options?: ImportreeOptions,\n): Promise<ImportTree> {\n return walk(entry, options ?? {});\n}\n\n/**\n * Given an import tree and a changed file, returns all files that\n * transitively depend on the changed file (i.e., files that would\n * need to be re-evaluated if the changed file is modified).\n *\n * The changed file itself is NOT included in the result.\n */\nexport function getAffectedFiles(\n tree: ImportTree,\n changedFile: string,\n): string[] {\n const absolute = resolve(changedFile);\n\n if (!tree.reverseGraph[absolute]) return [];\n\n const affected = new Set<string>();\n const queue = [absolute];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n const dependents = tree.reverseGraph[current];\n if (!dependents) continue;\n\n for (const parent of dependents) {\n if (!affected.has(parent)) {\n affected.add(parent);\n queue.push(parent);\n }\n }\n }\n\n return [...affected].sort();\n}\n"],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Configuration options for importree.
4
+ */
5
+ interface ImportreeOptions {
6
+ /**
7
+ * Root directory for resolving relative alias paths.
8
+ * Defaults to the current working directory.
9
+ */
10
+ rootDir?: string;
11
+ /**
12
+ * Path alias mappings. Keys are alias prefixes, values are the
13
+ * replacement paths (resolved relative to `rootDir` or absolute).
14
+ *
15
+ * @example { '@': './src', '~': './lib' }
16
+ */
17
+ aliases?: Record<string, string>;
18
+ /**
19
+ * File extensions to try when resolving imports without extensions.
20
+ *
21
+ * @default ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']
22
+ */
23
+ extensions?: string[];
24
+ }
25
+ /**
26
+ * The result of building an import dependency tree.
27
+ */
28
+ interface ImportTree {
29
+ /** Absolute path of the entry file. */
30
+ entrypoint: string;
31
+ /** Sorted array of absolute paths of all local files in the dependency tree. */
32
+ files: string[];
33
+ /** Sorted array of unique bare/external import specifiers (packages). */
34
+ externals: string[];
35
+ /**
36
+ * Forward adjacency list: each key is an absolute file path, and its value
37
+ * is an array of absolute paths of files it directly imports.
38
+ */
39
+ graph: Record<string, string[]>;
40
+ /**
41
+ * Reverse adjacency list: each key is an absolute file path, and its value
42
+ * is an array of absolute paths of files that import it.
43
+ */
44
+ reverseGraph: Record<string, string[]>;
45
+ }
46
+ //#endregion
47
+ //#region src/index.d.ts
48
+ /**
49
+ * Builds a full import dependency tree starting from an entry file.
50
+ *
51
+ * Recursively resolves all static imports, dynamic imports, require() calls,
52
+ * and re-exports. Supports path aliases for custom resolution.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const tree = await importree('./src/index.ts', {
57
+ * aliases: { '@': './src' },
58
+ * });
59
+ *
60
+ * console.log(tree.files); // all local dependency file paths
61
+ * console.log(tree.externals); // external package names
62
+ * console.log(tree.graph); // file → direct dependencies
63
+ * ```
64
+ */
65
+ declare function importree(entry: string, options?: ImportreeOptions): Promise<ImportTree>;
66
+ /**
67
+ * Given an import tree and a changed file, returns all files that
68
+ * transitively depend on the changed file (i.e., files that would
69
+ * need to be re-evaluated if the changed file is modified).
70
+ *
71
+ * The changed file itself is NOT included in the result.
72
+ */
73
+ declare function getAffectedFiles(tree: ImportTree, changedFile: string): string[];
74
+ //#endregion
75
+ export { type ImportTree, type ImportreeOptions, getAffectedFiles, importree };
76
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;AAGA;;UAAiB,gBAAA;EAaL;;;;EARV,OAAA;;;AAqBF;;;;EAbE,OAAA,GAAU,MAAA;;;;;;EAOV,UAAA;AAAA;;;;UAMe,UAAA;ECNjB;EDQE,UAAA;;EAGA,KAAA;;EAGA,SAAA;;;;;EAMA,KAAA,EAAO,MAAA;;;;;EAMP,YAAA,EAAc,MAAA;AAAA;;;;;;;;;;;;AApBhB;;;;;;;;iBCNsB,SAAA,CACpB,KAAA,UACA,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,UAAA;;;;;;;;iBAWK,gBAAA,CACd,IAAA,EAAM,UAAA,EACN,WAAA"}
@@ -0,0 +1,76 @@
1
+ //#region src/types.d.ts
2
+ /**
3
+ * Configuration options for importree.
4
+ */
5
+ interface ImportreeOptions {
6
+ /**
7
+ * Root directory for resolving relative alias paths.
8
+ * Defaults to the current working directory.
9
+ */
10
+ rootDir?: string;
11
+ /**
12
+ * Path alias mappings. Keys are alias prefixes, values are the
13
+ * replacement paths (resolved relative to `rootDir` or absolute).
14
+ *
15
+ * @example { '@': './src', '~': './lib' }
16
+ */
17
+ aliases?: Record<string, string>;
18
+ /**
19
+ * File extensions to try when resolving imports without extensions.
20
+ *
21
+ * @default ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']
22
+ */
23
+ extensions?: string[];
24
+ }
25
+ /**
26
+ * The result of building an import dependency tree.
27
+ */
28
+ interface ImportTree {
29
+ /** Absolute path of the entry file. */
30
+ entrypoint: string;
31
+ /** Sorted array of absolute paths of all local files in the dependency tree. */
32
+ files: string[];
33
+ /** Sorted array of unique bare/external import specifiers (packages). */
34
+ externals: string[];
35
+ /**
36
+ * Forward adjacency list: each key is an absolute file path, and its value
37
+ * is an array of absolute paths of files it directly imports.
38
+ */
39
+ graph: Record<string, string[]>;
40
+ /**
41
+ * Reverse adjacency list: each key is an absolute file path, and its value
42
+ * is an array of absolute paths of files that import it.
43
+ */
44
+ reverseGraph: Record<string, string[]>;
45
+ }
46
+ //#endregion
47
+ //#region src/index.d.ts
48
+ /**
49
+ * Builds a full import dependency tree starting from an entry file.
50
+ *
51
+ * Recursively resolves all static imports, dynamic imports, require() calls,
52
+ * and re-exports. Supports path aliases for custom resolution.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const tree = await importree('./src/index.ts', {
57
+ * aliases: { '@': './src' },
58
+ * });
59
+ *
60
+ * console.log(tree.files); // all local dependency file paths
61
+ * console.log(tree.externals); // external package names
62
+ * console.log(tree.graph); // file → direct dependencies
63
+ * ```
64
+ */
65
+ declare function importree(entry: string, options?: ImportreeOptions): Promise<ImportTree>;
66
+ /**
67
+ * Given an import tree and a changed file, returns all files that
68
+ * transitively depend on the changed file (i.e., files that would
69
+ * need to be re-evaluated if the changed file is modified).
70
+ *
71
+ * The changed file itself is NOT included in the result.
72
+ */
73
+ declare function getAffectedFiles(tree: ImportTree, changedFile: string): string[];
74
+ //#endregion
75
+ export { type ImportTree, type ImportreeOptions, getAffectedFiles, importree };
76
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;AAGA;;UAAiB,gBAAA;EAaL;;;;EARV,OAAA;;;AAqBF;;;;EAbE,OAAA,GAAU,MAAA;;;;;;EAOV,UAAA;AAAA;;;;UAMe,UAAA;ECNjB;EDQE,UAAA;;EAGA,KAAA;;EAGA,SAAA;;;;;EAMA,KAAA,EAAO,MAAA;;;;;EAMP,YAAA,EAAc,MAAA;AAAA;;;;;;;;;;;;AApBhB;;;;;;;;iBCNsB,SAAA,CACpB,KAAA,UACA,OAAA,GAAU,gBAAA,GACT,OAAA,CAAQ,UAAA;;;;;;;;iBAWK,gBAAA,CACd,IAAA,EAAM,UAAA,EACN,WAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,282 @@
1
+ import { dirname, isAbsolute, join, resolve } from "node:path";
2
+ import { readFile } from "node:fs/promises";
3
+ import { statSync } from "node:fs";
4
+ //#region src/scanner.ts
5
+ /**
6
+ * Strips comments from source code while preserving string literals.
7
+ *
8
+ * Comments are replaced with spaces (preserving newlines). Strings and
9
+ * template literals are left intact so that import specifiers inside
10
+ * `from 'specifier'` remain extractable. The function correctly handles
11
+ * comment-like sequences inside strings (e.g., `'//'` won't start a comment).
12
+ */
13
+ function stripComments(code) {
14
+ const len = code.length;
15
+ const result = new Array(len);
16
+ let i = 0;
17
+ while (i < len) {
18
+ const ch = code[i];
19
+ const next = i + 1 < len ? code[i + 1] : "";
20
+ if (ch === "/" && next === "/") {
21
+ result[i++] = " ";
22
+ result[i++] = " ";
23
+ while (i < len && code[i] !== "\n") result[i++] = " ";
24
+ continue;
25
+ }
26
+ if (ch === "/" && next === "*") {
27
+ result[i++] = " ";
28
+ result[i++] = " ";
29
+ while (i < len && !(code[i] === "*" && i + 1 < len && code[i + 1] === "/")) {
30
+ result[i] = code[i] === "\n" ? "\n" : " ";
31
+ i++;
32
+ }
33
+ if (i < len) {
34
+ result[i++] = " ";
35
+ result[i++] = " ";
36
+ }
37
+ continue;
38
+ }
39
+ if (ch === "'" || ch === "\"") {
40
+ const quote = ch;
41
+ result[i] = code[i];
42
+ i++;
43
+ while (i < len && code[i] !== quote) if (code[i] === "\\" && i + 1 < len) {
44
+ result[i] = code[i];
45
+ i++;
46
+ result[i] = code[i];
47
+ i++;
48
+ } else {
49
+ result[i] = code[i];
50
+ i++;
51
+ }
52
+ if (i < len) {
53
+ result[i] = code[i];
54
+ i++;
55
+ }
56
+ continue;
57
+ }
58
+ if (ch === "`") {
59
+ result[i] = code[i];
60
+ i++;
61
+ let depth = 0;
62
+ while (i < len) if (code[i] === "\\" && i + 1 < len) {
63
+ result[i] = code[i];
64
+ i++;
65
+ result[i] = code[i];
66
+ i++;
67
+ } else if (code[i] === "$" && i + 1 < len && code[i + 1] === "{") {
68
+ result[i] = code[i];
69
+ i++;
70
+ result[i] = code[i];
71
+ i++;
72
+ depth++;
73
+ } else if (code[i] === "}" && depth > 0) {
74
+ result[i] = code[i];
75
+ i++;
76
+ depth--;
77
+ } else if (code[i] === "`" && depth === 0) {
78
+ result[i] = code[i];
79
+ i++;
80
+ break;
81
+ } else {
82
+ result[i] = code[i];
83
+ i++;
84
+ }
85
+ continue;
86
+ }
87
+ result[i] = ch;
88
+ i++;
89
+ }
90
+ return result.join("");
91
+ }
92
+ const fromRe = /\bfrom\s+['"]([^'"]+)['"]/g;
93
+ const sideEffectRe = /\bimport\s+['"]([^'"]+)['"]/g;
94
+ const dynamicRe = /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
95
+ const requireRe = /\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
96
+ /**
97
+ * Scans source code and extracts all import/require specifiers.
98
+ *
99
+ * Handles: static imports, dynamic imports, require(), re-exports.
100
+ * Ignores imports inside comments. Imports inside string literals may
101
+ * produce false positives, but unresolvable paths are silently skipped
102
+ * by the resolver.
103
+ */
104
+ function scanImports(code) {
105
+ const stripped = stripComments(code);
106
+ const specifiers = /* @__PURE__ */ new Set();
107
+ for (const m of stripped.matchAll(fromRe)) specifiers.add(m[1]);
108
+ for (const m of stripped.matchAll(sideEffectRe)) specifiers.add(m[1]);
109
+ for (const m of stripped.matchAll(dynamicRe)) specifiers.add(m[1]);
110
+ for (const m of stripped.matchAll(requireRe)) specifiers.add(m[1]);
111
+ return [...specifiers];
112
+ }
113
+ //#endregion
114
+ //#region src/resolver.ts
115
+ const DEFAULT_EXTENSIONS = [
116
+ ".ts",
117
+ ".tsx",
118
+ ".js",
119
+ ".jsx",
120
+ ".mjs",
121
+ ".cjs"
122
+ ];
123
+ function fileExists(filePath) {
124
+ const stat = statSync(filePath, { throwIfNoEntry: false });
125
+ return stat !== void 0 && stat.isFile();
126
+ }
127
+ function dirExists(filePath) {
128
+ const stat = statSync(filePath, { throwIfNoEntry: false });
129
+ return stat !== void 0 && stat.isDirectory();
130
+ }
131
+ /**
132
+ * Extract the bare package name from an import specifier.
133
+ * - Scoped: `@scope/pkg/path` → `@scope/pkg`
134
+ * - Unscoped: `pkg/path` → `pkg`
135
+ */
136
+ function getBareSpecifier(specifier) {
137
+ if (specifier.startsWith("@")) {
138
+ const parts = specifier.split("/");
139
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
140
+ }
141
+ return specifier.split("/")[0];
142
+ }
143
+ function resolveFile(filePath, extensions) {
144
+ if (fileExists(filePath)) return filePath;
145
+ for (const ext of extensions) {
146
+ const withExt = filePath + ext;
147
+ if (fileExists(withExt)) return withExt;
148
+ }
149
+ if (dirExists(filePath)) for (const ext of extensions) {
150
+ const indexPath = join(filePath, `index${ext}`);
151
+ if (fileExists(indexPath)) return indexPath;
152
+ }
153
+ }
154
+ /**
155
+ * Creates a resolver function that resolves import specifiers to absolute
156
+ * file paths, with support for aliases and extension probing.
157
+ */
158
+ function createResolver(basedir, options) {
159
+ const extensions = options.extensions ?? DEFAULT_EXTENSIONS;
160
+ const resolvedAliasValues = (options.aliases ? Object.entries(options.aliases).sort((a, b) => b[0].length - a[0].length) : []).map(([key, value]) => [key, isAbsolute(value) ? value : resolve(basedir, value)]);
161
+ const cache = /* @__PURE__ */ new Map();
162
+ return function resolveSpecifier(specifier, fromFile) {
163
+ const fromDir = dirname(fromFile);
164
+ const cacheKey = `${specifier}\0${fromDir}`;
165
+ if (cache.has(cacheKey)) return cache.get(cacheKey);
166
+ let result;
167
+ if (specifier.startsWith("./") || specifier.startsWith("../")) {
168
+ const absolutePath = resolveFile(resolve(fromDir, specifier), extensions);
169
+ if (absolutePath) result = {
170
+ type: "local",
171
+ absolutePath
172
+ };
173
+ } else {
174
+ let matched = false;
175
+ for (const [prefix, replacement] of resolvedAliasValues) if (specifier === prefix || specifier.startsWith(prefix + "/")) {
176
+ const absolutePath = resolveFile(join(replacement, specifier === prefix ? "" : specifier.slice(prefix.length)), extensions);
177
+ if (absolutePath) result = {
178
+ type: "local",
179
+ absolutePath
180
+ };
181
+ matched = true;
182
+ break;
183
+ }
184
+ if (!matched) result = {
185
+ type: "external",
186
+ specifier: getBareSpecifier(specifier)
187
+ };
188
+ }
189
+ cache.set(cacheKey, result);
190
+ return result;
191
+ };
192
+ }
193
+ //#endregion
194
+ //#region src/walker.ts
195
+ /**
196
+ * Recursively walks imports starting from an entry file and builds
197
+ * the full dependency tree.
198
+ */
199
+ async function walk(entryFile, options) {
200
+ const entrypoint = resolve(entryFile);
201
+ const resolveSpecifier = createResolver(options.rootDir ? resolve(options.rootDir) : process.cwd(), options);
202
+ const graph = {};
203
+ const externals = /* @__PURE__ */ new Set();
204
+ const visited = /* @__PURE__ */ new Set();
205
+ async function visit(filePath) {
206
+ if (visited.has(filePath)) return;
207
+ visited.add(filePath);
208
+ const specifiers = scanImports(await readFile(filePath, "utf-8"));
209
+ const localDeps = [];
210
+ for (const spec of specifiers) {
211
+ const resolved = resolveSpecifier(spec, filePath);
212
+ if (!resolved) continue;
213
+ if (resolved.type === "external" && resolved.specifier) externals.add(resolved.specifier);
214
+ else if (resolved.type === "local" && resolved.absolutePath) localDeps.push(resolved.absolutePath);
215
+ }
216
+ graph[filePath] = localDeps;
217
+ await Promise.all(localDeps.map((dep) => visit(dep)));
218
+ }
219
+ await visit(entrypoint);
220
+ const reverseGraph = {};
221
+ for (const file of Object.keys(graph)) reverseGraph[file] = [];
222
+ for (const [file, deps] of Object.entries(graph)) for (const dep of deps) {
223
+ if (!reverseGraph[dep]) reverseGraph[dep] = [];
224
+ reverseGraph[dep].push(file);
225
+ }
226
+ return {
227
+ entrypoint,
228
+ files: Object.keys(graph).sort(),
229
+ externals: [...externals].sort(),
230
+ graph,
231
+ reverseGraph
232
+ };
233
+ }
234
+ //#endregion
235
+ //#region src/index.ts
236
+ /**
237
+ * Builds a full import dependency tree starting from an entry file.
238
+ *
239
+ * Recursively resolves all static imports, dynamic imports, require() calls,
240
+ * and re-exports. Supports path aliases for custom resolution.
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * const tree = await importree('./src/index.ts', {
245
+ * aliases: { '@': './src' },
246
+ * });
247
+ *
248
+ * console.log(tree.files); // all local dependency file paths
249
+ * console.log(tree.externals); // external package names
250
+ * console.log(tree.graph); // file → direct dependencies
251
+ * ```
252
+ */
253
+ async function importree(entry, options) {
254
+ return walk(entry, options ?? {});
255
+ }
256
+ /**
257
+ * Given an import tree and a changed file, returns all files that
258
+ * transitively depend on the changed file (i.e., files that would
259
+ * need to be re-evaluated if the changed file is modified).
260
+ *
261
+ * The changed file itself is NOT included in the result.
262
+ */
263
+ function getAffectedFiles(tree, changedFile) {
264
+ const absolute = resolve(changedFile);
265
+ if (!tree.reverseGraph[absolute]) return [];
266
+ const affected = /* @__PURE__ */ new Set();
267
+ const queue = [absolute];
268
+ while (queue.length > 0) {
269
+ const current = queue.shift();
270
+ const dependents = tree.reverseGraph[current];
271
+ if (!dependents) continue;
272
+ for (const parent of dependents) if (!affected.has(parent)) {
273
+ affected.add(parent);
274
+ queue.push(parent);
275
+ }
276
+ }
277
+ return [...affected].sort();
278
+ }
279
+ //#endregion
280
+ export { getAffectedFiles, importree };
281
+
282
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/scanner.ts","../src/resolver.ts","../src/walker.ts","../src/index.ts"],"sourcesContent":["/**\n * Strips comments from source code while preserving string literals.\n *\n * Comments are replaced with spaces (preserving newlines). Strings and\n * template literals are left intact so that import specifiers inside\n * `from 'specifier'` remain extractable. The function correctly handles\n * comment-like sequences inside strings (e.g., `'//'` won't start a comment).\n */\nexport function stripComments(code: string): string {\n const len = code.length;\n const result: string[] = new Array(len);\n let i = 0;\n\n while (i < len) {\n const ch = code[i];\n const next = i + 1 < len ? code[i + 1] : '';\n\n // Line comment → blank to end of line\n if (ch === '/' && next === '/') {\n result[i++] = ' ';\n result[i++] = ' ';\n while (i < len && code[i] !== '\\n') {\n result[i++] = ' ';\n }\n continue;\n }\n\n // Block comment → blank to closing */\n if (ch === '/' && next === '*') {\n result[i++] = ' ';\n result[i++] = ' ';\n while (i < len && !(code[i] === '*' && i + 1 < len && code[i + 1] === '/')) {\n result[i] = code[i] === '\\n' ? '\\n' : ' ';\n i++;\n }\n if (i < len) {\n result[i++] = ' '; // *\n result[i++] = ' '; // /\n }\n continue;\n }\n\n // Single or double quoted string — copy verbatim (skip past to avoid\n // misidentifying comment markers inside strings)\n if (ch === \"'\" || ch === '\"') {\n const quote = ch;\n result[i] = code[i];\n i++;\n while (i < len && code[i] !== quote) {\n if (code[i] === '\\\\' && i + 1 < len) {\n result[i] = code[i];\n i++;\n result[i] = code[i];\n i++;\n } else {\n result[i] = code[i];\n i++;\n }\n }\n if (i < len) {\n result[i] = code[i];\n i++;\n }\n continue;\n }\n\n // Template literal — copy verbatim, handling ${} nesting\n if (ch === '`') {\n result[i] = code[i];\n i++;\n let depth = 0;\n while (i < len) {\n if (code[i] === '\\\\' && i + 1 < len) {\n result[i] = code[i];\n i++;\n result[i] = code[i];\n i++;\n } else if (code[i] === '$' && i + 1 < len && code[i + 1] === '{') {\n result[i] = code[i];\n i++;\n result[i] = code[i];\n i++;\n depth++;\n } else if (code[i] === '}' && depth > 0) {\n result[i] = code[i];\n i++;\n depth--;\n } else if (code[i] === '`' && depth === 0) {\n result[i] = code[i];\n i++;\n break;\n } else {\n result[i] = code[i];\n i++;\n }\n }\n continue;\n }\n\n // Regular character\n result[i] = ch;\n i++;\n }\n\n return result.join('');\n}\n\n// Static regex patterns — compiled once\nconst fromRe = /\\bfrom\\s+['\"]([^'\"]+)['\"]/g;\nconst sideEffectRe = /\\bimport\\s+['\"]([^'\"]+)['\"]/g;\nconst dynamicRe = /\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\nconst requireRe = /\\brequire\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\n/**\n * Scans source code and extracts all import/require specifiers.\n *\n * Handles: static imports, dynamic imports, require(), re-exports.\n * Ignores imports inside comments. Imports inside string literals may\n * produce false positives, but unresolvable paths are silently skipped\n * by the resolver.\n */\nexport function scanImports(code: string): string[] {\n const stripped = stripComments(code);\n const specifiers = new Set<string>();\n\n for (const m of stripped.matchAll(fromRe)) specifiers.add(m[1]);\n for (const m of stripped.matchAll(sideEffectRe)) specifiers.add(m[1]);\n for (const m of stripped.matchAll(dynamicRe)) specifiers.add(m[1]);\n for (const m of stripped.matchAll(requireRe)) specifiers.add(m[1]);\n\n return [...specifiers];\n}\n","import { statSync } from 'node:fs';\nimport { dirname, join, resolve, isAbsolute } from 'node:path';\nimport type { ImportreeOptions, ResolvedImport } from './types.js';\n\nconst DEFAULT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];\n\nfunction fileExists(filePath: string): boolean {\n const stat = statSync(filePath, { throwIfNoEntry: false });\n return stat !== undefined && stat.isFile();\n}\n\nfunction dirExists(filePath: string): boolean {\n const stat = statSync(filePath, { throwIfNoEntry: false });\n return stat !== undefined && stat.isDirectory();\n}\n\n/**\n * Extract the bare package name from an import specifier.\n * - Scoped: `@scope/pkg/path` → `@scope/pkg`\n * - Unscoped: `pkg/path` → `pkg`\n */\nfunction getBareSpecifier(specifier: string): string {\n if (specifier.startsWith('@')) {\n const parts = specifier.split('/');\n return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;\n }\n return specifier.split('/')[0];\n}\n\nfunction resolveFile(\n filePath: string,\n extensions: string[],\n): string | undefined {\n // Try exact path\n if (fileExists(filePath)) return filePath;\n\n // Try with each extension\n for (const ext of extensions) {\n const withExt = filePath + ext;\n if (fileExists(withExt)) return withExt;\n }\n\n // Try as directory with index file\n if (dirExists(filePath)) {\n for (const ext of extensions) {\n const indexPath = join(filePath, `index${ext}`);\n if (fileExists(indexPath)) return indexPath;\n }\n }\n\n return undefined;\n}\n\nexport interface Resolver {\n (specifier: string, fromFile: string): ResolvedImport | undefined;\n}\n\n/**\n * Creates a resolver function that resolves import specifiers to absolute\n * file paths, with support for aliases and extension probing.\n */\nexport function createResolver(\n basedir: string,\n options: ImportreeOptions,\n): Resolver {\n const extensions = options.extensions ?? DEFAULT_EXTENSIONS;\n\n // Sort aliases by key length descending for longest-prefix matching\n const aliases = options.aliases\n ? Object.entries(options.aliases).sort((a, b) => b[0].length - a[0].length)\n : [];\n\n const resolvedAliasValues = aliases.map(([key, value]) => [\n key,\n isAbsolute(value) ? value : resolve(basedir, value),\n ] as const);\n\n const cache = new Map<string, ResolvedImport | undefined>();\n\n return function resolveSpecifier(\n specifier: string,\n fromFile: string,\n ): ResolvedImport | undefined {\n const fromDir = dirname(fromFile);\n const cacheKey = `${specifier}\\0${fromDir}`;\n\n if (cache.has(cacheKey)) return cache.get(cacheKey);\n\n let result: ResolvedImport | undefined;\n\n // Relative import\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n const absolutePath = resolveFile(resolve(fromDir, specifier), extensions);\n if (absolutePath) {\n result = { type: 'local', absolutePath };\n }\n }\n // Check aliases\n else {\n let matched = false;\n for (const [prefix, replacement] of resolvedAliasValues) {\n if (specifier === prefix || specifier.startsWith(prefix + '/')) {\n const rest = specifier === prefix ? '' : specifier.slice(prefix.length);\n const absolutePath = resolveFile(join(replacement, rest), extensions);\n if (absolutePath) {\n result = { type: 'local', absolutePath };\n }\n matched = true;\n break;\n }\n }\n\n // Bare specifier → external\n if (!matched) {\n result = { type: 'external', specifier: getBareSpecifier(specifier) };\n }\n }\n\n cache.set(cacheKey, result);\n return result;\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport type { ImportreeOptions, ImportTree } from './types.js';\nimport { scanImports } from './scanner.js';\nimport { createResolver } from './resolver.js';\n\n/**\n * Recursively walks imports starting from an entry file and builds\n * the full dependency tree.\n */\nexport async function walk(\n entryFile: string,\n options: ImportreeOptions,\n): Promise<ImportTree> {\n const entrypoint = resolve(entryFile);\n const basedir = options.rootDir ? resolve(options.rootDir) : process.cwd();\n const resolveSpecifier = createResolver(basedir, options);\n\n const graph: Record<string, string[]> = {};\n const externals = new Set<string>();\n const visited = new Set<string>();\n\n async function visit(filePath: string): Promise<void> {\n if (visited.has(filePath)) return;\n visited.add(filePath);\n\n const content = await readFile(filePath, 'utf-8');\n const specifiers = scanImports(content);\n\n const localDeps: string[] = [];\n for (const spec of specifiers) {\n const resolved = resolveSpecifier(spec, filePath);\n if (!resolved) continue;\n\n if (resolved.type === 'external' && resolved.specifier) {\n externals.add(resolved.specifier);\n } else if (resolved.type === 'local' && resolved.absolutePath) {\n localDeps.push(resolved.absolutePath);\n }\n }\n\n graph[filePath] = localDeps;\n\n await Promise.all(localDeps.map((dep) => visit(dep)));\n }\n\n await visit(entrypoint);\n\n // Build reverse graph\n const reverseGraph: Record<string, string[]> = {};\n for (const file of Object.keys(graph)) {\n reverseGraph[file] = [];\n }\n for (const [file, deps] of Object.entries(graph)) {\n for (const dep of deps) {\n if (!reverseGraph[dep]) reverseGraph[dep] = [];\n reverseGraph[dep].push(file);\n }\n }\n\n return {\n entrypoint,\n files: Object.keys(graph).sort(),\n externals: [...externals].sort(),\n graph,\n reverseGraph,\n };\n}\n","import { resolve } from 'node:path';\nimport type { ImportreeOptions, ImportTree } from './types.js';\nimport { walk } from './walker.js';\n\nexport type { ImportreeOptions, ImportTree } from './types.js';\n\n/**\n * Builds a full import dependency tree starting from an entry file.\n *\n * Recursively resolves all static imports, dynamic imports, require() calls,\n * and re-exports. Supports path aliases for custom resolution.\n *\n * @example\n * ```ts\n * const tree = await importree('./src/index.ts', {\n * aliases: { '@': './src' },\n * });\n *\n * console.log(tree.files); // all local dependency file paths\n * console.log(tree.externals); // external package names\n * console.log(tree.graph); // file → direct dependencies\n * ```\n */\nexport async function importree(\n entry: string,\n options?: ImportreeOptions,\n): Promise<ImportTree> {\n return walk(entry, options ?? {});\n}\n\n/**\n * Given an import tree and a changed file, returns all files that\n * transitively depend on the changed file (i.e., files that would\n * need to be re-evaluated if the changed file is modified).\n *\n * The changed file itself is NOT included in the result.\n */\nexport function getAffectedFiles(\n tree: ImportTree,\n changedFile: string,\n): string[] {\n const absolute = resolve(changedFile);\n\n if (!tree.reverseGraph[absolute]) return [];\n\n const affected = new Set<string>();\n const queue = [absolute];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n const dependents = tree.reverseGraph[current];\n if (!dependents) continue;\n\n for (const parent of dependents) {\n if (!affected.has(parent)) {\n affected.add(parent);\n queue.push(parent);\n }\n }\n }\n\n return [...affected].sort();\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "importree",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Build import dependency trees for TypeScript and JavaScript files. Fast, zero-dependency static analysis for dependency detection and cache invalidation.",
6
+ "author": "Alex Grozav <alex@grozav.com>",
7
+ "license": "ISC",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/alexgrozav/importree.git"
11
+ },
12
+ "homepage": "https://github.com/alexgrozav/importree#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/alexgrozav/importree/issues"
15
+ },
16
+ "keywords": [
17
+ "imports",
18
+ "dependencies",
19
+ "dependency-graph",
20
+ "import-tree",
21
+ "static-analysis",
22
+ "cache-invalidation",
23
+ "typescript",
24
+ "javascript"
25
+ ],
26
+ "engines": {
27
+ "node": ">=18"
28
+ },
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.mjs",
31
+ "types": "./dist/index.d.mts",
32
+ "exports": {
33
+ ".": {
34
+ "import": {
35
+ "types": "./dist/index.d.mts",
36
+ "default": "./dist/index.mjs"
37
+ },
38
+ "require": {
39
+ "types": "./dist/index.d.cts",
40
+ "default": "./dist/index.cjs"
41
+ }
42
+ }
43
+ },
44
+ "files": [
45
+ "dist"
46
+ ],
47
+ "devDependencies": {
48
+ "@vitest/coverage-v8": "^4.0.18",
49
+ "tsdown": "^0.21.0",
50
+ "typescript": "^5.7.0",
51
+ "vitest": "^4.0.18"
52
+ },
53
+ "scripts": {
54
+ "build": "tsdown",
55
+ "build:docs": "cp -r docs dist-docs",
56
+ "build:watch": "tsdown --watch",
57
+ "dev": "tsdown --watch",
58
+ "dev:docs": "npx http-server docs -p 8765 -o",
59
+ "test": "vitest run",
60
+ "test:watch": "vitest"
61
+ }
62
+ }