import { strict as assert } from 'node:assert' import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { test } from 'node:test' import { ROOT_SKIP_DIRS, listRootFiles, parseSourceRoot, resolveSourceRoots, rootRelativePath, sanitizeRootName, skipsRootDir, } from '../lib/roots.js' test('a root defaults its name to the directory basename', () => { const parsed = parseSourceRoot('/usr/local/lib/node_modules/@deepseek-ai', '/repo') assert.equal(parsed.name, '@deepseek-ai') assert.equal(parsed.path, '/usr/local/lib/node_modules/@deepseek-ai') assert.equal(parsed.workspaceRelative, false) }) test('an explicit name is taken as written', () => { const parsed = parseSourceRoot('dsh=/usr/local/lib/node_modules/@deepseek-ai', '/repo') assert.equal(parsed.name, 'dsh') assert.equal(parsed.path, '/usr/local/lib/node_modules/@deepseek-ai') }) test('a relative root resolves against the workspace and says so', () => { const parsed = parseSourceRoot('vendor/lib', '/repo') assert.equal(parsed.path, '/repo/vendor/lib') assert.equal(parsed.name, 'lib') assert.equal(parsed.workspaceRelative, true) }) test('a name containing a separator is refused, because it would forge a path', () => { assert.throws(() => parseSourceRoot('a/b=/tmp/x', '/repo'), /path separator/) }) test('an entry that names no path or yields no name is refused', () => { assert.throws(() => parseSourceRoot('', '/repo'), /empty entry/) assert.throws(() => parseSourceRoot('name=', '/repo'), /names no path/) // A root that climbs to the filesystem root has no basename to be labelled // with, and `resolve` collapsing the traversal is exactly why the check is on // the resolved path rather than on the text of the entry. assert.throws(() => parseSourceRoot('/../..', '/repo'), /does not yield a usable name/) }) test('sanitizeRootName keeps the characters a path segment can hold', () => { assert.equal(sanitizeRootName('@deepseek-ai'), '@deepseek-ai') assert.equal(sanitizeRootName('my root'), 'my-root') assert.equal(sanitizeRootName('..'), '') assert.equal(sanitizeRootName('.'), '') assert.equal(sanitizeRootName(' '), '') }) test('rootRelativePath never emits a leading or trailing separator', () => { assert.equal(rootRelativePath('dsh', 'lib/index.js'), 'dsh/lib/index.js') assert.equal(rootRelativePath('dsh', ''), 'dsh') }) test('resolveSourceRoots skips a bad entry loudly and keeps the rest', () => { const result = resolveSourceRoots(['/good', 'missing=/nope', '/good'], { workspace: '/repo', isDirectory: (path) => path === '/good', }) assert.deepEqual(result.roots, [{ name: 'good', path: '/good' }]) // A duplicate name is dropped rather than silently overwriting: two roots with // one label would make every result from the second ambiguous. assert.equal(result.problems.length, 2) assert.match(result.problems[0], /not a directory/) assert.match(result.problems[1], /duplicates the name/) }) test('resolveSourceRoots tolerates no configuration at all', () => { assert.deepEqual(resolveSourceRoots([], { workspace: '/repo' }), { roots: [], problems: [] }) assert.deepEqual(resolveSourceRoots(undefined, { workspace: '/repo' }), { roots: [], problems: [] }) }) test('the root itself is entered whatever it is called, and node_modules below it is not', () => { // This asymmetry is the feature: a root is normally named `.../node_modules/...`, // while a `node_modules` inside one of its packages is somebody else's code. assert.equal(skipsRootDir(0, 'node_modules'), false, 'the root must not be skipped') assert.equal(skipsRootDir(0, '.git'), false) assert.equal(skipsRootDir(1, 'node_modules'), true, 'a nested dependency store must be skipped') assert.equal(skipsRootDir(3, 'node_modules'), true) assert.equal(skipsRootDir(1, 'dist'), true) assert.equal(skipsRootDir(1, 'lib'), false) assert.ok(ROOT_SKIP_DIRS.includes('node_modules'), 'the skip list must still name node_modules') }) test('listRootFiles indexes a package tree and not its dependencies', async (t) => { const root = await mkdtemp(join(tmpdir(), 'recall-root-')) t.after(() => rm(root, { recursive: true, force: true })) const packages = join(root, 'node_modules', '@scope') await mkdir(join(packages, 'alpha', 'lib'), { recursive: true }) await mkdir(join(packages, 'alpha', 'node_modules', 'dep'), { recursive: true }) await mkdir(join(packages, 'beta', 'dist'), { recursive: true }) await writeFile(join(packages, 'alpha', 'lib', 'index.js'), 'export const a = 1\n') await writeFile(join(packages, 'alpha', 'node_modules', 'dep', 'index.js'), 'module.exports = 0\n') await writeFile(join(packages, 'beta', 'dist', 'bundle.js'), 'var x = 1\n') await writeFile(join(packages, 'beta', 'index.js'), 'export const b = 2\n') await writeFile(join(packages, 'README.md'), '# not code\n') const walk = await listRootFiles({ name: 'dsh', path: packages }) assert.deepEqual( walk.files.map((file) => file.path), ['dsh/alpha/lib/index.js', 'dsh/beta/index.js'], 'the root is entered, a dependency below it is not, and dist is not', ) // Every path is labelled but resolvable: `read` needs the absolute one. for (const file of walk.files) assert.ok(file.absolute.startsWith(packages), file.absolute) }) test('listRootFiles does not follow a symlink out of the root', async (t) => { const root = await mkdtemp(join(tmpdir(), 'recall-root-')) const outside = await mkdtemp(join(tmpdir(), 'recall-outside-')) t.after(async () => { await rm(root, { recursive: true, force: true }) await rm(outside, { recursive: true, force: true }) }) await mkdir(join(root, 'pkg'), { recursive: true }) await writeFile(join(root, 'pkg', 'a.js'), 'export const a = 1\n') await writeFile(join(outside, 'secret.js'), 'const secret = 1\n') await symlink(outside, join(root, 'link'), 'dir') const walk = await listRootFiles({ name: 'r', path: root }) assert.deepEqual(walk.files.map((file) => file.path), ['r/pkg/a.js']) }) test('a file at the root of a root is labelled with the root name alone', async (t) => { const root = await mkdtemp(join(tmpdir(), 'recall-root-')) t.after(() => rm(root, { recursive: true, force: true })) await writeFile(join(root, 'index.js'), 'export const root = 1\n') const walk = await listRootFiles({ name: 'solo', path: root }) assert.deepEqual(walk.files.map((file) => file.path), ['solo/index.js']) })