importree 2.0.0 → 2.0.1

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/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- let node_path = require("node:path");
3
2
  let node_fs = require("node:fs");
3
+ let node_path = require("node:path");
4
4
  //#region src/scanner.ts
5
5
  /**
6
6
  * Strips comments from source code while preserving string literals.
@@ -209,6 +209,40 @@ function createResolver(basedir, options) {
209
209
  }
210
210
  //#endregion
211
211
  //#region src/walker.ts
212
+ function buildEdges(rawImports, resolveSpecifier, filePath) {
213
+ const edges = [];
214
+ const externals = [];
215
+ for (const raw of rawImports) {
216
+ const resolved = resolveSpecifier(raw.path, filePath);
217
+ if (!resolved) continue;
218
+ if (resolved.type === "external" && resolved.specifier) externals.push(resolved.specifier);
219
+ else if (resolved.type === "local" && resolved.absolutePath) {
220
+ const existing = edges.find((e) => e.path === resolved.absolutePath);
221
+ if (existing) {
222
+ if (raw.isNamespace) {
223
+ existing.isNamespace = true;
224
+ existing.specifiers = void 0;
225
+ existing.isSideEffect = void 0;
226
+ } else if (raw.specifiers && !existing.isNamespace) {
227
+ (existing.specifiers ??= []).push(...raw.specifiers);
228
+ existing.isSideEffect = void 0;
229
+ }
230
+ if (raw.isSideEffect && !existing.specifiers && !existing.isNamespace) existing.isSideEffect = true;
231
+ if (raw.isDynamic) existing.isDynamic = true;
232
+ } else edges.push({
233
+ path: resolved.absolutePath,
234
+ specifiers: raw.specifiers,
235
+ isNamespace: raw.isNamespace,
236
+ isDynamic: raw.isDynamic,
237
+ isSideEffect: raw.isSideEffect
238
+ });
239
+ }
240
+ }
241
+ return {
242
+ edges,
243
+ externals
244
+ };
245
+ }
212
246
  /**
213
247
  * Walks imports starting from an entry file and builds the full dependency tree.
214
248
  * Uses iterative DFS with an explicit stack to avoid call-stack limits on deep chains.
@@ -224,34 +258,8 @@ async function walk(entryFile, options) {
224
258
  const filePath = stack.pop();
225
259
  if (visited.has(filePath)) continue;
226
260
  visited.add(filePath);
227
- const rawImports = scanImports((0, node_fs.readFileSync)(filePath, "utf-8"));
228
- const edges = [];
229
- for (const raw of rawImports) {
230
- const resolved = resolveSpecifier(raw.path, filePath);
231
- if (!resolved) continue;
232
- if (resolved.type === "external" && resolved.specifier) externals.add(resolved.specifier);
233
- else if (resolved.type === "local" && resolved.absolutePath) {
234
- const existing = edges.find((e) => e.path === resolved.absolutePath);
235
- if (existing) {
236
- if (raw.isNamespace) {
237
- existing.isNamespace = true;
238
- existing.specifiers = void 0;
239
- existing.isSideEffect = void 0;
240
- } else if (raw.specifiers && !existing.isNamespace) {
241
- (existing.specifiers ??= []).push(...raw.specifiers);
242
- existing.isSideEffect = void 0;
243
- }
244
- if (raw.isSideEffect && !existing.specifiers && !existing.isNamespace) existing.isSideEffect = true;
245
- if (raw.isDynamic) existing.isDynamic = true;
246
- } else edges.push({
247
- path: resolved.absolutePath,
248
- specifiers: raw.specifiers,
249
- isNamespace: raw.isNamespace,
250
- isDynamic: raw.isDynamic,
251
- isSideEffect: raw.isSideEffect
252
- });
253
- }
254
- }
261
+ const { edges, externals: fileExternals } = buildEdges(scanImports((0, node_fs.readFileSync)(filePath, "utf-8")), resolveSpecifier, filePath);
262
+ for (const ext of fileExternals) externals.add(ext);
255
263
  graph[filePath] = edges;
256
264
  for (const edge of edges) stack.push(edge.path);
257
265
  }
@@ -298,6 +306,26 @@ async function importree(entry, options) {
298
306
  return walk(entry, options ?? {});
299
307
  }
300
308
  /**
309
+ * Parses a single file and returns its direct import edges without recursive traversal.
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * const edges = parseImports('./src/components/Button.tsx', {
314
+ * aliases: { '@': './src' },
315
+ * });
316
+ *
317
+ * for (const edge of edges) {
318
+ * console.log(edge.path, edge.specifiers);
319
+ * }
320
+ * ```
321
+ */
322
+ function parseImports(filePath, options) {
323
+ const absolutePath = (0, node_path.resolve)(filePath);
324
+ const resolveSpecifier = createResolver(options?.rootDir ? (0, node_path.resolve)(options.rootDir) : process.cwd(), options ?? {});
325
+ const { edges } = buildEdges(scanImports((0, node_fs.readFileSync)(absolutePath, "utf-8")), resolveSpecifier, absolutePath);
326
+ return edges;
327
+ }
328
+ /**
301
329
  * Given an import tree and a changed file, returns all files that
302
330
  * transitively depend on the changed file (i.e., files that would
303
331
  * need to be re-evaluated if the changed file is modified).
@@ -324,5 +352,6 @@ function getAffectedFiles(tree, changedFile) {
324
352
  //#endregion
325
353
  exports.getAffectedFiles = getAffectedFiles;
326
354
  exports.importree = importree;
355
+ exports.parseImports = parseImports;
327
356
 
328
357
  //# sourceMappingURL=index.cjs.map
@@ -1 +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 * Line comments (`//`) are removed entirely. Block comments are replaced\n * with a single space (newlines within them are preserved). 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 parts: string[] = [];\n let i = 0;\n let segStart = 0;\n\n while (i < len) {\n const ch = code[i];\n const next = i + 1 < len ? code[i + 1] : \"\";\n\n if (ch === \"/\" && next === \"/\") {\n parts.push(code.slice(segStart, i));\n while (i < len && code[i] !== \"\\n\") i++;\n segStart = i;\n continue;\n }\n\n if (ch === \"/\" && next === \"*\") {\n parts.push(code.slice(segStart, i));\n i += 2;\n while (i < len && !(code[i] === \"*\" && i + 1 < len && code[i + 1] === \"/\")) {\n if (code[i] === \"\\n\") parts.push(\"\\n\");\n i++;\n }\n if (i < len) i += 2;\n parts.push(\" \");\n segStart = i;\n continue;\n }\n\n if (ch === \"'\" || ch === '\"') {\n const quote = ch;\n i++;\n while (i < len && code[i] !== quote) {\n if (code[i] === \"\\\\\" && i + 1 < len) i++;\n i++;\n }\n if (i < len) i++;\n continue;\n }\n\n if (ch === \"`\") {\n i++;\n let depth = 0;\n while (i < len) {\n if (code[i] === \"\\\\\" && i + 1 < len) {\n i += 2;\n } else if (code[i] === \"$\" && i + 1 < len && code[i + 1] === \"{\") {\n i += 2;\n depth++;\n } else if (code[i] === \"}\" && depth > 0) {\n i++;\n depth--;\n } else if (code[i] === \"`\" && depth === 0) {\n i++;\n break;\n } else {\n i++;\n }\n }\n continue;\n }\n\n i++;\n }\n\n parts.push(code.slice(segStart));\n return parts.join(\"\");\n}\n\nexport interface RawImport {\n path: string;\n specifiers?: string[];\n isNamespace?: boolean;\n isDynamic?: boolean;\n isSideEffect?: boolean;\n}\n\n// Static regex patterns — compiled once\nconst nsImportRe = /\\bimport\\s+\\*\\s+as\\s+[$\\w]+\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst namedImportRe =\n /\\bimport\\s+(?:type\\s+)?(?:([$\\w]+)\\s*,\\s*)?\\{([^}]*)\\}\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst defaultImportRe = /\\bimport\\s+(?:type\\s+)?([$\\w]+)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst reexportStarRe = /\\bexport\\s+\\*\\s+(?:as\\s+[$\\w]+\\s+)?from\\s+['\"]([^'\"]+)['\"]/g;\nconst reexportNamedRe = /\\bexport\\s+(?:type\\s+)?\\{([^}]*)\\}\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst sideEffectRe = /\\bimport\\s+['\"]([^'\"]+)['\"]/g;\nconst dynamicRe = /\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\nconst requireRe = /\\brequire\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\nexport function parseSpecifiers(clause: string): string[] {\n return clause\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n .map((s) => {\n const withoutType = s.replace(/^type\\s+/, \"\");\n const parts = withoutType.split(/\\s+as\\s+/);\n return parts[0].trim();\n })\n .filter(Boolean);\n}\n\n/**\n * Scans source code and extracts all imports with metadata.\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): RawImport[] {\n const stripped = stripComments(code);\n const results: RawImport[] = [];\n\n for (const m of stripped.matchAll(nsImportRe)) {\n results.push({ path: m[1], isNamespace: true });\n }\n\n for (const m of stripped.matchAll(namedImportRe)) {\n const specifiers = parseSpecifiers(m[2]);\n if (m[1]) specifiers.unshift(\"default\");\n results.push({ path: m[3], specifiers });\n }\n\n for (const m of stripped.matchAll(defaultImportRe)) {\n results.push({ path: m[2], specifiers: [\"default\"] });\n }\n\n for (const m of stripped.matchAll(reexportStarRe)) {\n results.push({ path: m[1], isNamespace: true });\n }\n\n for (const m of stripped.matchAll(reexportNamedRe)) {\n results.push({ path: m[2], specifiers: parseSpecifiers(m[1]) });\n }\n\n for (const m of stripped.matchAll(sideEffectRe)) {\n results.push({ path: m[1], isSideEffect: true });\n }\n\n for (const m of stripped.matchAll(dynamicRe)) {\n results.push({ path: m[1], isDynamic: true });\n }\n\n for (const m of stripped.matchAll(requireRe)) {\n results.push({ path: m[1] });\n }\n\n return results;\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(filePath: string, extensions: string[]): 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(basedir: string, options: ImportreeOptions): 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(\n ([key, value]) => [key, isAbsolute(value) ? value : resolve(basedir, value)] as const,\n );\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 { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { ImportreeOptions, ImportTree, ImportEdge } from \"./types.js\";\nimport { scanImports } from \"./scanner.js\";\nimport { createResolver } from \"./resolver.js\";\n\n/**\n * Walks imports starting from an entry file and builds the full dependency tree.\n * Uses iterative DFS with an explicit stack to avoid call-stack limits on deep chains.\n */\nexport async function walk(entryFile: string, options: ImportreeOptions): 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, ImportEdge[]> = {};\n const externals = new Set<string>();\n const visited = new Set<string>();\n const stack = [entrypoint];\n\n while (stack.length > 0) {\n const filePath = stack.pop()!;\n if (visited.has(filePath)) continue;\n visited.add(filePath);\n\n const content = readFileSync(filePath, \"utf-8\");\n const rawImports = scanImports(content);\n\n const edges: ImportEdge[] = [];\n for (const raw of rawImports) {\n const resolved = resolveSpecifier(raw.path, 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 const existing = edges.find((e) => e.path === resolved.absolutePath);\n if (existing) {\n if (raw.isNamespace) {\n existing.isNamespace = true;\n existing.specifiers = undefined;\n existing.isSideEffect = undefined;\n } else if (raw.specifiers && !existing.isNamespace) {\n (existing.specifiers ??= []).push(...raw.specifiers);\n existing.isSideEffect = undefined;\n }\n if (raw.isSideEffect && !existing.specifiers && !existing.isNamespace) {\n existing.isSideEffect = true;\n }\n if (raw.isDynamic) existing.isDynamic = true;\n } else {\n edges.push({\n path: resolved.absolutePath,\n specifiers: raw.specifiers,\n isNamespace: raw.isNamespace,\n isDynamic: raw.isDynamic,\n isSideEffect: raw.isSideEffect,\n });\n }\n }\n }\n\n graph[filePath] = edges;\n\n for (const edge of edges) stack.push(edge.path);\n }\n\n // Build reverse graph\n const reverseGraph: Record<string, ImportEdge[]> = {};\n for (const file of Object.keys(graph)) {\n reverseGraph[file] = [];\n }\n for (const [file, edges] of Object.entries(graph)) {\n for (const edge of edges) {\n if (!reverseGraph[edge.path]) reverseGraph[edge.path] = [];\n reverseGraph[edge.path].push({\n path: file,\n specifiers: edge.specifiers,\n isNamespace: edge.isNamespace,\n isDynamic: edge.isDynamic,\n isSideEffect: edge.isSideEffect,\n });\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, ImportEdge } 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(entry: string, options?: ImportreeOptions): 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(tree: ImportTree, changedFile: string): 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 edge of dependents) {\n if (!affected.has(edge.path)) {\n affected.add(edge.path);\n queue.push(edge.path);\n }\n }\n }\n\n affected.delete(absolute);\n return [...affected].sort();\n}\n"],"mappings":"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"}
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 * Line comments (`//`) are removed entirely. Block comments are replaced\n * with a single space (newlines within them are preserved). 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 parts: string[] = [];\n let i = 0;\n let segStart = 0;\n\n while (i < len) {\n const ch = code[i];\n const next = i + 1 < len ? code[i + 1] : \"\";\n\n if (ch === \"/\" && next === \"/\") {\n parts.push(code.slice(segStart, i));\n while (i < len && code[i] !== \"\\n\") i++;\n segStart = i;\n continue;\n }\n\n if (ch === \"/\" && next === \"*\") {\n parts.push(code.slice(segStart, i));\n i += 2;\n while (i < len && !(code[i] === \"*\" && i + 1 < len && code[i + 1] === \"/\")) {\n if (code[i] === \"\\n\") parts.push(\"\\n\");\n i++;\n }\n if (i < len) i += 2;\n parts.push(\" \");\n segStart = i;\n continue;\n }\n\n if (ch === \"'\" || ch === '\"') {\n const quote = ch;\n i++;\n while (i < len && code[i] !== quote) {\n if (code[i] === \"\\\\\" && i + 1 < len) i++;\n i++;\n }\n if (i < len) i++;\n continue;\n }\n\n if (ch === \"`\") {\n i++;\n let depth = 0;\n while (i < len) {\n if (code[i] === \"\\\\\" && i + 1 < len) {\n i += 2;\n } else if (code[i] === \"$\" && i + 1 < len && code[i + 1] === \"{\") {\n i += 2;\n depth++;\n } else if (code[i] === \"}\" && depth > 0) {\n i++;\n depth--;\n } else if (code[i] === \"`\" && depth === 0) {\n i++;\n break;\n } else {\n i++;\n }\n }\n continue;\n }\n\n i++;\n }\n\n parts.push(code.slice(segStart));\n return parts.join(\"\");\n}\n\nexport interface RawImport {\n path: string;\n specifiers?: string[];\n isNamespace?: boolean;\n isDynamic?: boolean;\n isSideEffect?: boolean;\n}\n\n// Static regex patterns — compiled once\nconst nsImportRe = /\\bimport\\s+\\*\\s+as\\s+[$\\w]+\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst namedImportRe =\n /\\bimport\\s+(?:type\\s+)?(?:([$\\w]+)\\s*,\\s*)?\\{([^}]*)\\}\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst defaultImportRe = /\\bimport\\s+(?:type\\s+)?([$\\w]+)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst reexportStarRe = /\\bexport\\s+\\*\\s+(?:as\\s+[$\\w]+\\s+)?from\\s+['\"]([^'\"]+)['\"]/g;\nconst reexportNamedRe = /\\bexport\\s+(?:type\\s+)?\\{([^}]*)\\}\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst sideEffectRe = /\\bimport\\s+['\"]([^'\"]+)['\"]/g;\nconst dynamicRe = /\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\nconst requireRe = /\\brequire\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\nexport function parseSpecifiers(clause: string): string[] {\n return clause\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n .map((s) => {\n const withoutType = s.replace(/^type\\s+/, \"\");\n const parts = withoutType.split(/\\s+as\\s+/);\n return parts[0].trim();\n })\n .filter(Boolean);\n}\n\n/**\n * Scans source code and extracts all imports with metadata.\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): RawImport[] {\n const stripped = stripComments(code);\n const results: RawImport[] = [];\n\n for (const m of stripped.matchAll(nsImportRe)) {\n results.push({ path: m[1], isNamespace: true });\n }\n\n for (const m of stripped.matchAll(namedImportRe)) {\n const specifiers = parseSpecifiers(m[2]);\n if (m[1]) specifiers.unshift(\"default\");\n results.push({ path: m[3], specifiers });\n }\n\n for (const m of stripped.matchAll(defaultImportRe)) {\n results.push({ path: m[2], specifiers: [\"default\"] });\n }\n\n for (const m of stripped.matchAll(reexportStarRe)) {\n results.push({ path: m[1], isNamespace: true });\n }\n\n for (const m of stripped.matchAll(reexportNamedRe)) {\n results.push({ path: m[2], specifiers: parseSpecifiers(m[1]) });\n }\n\n for (const m of stripped.matchAll(sideEffectRe)) {\n results.push({ path: m[1], isSideEffect: true });\n }\n\n for (const m of stripped.matchAll(dynamicRe)) {\n results.push({ path: m[1], isDynamic: true });\n }\n\n for (const m of stripped.matchAll(requireRe)) {\n results.push({ path: m[1] });\n }\n\n return results;\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(filePath: string, extensions: string[]): 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(basedir: string, options: ImportreeOptions): 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(\n ([key, value]) => [key, isAbsolute(value) ? value : resolve(basedir, value)] as const,\n );\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 { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { ImportreeOptions, ImportTree, ImportEdge } from \"./types.js\";\nimport type { RawImport } from \"./scanner.js\";\nimport { scanImports } from \"./scanner.js\";\nimport type { Resolver } from \"./resolver.js\";\nimport { createResolver } from \"./resolver.js\";\n\nexport function buildEdges(\n rawImports: RawImport[],\n resolveSpecifier: Resolver,\n filePath: string,\n): { edges: ImportEdge[]; externals: string[] } {\n const edges: ImportEdge[] = [];\n const externals: string[] = [];\n\n for (const raw of rawImports) {\n const resolved = resolveSpecifier(raw.path, filePath);\n if (!resolved) continue;\n\n if (resolved.type === \"external\" && resolved.specifier) {\n externals.push(resolved.specifier);\n } else if (resolved.type === \"local\" && resolved.absolutePath) {\n const existing = edges.find((e) => e.path === resolved.absolutePath);\n if (existing) {\n if (raw.isNamespace) {\n existing.isNamespace = true;\n existing.specifiers = undefined;\n existing.isSideEffect = undefined;\n } else if (raw.specifiers && !existing.isNamespace) {\n (existing.specifiers ??= []).push(...raw.specifiers);\n existing.isSideEffect = undefined;\n }\n if (raw.isSideEffect && !existing.specifiers && !existing.isNamespace) {\n existing.isSideEffect = true;\n }\n if (raw.isDynamic) existing.isDynamic = true;\n } else {\n edges.push({\n path: resolved.absolutePath,\n specifiers: raw.specifiers,\n isNamespace: raw.isNamespace,\n isDynamic: raw.isDynamic,\n isSideEffect: raw.isSideEffect,\n });\n }\n }\n }\n\n return { edges, externals };\n}\n\n/**\n * Walks imports starting from an entry file and builds the full dependency tree.\n * Uses iterative DFS with an explicit stack to avoid call-stack limits on deep chains.\n */\nexport async function walk(entryFile: string, options: ImportreeOptions): 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, ImportEdge[]> = {};\n const externals = new Set<string>();\n const visited = new Set<string>();\n const stack = [entrypoint];\n\n while (stack.length > 0) {\n const filePath = stack.pop()!;\n if (visited.has(filePath)) continue;\n visited.add(filePath);\n\n const content = readFileSync(filePath, \"utf-8\");\n const rawImports = scanImports(content);\n const { edges, externals: fileExternals } = buildEdges(rawImports, resolveSpecifier, filePath);\n\n for (const ext of fileExternals) externals.add(ext);\n graph[filePath] = edges;\n\n for (const edge of edges) stack.push(edge.path);\n }\n\n // Build reverse graph\n const reverseGraph: Record<string, ImportEdge[]> = {};\n for (const file of Object.keys(graph)) {\n reverseGraph[file] = [];\n }\n for (const [file, edges] of Object.entries(graph)) {\n for (const edge of edges) {\n if (!reverseGraph[edge.path]) reverseGraph[edge.path] = [];\n reverseGraph[edge.path].push({\n path: file,\n specifiers: edge.specifiers,\n isNamespace: edge.isNamespace,\n isDynamic: edge.isDynamic,\n isSideEffect: edge.isSideEffect,\n });\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 { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { ImportreeOptions, ImportTree, ImportEdge } from \"./types.js\";\nimport { buildEdges, walk } from \"./walker.js\";\nimport { scanImports } from \"./scanner.js\";\nimport { createResolver } from \"./resolver.js\";\n\nexport type { ImportreeOptions, ImportTree, ImportEdge } 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(entry: string, options?: ImportreeOptions): Promise<ImportTree> {\n return walk(entry, options ?? {});\n}\n\n/**\n * Parses a single file and returns its direct import edges without recursive traversal.\n *\n * @example\n * ```ts\n * const edges = parseImports('./src/components/Button.tsx', {\n * aliases: { '@': './src' },\n * });\n *\n * for (const edge of edges) {\n * console.log(edge.path, edge.specifiers);\n * }\n * ```\n */\nexport function parseImports(filePath: string, options?: ImportreeOptions): ImportEdge[] {\n const absolutePath = resolve(filePath);\n const basedir = options?.rootDir ? resolve(options.rootDir) : process.cwd();\n const resolveSpecifier = createResolver(basedir, options ?? {});\n\n const content = readFileSync(absolutePath, \"utf-8\");\n const rawImports = scanImports(content);\n const { edges } = buildEdges(rawImports, resolveSpecifier, absolutePath);\n\n return edges;\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(tree: ImportTree, changedFile: string): 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 edge of dependents) {\n if (!affected.has(edge.path)) {\n affected.add(edge.path);\n queue.push(edge.path);\n }\n }\n }\n\n affected.delete(absolute);\n return [...affected].sort();\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -83,6 +83,21 @@ interface ImportTree {
83
83
  */
84
84
  declare function importree(entry: string, options?: ImportreeOptions): Promise<ImportTree>;
85
85
  /**
86
+ * Parses a single file and returns its direct import edges without recursive traversal.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const edges = parseImports('./src/components/Button.tsx', {
91
+ * aliases: { '@': './src' },
92
+ * });
93
+ *
94
+ * for (const edge of edges) {
95
+ * console.log(edge.path, edge.specifiers);
96
+ * }
97
+ * ```
98
+ */
99
+ declare function parseImports(filePath: string, options?: ImportreeOptions): ImportEdge[];
100
+ /**
86
101
  * Given an import tree and a changed file, returns all files that
87
102
  * transitively depend on the changed file (i.e., files that would
88
103
  * need to be re-evaluated if the changed file is modified).
@@ -91,5 +106,5 @@ declare function importree(entry: string, options?: ImportreeOptions): Promise<I
91
106
  */
92
107
  declare function getAffectedFiles(tree: ImportTree, changedFile: string): string[];
93
108
  //#endregion
94
- export { type ImportEdge, type ImportTree, type ImportreeOptions, getAffectedFiles, importree };
109
+ export { type ImportEdge, type ImportTree, type ImportreeOptions, getAffectedFiles, importree, parseImports };
95
110
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;AAGA;;UAAiB,gBAAA;EAaL;;;;EARV,OAAA;;;AAsBF;;;;EAdE,OAAA,GAAU,MAAA;;;;;;EAOV,UAAA;AAAA;;;;;UAOe,UAAA;;EAEf,IAAA;EAyCc;;;;EAnCd,UAAA;;EAGA,WAAA;;EAGA,SAAA;;EAGA,YAAA;AAAA;;;;UAMe,UAAA;EC9BK;EDgCpB,UAAA;;EAGA,KAAA;;EAGA,SAAA;ECtC0E;;;;ED4C1E,KAAA,EAAO,MAAA,SAAe,UAAA;;;;ACjCxB;EDuCE,YAAA,EAAc,MAAA,SAAe,UAAA;AAAA;;;;;;;;;;;;AA3C/B;;;;;;;;iBCPsB,SAAA,CAAU,KAAA,UAAe,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,UAAA;;;AD8BpF;;;;;iBCnBgB,gBAAA,CAAiB,IAAA,EAAM,UAAA,EAAY,WAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;AAGA;;UAAiB,gBAAA;EAaL;;;;EARV,OAAA;;;AAsBF;;;;EAdE,OAAA,GAAU,MAAA;;;;;;EAOV,UAAA;AAAA;;;;;UAOe,UAAA;;EAEf,IAAA;EAyCc;;;;EAnCd,UAAA;;EAGA,WAAA;;EAGA,SAAA;;EAGA,YAAA;AAAA;;;;UAMe,UAAA;EC3BK;ED6BpB,UAAA;;EAGA,KAAA;;EAGA,SAAA;ECnC0E;;;;EDyC1E,KAAA,EAAO,MAAA,SAAe,UAAA;;;;ACvBxB;ED6BE,YAAA,EAAc,MAAA,SAAe,UAAA;AAAA;;;;;;;;;;;;AA3C/B;;;;;;;;iBCJsB,SAAA,CAAU,KAAA,UAAe,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,UAAA;;;AD2BpF;;;;;;;;;;;;iBCTgB,YAAA,CAAa,QAAA,UAAkB,OAAA,GAAU,gBAAA,GAAmB,UAAA;;;;;;;;iBAmB5D,gBAAA,CAAiB,IAAA,EAAM,UAAA,EAAY,WAAA"}
package/dist/index.d.mts CHANGED
@@ -83,6 +83,21 @@ interface ImportTree {
83
83
  */
84
84
  declare function importree(entry: string, options?: ImportreeOptions): Promise<ImportTree>;
85
85
  /**
86
+ * Parses a single file and returns its direct import edges without recursive traversal.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * const edges = parseImports('./src/components/Button.tsx', {
91
+ * aliases: { '@': './src' },
92
+ * });
93
+ *
94
+ * for (const edge of edges) {
95
+ * console.log(edge.path, edge.specifiers);
96
+ * }
97
+ * ```
98
+ */
99
+ declare function parseImports(filePath: string, options?: ImportreeOptions): ImportEdge[];
100
+ /**
86
101
  * Given an import tree and a changed file, returns all files that
87
102
  * transitively depend on the changed file (i.e., files that would
88
103
  * need to be re-evaluated if the changed file is modified).
@@ -91,5 +106,5 @@ declare function importree(entry: string, options?: ImportreeOptions): Promise<I
91
106
  */
92
107
  declare function getAffectedFiles(tree: ImportTree, changedFile: string): string[];
93
108
  //#endregion
94
- export { type ImportEdge, type ImportTree, type ImportreeOptions, getAffectedFiles, importree };
109
+ export { type ImportEdge, type ImportTree, type ImportreeOptions, getAffectedFiles, importree, parseImports };
95
110
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;AAGA;;UAAiB,gBAAA;EAaL;;;;EARV,OAAA;;;AAsBF;;;;EAdE,OAAA,GAAU,MAAA;;;;;;EAOV,UAAA;AAAA;;;;;UAOe,UAAA;;EAEf,IAAA;EAyCc;;;;EAnCd,UAAA;;EAGA,WAAA;;EAGA,SAAA;;EAGA,YAAA;AAAA;;;;UAMe,UAAA;EC9BK;EDgCpB,UAAA;;EAGA,KAAA;;EAGA,SAAA;ECtC0E;;;;ED4C1E,KAAA,EAAO,MAAA,SAAe,UAAA;;;;ACjCxB;EDuCE,YAAA,EAAc,MAAA,SAAe,UAAA;AAAA;;;;;;;;;;;;AA3C/B;;;;;;;;iBCPsB,SAAA,CAAU,KAAA,UAAe,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,UAAA;;;AD8BpF;;;;;iBCnBgB,gBAAA,CAAiB,IAAA,EAAM,UAAA,EAAY,WAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/index.ts"],"mappings":";;AAGA;;UAAiB,gBAAA;EAaL;;;;EARV,OAAA;;;AAsBF;;;;EAdE,OAAA,GAAU,MAAA;;;;;;EAOV,UAAA;AAAA;;;;;UAOe,UAAA;;EAEf,IAAA;EAyCc;;;;EAnCd,UAAA;;EAGA,WAAA;;EAGA,SAAA;;EAGA,YAAA;AAAA;;;;UAMe,UAAA;EC3BK;ED6BpB,UAAA;;EAGA,KAAA;;EAGA,SAAA;ECnC0E;;;;EDyC1E,KAAA,EAAO,MAAA,SAAe,UAAA;;;;ACvBxB;ED6BE,YAAA,EAAc,MAAA,SAAe,UAAA;AAAA;;;;;;;;;;;;AA3C/B;;;;;;;;iBCJsB,SAAA,CAAU,KAAA,UAAe,OAAA,GAAU,gBAAA,GAAmB,OAAA,CAAQ,UAAA;;;AD2BpF;;;;;;;;;;;;iBCTgB,YAAA,CAAa,QAAA,UAAkB,OAAA,GAAU,gBAAA,GAAmB,UAAA;;;;;;;;iBAmB5D,gBAAA,CAAiB,IAAA,EAAM,UAAA,EAAY,WAAA"}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { dirname, isAbsolute, join, resolve } from "node:path";
2
1
  import { readFileSync, statSync } from "node:fs";
2
+ import { dirname, isAbsolute, join, resolve } from "node:path";
3
3
  //#region src/scanner.ts
4
4
  /**
5
5
  * Strips comments from source code while preserving string literals.
@@ -208,6 +208,40 @@ function createResolver(basedir, options) {
208
208
  }
209
209
  //#endregion
210
210
  //#region src/walker.ts
211
+ function buildEdges(rawImports, resolveSpecifier, filePath) {
212
+ const edges = [];
213
+ const externals = [];
214
+ for (const raw of rawImports) {
215
+ const resolved = resolveSpecifier(raw.path, filePath);
216
+ if (!resolved) continue;
217
+ if (resolved.type === "external" && resolved.specifier) externals.push(resolved.specifier);
218
+ else if (resolved.type === "local" && resolved.absolutePath) {
219
+ const existing = edges.find((e) => e.path === resolved.absolutePath);
220
+ if (existing) {
221
+ if (raw.isNamespace) {
222
+ existing.isNamespace = true;
223
+ existing.specifiers = void 0;
224
+ existing.isSideEffect = void 0;
225
+ } else if (raw.specifiers && !existing.isNamespace) {
226
+ (existing.specifiers ??= []).push(...raw.specifiers);
227
+ existing.isSideEffect = void 0;
228
+ }
229
+ if (raw.isSideEffect && !existing.specifiers && !existing.isNamespace) existing.isSideEffect = true;
230
+ if (raw.isDynamic) existing.isDynamic = true;
231
+ } else edges.push({
232
+ path: resolved.absolutePath,
233
+ specifiers: raw.specifiers,
234
+ isNamespace: raw.isNamespace,
235
+ isDynamic: raw.isDynamic,
236
+ isSideEffect: raw.isSideEffect
237
+ });
238
+ }
239
+ }
240
+ return {
241
+ edges,
242
+ externals
243
+ };
244
+ }
211
245
  /**
212
246
  * Walks imports starting from an entry file and builds the full dependency tree.
213
247
  * Uses iterative DFS with an explicit stack to avoid call-stack limits on deep chains.
@@ -223,34 +257,8 @@ async function walk(entryFile, options) {
223
257
  const filePath = stack.pop();
224
258
  if (visited.has(filePath)) continue;
225
259
  visited.add(filePath);
226
- const rawImports = scanImports(readFileSync(filePath, "utf-8"));
227
- const edges = [];
228
- for (const raw of rawImports) {
229
- const resolved = resolveSpecifier(raw.path, filePath);
230
- if (!resolved) continue;
231
- if (resolved.type === "external" && resolved.specifier) externals.add(resolved.specifier);
232
- else if (resolved.type === "local" && resolved.absolutePath) {
233
- const existing = edges.find((e) => e.path === resolved.absolutePath);
234
- if (existing) {
235
- if (raw.isNamespace) {
236
- existing.isNamespace = true;
237
- existing.specifiers = void 0;
238
- existing.isSideEffect = void 0;
239
- } else if (raw.specifiers && !existing.isNamespace) {
240
- (existing.specifiers ??= []).push(...raw.specifiers);
241
- existing.isSideEffect = void 0;
242
- }
243
- if (raw.isSideEffect && !existing.specifiers && !existing.isNamespace) existing.isSideEffect = true;
244
- if (raw.isDynamic) existing.isDynamic = true;
245
- } else edges.push({
246
- path: resolved.absolutePath,
247
- specifiers: raw.specifiers,
248
- isNamespace: raw.isNamespace,
249
- isDynamic: raw.isDynamic,
250
- isSideEffect: raw.isSideEffect
251
- });
252
- }
253
- }
260
+ const { edges, externals: fileExternals } = buildEdges(scanImports(readFileSync(filePath, "utf-8")), resolveSpecifier, filePath);
261
+ for (const ext of fileExternals) externals.add(ext);
254
262
  graph[filePath] = edges;
255
263
  for (const edge of edges) stack.push(edge.path);
256
264
  }
@@ -297,6 +305,26 @@ async function importree(entry, options) {
297
305
  return walk(entry, options ?? {});
298
306
  }
299
307
  /**
308
+ * Parses a single file and returns its direct import edges without recursive traversal.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * const edges = parseImports('./src/components/Button.tsx', {
313
+ * aliases: { '@': './src' },
314
+ * });
315
+ *
316
+ * for (const edge of edges) {
317
+ * console.log(edge.path, edge.specifiers);
318
+ * }
319
+ * ```
320
+ */
321
+ function parseImports(filePath, options) {
322
+ const absolutePath = resolve(filePath);
323
+ const resolveSpecifier = createResolver(options?.rootDir ? resolve(options.rootDir) : process.cwd(), options ?? {});
324
+ const { edges } = buildEdges(scanImports(readFileSync(absolutePath, "utf-8")), resolveSpecifier, absolutePath);
325
+ return edges;
326
+ }
327
+ /**
300
328
  * Given an import tree and a changed file, returns all files that
301
329
  * transitively depend on the changed file (i.e., files that would
302
330
  * need to be re-evaluated if the changed file is modified).
@@ -321,6 +349,6 @@ function getAffectedFiles(tree, changedFile) {
321
349
  return [...affected].sort();
322
350
  }
323
351
  //#endregion
324
- export { getAffectedFiles, importree };
352
+ export { getAffectedFiles, importree, parseImports };
325
353
 
326
354
  //# sourceMappingURL=index.mjs.map
@@ -1 +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 * Line comments (`//`) are removed entirely. Block comments are replaced\n * with a single space (newlines within them are preserved). 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 parts: string[] = [];\n let i = 0;\n let segStart = 0;\n\n while (i < len) {\n const ch = code[i];\n const next = i + 1 < len ? code[i + 1] : \"\";\n\n if (ch === \"/\" && next === \"/\") {\n parts.push(code.slice(segStart, i));\n while (i < len && code[i] !== \"\\n\") i++;\n segStart = i;\n continue;\n }\n\n if (ch === \"/\" && next === \"*\") {\n parts.push(code.slice(segStart, i));\n i += 2;\n while (i < len && !(code[i] === \"*\" && i + 1 < len && code[i + 1] === \"/\")) {\n if (code[i] === \"\\n\") parts.push(\"\\n\");\n i++;\n }\n if (i < len) i += 2;\n parts.push(\" \");\n segStart = i;\n continue;\n }\n\n if (ch === \"'\" || ch === '\"') {\n const quote = ch;\n i++;\n while (i < len && code[i] !== quote) {\n if (code[i] === \"\\\\\" && i + 1 < len) i++;\n i++;\n }\n if (i < len) i++;\n continue;\n }\n\n if (ch === \"`\") {\n i++;\n let depth = 0;\n while (i < len) {\n if (code[i] === \"\\\\\" && i + 1 < len) {\n i += 2;\n } else if (code[i] === \"$\" && i + 1 < len && code[i + 1] === \"{\") {\n i += 2;\n depth++;\n } else if (code[i] === \"}\" && depth > 0) {\n i++;\n depth--;\n } else if (code[i] === \"`\" && depth === 0) {\n i++;\n break;\n } else {\n i++;\n }\n }\n continue;\n }\n\n i++;\n }\n\n parts.push(code.slice(segStart));\n return parts.join(\"\");\n}\n\nexport interface RawImport {\n path: string;\n specifiers?: string[];\n isNamespace?: boolean;\n isDynamic?: boolean;\n isSideEffect?: boolean;\n}\n\n// Static regex patterns — compiled once\nconst nsImportRe = /\\bimport\\s+\\*\\s+as\\s+[$\\w]+\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst namedImportRe =\n /\\bimport\\s+(?:type\\s+)?(?:([$\\w]+)\\s*,\\s*)?\\{([^}]*)\\}\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst defaultImportRe = /\\bimport\\s+(?:type\\s+)?([$\\w]+)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst reexportStarRe = /\\bexport\\s+\\*\\s+(?:as\\s+[$\\w]+\\s+)?from\\s+['\"]([^'\"]+)['\"]/g;\nconst reexportNamedRe = /\\bexport\\s+(?:type\\s+)?\\{([^}]*)\\}\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst sideEffectRe = /\\bimport\\s+['\"]([^'\"]+)['\"]/g;\nconst dynamicRe = /\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\nconst requireRe = /\\brequire\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\nexport function parseSpecifiers(clause: string): string[] {\n return clause\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n .map((s) => {\n const withoutType = s.replace(/^type\\s+/, \"\");\n const parts = withoutType.split(/\\s+as\\s+/);\n return parts[0].trim();\n })\n .filter(Boolean);\n}\n\n/**\n * Scans source code and extracts all imports with metadata.\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): RawImport[] {\n const stripped = stripComments(code);\n const results: RawImport[] = [];\n\n for (const m of stripped.matchAll(nsImportRe)) {\n results.push({ path: m[1], isNamespace: true });\n }\n\n for (const m of stripped.matchAll(namedImportRe)) {\n const specifiers = parseSpecifiers(m[2]);\n if (m[1]) specifiers.unshift(\"default\");\n results.push({ path: m[3], specifiers });\n }\n\n for (const m of stripped.matchAll(defaultImportRe)) {\n results.push({ path: m[2], specifiers: [\"default\"] });\n }\n\n for (const m of stripped.matchAll(reexportStarRe)) {\n results.push({ path: m[1], isNamespace: true });\n }\n\n for (const m of stripped.matchAll(reexportNamedRe)) {\n results.push({ path: m[2], specifiers: parseSpecifiers(m[1]) });\n }\n\n for (const m of stripped.matchAll(sideEffectRe)) {\n results.push({ path: m[1], isSideEffect: true });\n }\n\n for (const m of stripped.matchAll(dynamicRe)) {\n results.push({ path: m[1], isDynamic: true });\n }\n\n for (const m of stripped.matchAll(requireRe)) {\n results.push({ path: m[1] });\n }\n\n return results;\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(filePath: string, extensions: string[]): 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(basedir: string, options: ImportreeOptions): 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(\n ([key, value]) => [key, isAbsolute(value) ? value : resolve(basedir, value)] as const,\n );\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 { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { ImportreeOptions, ImportTree, ImportEdge } from \"./types.js\";\nimport { scanImports } from \"./scanner.js\";\nimport { createResolver } from \"./resolver.js\";\n\n/**\n * Walks imports starting from an entry file and builds the full dependency tree.\n * Uses iterative DFS with an explicit stack to avoid call-stack limits on deep chains.\n */\nexport async function walk(entryFile: string, options: ImportreeOptions): 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, ImportEdge[]> = {};\n const externals = new Set<string>();\n const visited = new Set<string>();\n const stack = [entrypoint];\n\n while (stack.length > 0) {\n const filePath = stack.pop()!;\n if (visited.has(filePath)) continue;\n visited.add(filePath);\n\n const content = readFileSync(filePath, \"utf-8\");\n const rawImports = scanImports(content);\n\n const edges: ImportEdge[] = [];\n for (const raw of rawImports) {\n const resolved = resolveSpecifier(raw.path, 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 const existing = edges.find((e) => e.path === resolved.absolutePath);\n if (existing) {\n if (raw.isNamespace) {\n existing.isNamespace = true;\n existing.specifiers = undefined;\n existing.isSideEffect = undefined;\n } else if (raw.specifiers && !existing.isNamespace) {\n (existing.specifiers ??= []).push(...raw.specifiers);\n existing.isSideEffect = undefined;\n }\n if (raw.isSideEffect && !existing.specifiers && !existing.isNamespace) {\n existing.isSideEffect = true;\n }\n if (raw.isDynamic) existing.isDynamic = true;\n } else {\n edges.push({\n path: resolved.absolutePath,\n specifiers: raw.specifiers,\n isNamespace: raw.isNamespace,\n isDynamic: raw.isDynamic,\n isSideEffect: raw.isSideEffect,\n });\n }\n }\n }\n\n graph[filePath] = edges;\n\n for (const edge of edges) stack.push(edge.path);\n }\n\n // Build reverse graph\n const reverseGraph: Record<string, ImportEdge[]> = {};\n for (const file of Object.keys(graph)) {\n reverseGraph[file] = [];\n }\n for (const [file, edges] of Object.entries(graph)) {\n for (const edge of edges) {\n if (!reverseGraph[edge.path]) reverseGraph[edge.path] = [];\n reverseGraph[edge.path].push({\n path: file,\n specifiers: edge.specifiers,\n isNamespace: edge.isNamespace,\n isDynamic: edge.isDynamic,\n isSideEffect: edge.isSideEffect,\n });\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, ImportEdge } 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(entry: string, options?: ImportreeOptions): 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(tree: ImportTree, changedFile: string): 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 edge of dependents) {\n if (!affected.has(edge.path)) {\n affected.add(edge.path);\n queue.push(edge.path);\n }\n }\n }\n\n affected.delete(absolute);\n return [...affected].sort();\n}\n"],"mappings":"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"}
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 * Line comments (`//`) are removed entirely. Block comments are replaced\n * with a single space (newlines within them are preserved). 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 parts: string[] = [];\n let i = 0;\n let segStart = 0;\n\n while (i < len) {\n const ch = code[i];\n const next = i + 1 < len ? code[i + 1] : \"\";\n\n if (ch === \"/\" && next === \"/\") {\n parts.push(code.slice(segStart, i));\n while (i < len && code[i] !== \"\\n\") i++;\n segStart = i;\n continue;\n }\n\n if (ch === \"/\" && next === \"*\") {\n parts.push(code.slice(segStart, i));\n i += 2;\n while (i < len && !(code[i] === \"*\" && i + 1 < len && code[i + 1] === \"/\")) {\n if (code[i] === \"\\n\") parts.push(\"\\n\");\n i++;\n }\n if (i < len) i += 2;\n parts.push(\" \");\n segStart = i;\n continue;\n }\n\n if (ch === \"'\" || ch === '\"') {\n const quote = ch;\n i++;\n while (i < len && code[i] !== quote) {\n if (code[i] === \"\\\\\" && i + 1 < len) i++;\n i++;\n }\n if (i < len) i++;\n continue;\n }\n\n if (ch === \"`\") {\n i++;\n let depth = 0;\n while (i < len) {\n if (code[i] === \"\\\\\" && i + 1 < len) {\n i += 2;\n } else if (code[i] === \"$\" && i + 1 < len && code[i + 1] === \"{\") {\n i += 2;\n depth++;\n } else if (code[i] === \"}\" && depth > 0) {\n i++;\n depth--;\n } else if (code[i] === \"`\" && depth === 0) {\n i++;\n break;\n } else {\n i++;\n }\n }\n continue;\n }\n\n i++;\n }\n\n parts.push(code.slice(segStart));\n return parts.join(\"\");\n}\n\nexport interface RawImport {\n path: string;\n specifiers?: string[];\n isNamespace?: boolean;\n isDynamic?: boolean;\n isSideEffect?: boolean;\n}\n\n// Static regex patterns — compiled once\nconst nsImportRe = /\\bimport\\s+\\*\\s+as\\s+[$\\w]+\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst namedImportRe =\n /\\bimport\\s+(?:type\\s+)?(?:([$\\w]+)\\s*,\\s*)?\\{([^}]*)\\}\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst defaultImportRe = /\\bimport\\s+(?:type\\s+)?([$\\w]+)\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst reexportStarRe = /\\bexport\\s+\\*\\s+(?:as\\s+[$\\w]+\\s+)?from\\s+['\"]([^'\"]+)['\"]/g;\nconst reexportNamedRe = /\\bexport\\s+(?:type\\s+)?\\{([^}]*)\\}\\s+from\\s+['\"]([^'\"]+)['\"]/g;\nconst sideEffectRe = /\\bimport\\s+['\"]([^'\"]+)['\"]/g;\nconst dynamicRe = /\\bimport\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\nconst requireRe = /\\brequire\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\nexport function parseSpecifiers(clause: string): string[] {\n return clause\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n .map((s) => {\n const withoutType = s.replace(/^type\\s+/, \"\");\n const parts = withoutType.split(/\\s+as\\s+/);\n return parts[0].trim();\n })\n .filter(Boolean);\n}\n\n/**\n * Scans source code and extracts all imports with metadata.\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): RawImport[] {\n const stripped = stripComments(code);\n const results: RawImport[] = [];\n\n for (const m of stripped.matchAll(nsImportRe)) {\n results.push({ path: m[1], isNamespace: true });\n }\n\n for (const m of stripped.matchAll(namedImportRe)) {\n const specifiers = parseSpecifiers(m[2]);\n if (m[1]) specifiers.unshift(\"default\");\n results.push({ path: m[3], specifiers });\n }\n\n for (const m of stripped.matchAll(defaultImportRe)) {\n results.push({ path: m[2], specifiers: [\"default\"] });\n }\n\n for (const m of stripped.matchAll(reexportStarRe)) {\n results.push({ path: m[1], isNamespace: true });\n }\n\n for (const m of stripped.matchAll(reexportNamedRe)) {\n results.push({ path: m[2], specifiers: parseSpecifiers(m[1]) });\n }\n\n for (const m of stripped.matchAll(sideEffectRe)) {\n results.push({ path: m[1], isSideEffect: true });\n }\n\n for (const m of stripped.matchAll(dynamicRe)) {\n results.push({ path: m[1], isDynamic: true });\n }\n\n for (const m of stripped.matchAll(requireRe)) {\n results.push({ path: m[1] });\n }\n\n return results;\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(filePath: string, extensions: string[]): 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(basedir: string, options: ImportreeOptions): 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(\n ([key, value]) => [key, isAbsolute(value) ? value : resolve(basedir, value)] as const,\n );\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 { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { ImportreeOptions, ImportTree, ImportEdge } from \"./types.js\";\nimport type { RawImport } from \"./scanner.js\";\nimport { scanImports } from \"./scanner.js\";\nimport type { Resolver } from \"./resolver.js\";\nimport { createResolver } from \"./resolver.js\";\n\nexport function buildEdges(\n rawImports: RawImport[],\n resolveSpecifier: Resolver,\n filePath: string,\n): { edges: ImportEdge[]; externals: string[] } {\n const edges: ImportEdge[] = [];\n const externals: string[] = [];\n\n for (const raw of rawImports) {\n const resolved = resolveSpecifier(raw.path, filePath);\n if (!resolved) continue;\n\n if (resolved.type === \"external\" && resolved.specifier) {\n externals.push(resolved.specifier);\n } else if (resolved.type === \"local\" && resolved.absolutePath) {\n const existing = edges.find((e) => e.path === resolved.absolutePath);\n if (existing) {\n if (raw.isNamespace) {\n existing.isNamespace = true;\n existing.specifiers = undefined;\n existing.isSideEffect = undefined;\n } else if (raw.specifiers && !existing.isNamespace) {\n (existing.specifiers ??= []).push(...raw.specifiers);\n existing.isSideEffect = undefined;\n }\n if (raw.isSideEffect && !existing.specifiers && !existing.isNamespace) {\n existing.isSideEffect = true;\n }\n if (raw.isDynamic) existing.isDynamic = true;\n } else {\n edges.push({\n path: resolved.absolutePath,\n specifiers: raw.specifiers,\n isNamespace: raw.isNamespace,\n isDynamic: raw.isDynamic,\n isSideEffect: raw.isSideEffect,\n });\n }\n }\n }\n\n return { edges, externals };\n}\n\n/**\n * Walks imports starting from an entry file and builds the full dependency tree.\n * Uses iterative DFS with an explicit stack to avoid call-stack limits on deep chains.\n */\nexport async function walk(entryFile: string, options: ImportreeOptions): 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, ImportEdge[]> = {};\n const externals = new Set<string>();\n const visited = new Set<string>();\n const stack = [entrypoint];\n\n while (stack.length > 0) {\n const filePath = stack.pop()!;\n if (visited.has(filePath)) continue;\n visited.add(filePath);\n\n const content = readFileSync(filePath, \"utf-8\");\n const rawImports = scanImports(content);\n const { edges, externals: fileExternals } = buildEdges(rawImports, resolveSpecifier, filePath);\n\n for (const ext of fileExternals) externals.add(ext);\n graph[filePath] = edges;\n\n for (const edge of edges) stack.push(edge.path);\n }\n\n // Build reverse graph\n const reverseGraph: Record<string, ImportEdge[]> = {};\n for (const file of Object.keys(graph)) {\n reverseGraph[file] = [];\n }\n for (const [file, edges] of Object.entries(graph)) {\n for (const edge of edges) {\n if (!reverseGraph[edge.path]) reverseGraph[edge.path] = [];\n reverseGraph[edge.path].push({\n path: file,\n specifiers: edge.specifiers,\n isNamespace: edge.isNamespace,\n isDynamic: edge.isDynamic,\n isSideEffect: edge.isSideEffect,\n });\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 { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { ImportreeOptions, ImportTree, ImportEdge } from \"./types.js\";\nimport { buildEdges, walk } from \"./walker.js\";\nimport { scanImports } from \"./scanner.js\";\nimport { createResolver } from \"./resolver.js\";\n\nexport type { ImportreeOptions, ImportTree, ImportEdge } 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(entry: string, options?: ImportreeOptions): Promise<ImportTree> {\n return walk(entry, options ?? {});\n}\n\n/**\n * Parses a single file and returns its direct import edges without recursive traversal.\n *\n * @example\n * ```ts\n * const edges = parseImports('./src/components/Button.tsx', {\n * aliases: { '@': './src' },\n * });\n *\n * for (const edge of edges) {\n * console.log(edge.path, edge.specifiers);\n * }\n * ```\n */\nexport function parseImports(filePath: string, options?: ImportreeOptions): ImportEdge[] {\n const absolutePath = resolve(filePath);\n const basedir = options?.rootDir ? resolve(options.rootDir) : process.cwd();\n const resolveSpecifier = createResolver(basedir, options ?? {});\n\n const content = readFileSync(absolutePath, \"utf-8\");\n const rawImports = scanImports(content);\n const { edges } = buildEdges(rawImports, resolveSpecifier, absolutePath);\n\n return edges;\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(tree: ImportTree, changedFile: string): 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 edge of dependents) {\n if (!affected.has(edge.path)) {\n affected.add(edge.path);\n queue.push(edge.path);\n }\n }\n }\n\n affected.delete(absolute);\n return [...affected].sort();\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "importree",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Build import dependency trees for TypeScript and JavaScript files. Fast, zero-dependency static analysis for dependency detection and cache invalidation.",
5
5
  "keywords": [
6
6
  "cache-invalidation",