skott: process.chdir does not affect working directory used by skott for creating node id paths

Summary

When running an analysis with the API and specifying a directory via cwd, the node ids end up being paths relative to the parent node process’s current working directory. For example, if running a node script index.js from /usr/application which runs

const api = await skott({
  cwd: '/some/other/path'
  incremental: false,
  circularMaxDepth: 100
});

const { graph } = await api.getStructure();

Node ids would follow the form ../../some/other/path/index.js instead of the expectation of index.js (or even just /some/other/path/index.js). Whether this is intentional or not is difficult to tell.

However, even when running process.chdir('/some/other/path') prior to the above, it doesn’t affect the node ids. I would expect that to have some effect.

Reproduction steps

I tried to make a codesandbox that fails, but cannot reproduce there. However, running the following locally causes me the issue

  1. Create index.js file at /tmp/repo/index.js
  2. Run the following with node script.js

script.js

import process from 'process';
import skott from 'skott';

process.chdir('/tmp/repo');

const api = await skott({
  cwd: '/tmp/repo',
  incremental: false,
});

const { graph } = await api.getStructure();
console.log(JSON.stringify(graph, null, 2) + '\n');

Expected result: Graph with single node with id index.js

Actual result: Depending on where running from, graph with single node with id like ../../../tmp/repo/index.js

Details

This appears to be related to the following line: https://github.com/antoine-coulon/skott/blob/295d4b8aed0593f0f7a738e4d0214cd4520ba32a/packages/skott/src/skott.ts#L158C3-L158C18

this.#baseDir = '.';

This #baseDir is updated during buildFromEntrypoint, but not in buildFromRootDirectory (which is invoked here). In resolveNodePath, this ends up returning path.relative(this.#baseDir, nodePath). I expect the solution here is one of the following:

  • Add an extra parameter to skott that allows the specification of the baseDir of the repo for analysis explicitly. This makes sense when used in conjunction with cwd in the context of a monorepo. Right now, skott implicitly assumes via the API that it’s being executed in the root of the project under analysis.
  • When running buildFromRootDirectory, change the #baseDir to be the cwd specified in the options. This may have some interactions when accessing scope “above” the cwd that I’m not aware of, however.

Standard questions

Question Answer
skott installed version? 0.33.0
Operating system? Mac
Would you consider contributing a PR? Yes
Node.js version (node -v)? 18.19.1

About this issue

  • Original URL
  • State: open
  • Created 4 months ago
  • Comments: 16 (8 by maintainers)

Most upvoted comments

That’s usually the type of small bug that can make you waste hours 😆

I just released 0.33.1 including the small patch for process.chdir, let me know how that goes for you on that side

Yes, that’s exactly right. Dang, I was looking around the repository for what you found in the config file, but missed it. Nice!

Thanks for providing these graphs @mattkindy. I am able to reproduce the behavior consistently so I should be able to land something confidently.

Let’s just recap on the situation to be sure it will fit your expectations.

Given an absolute path such as /var/folders/3v/some-project (basically a temp folder) and a skott’s process being run in the following folder /Users/johnskott/blabla/some-folder with that:

skott({ cwd: "/var/folders/3v/some-project" })

skott will have graph node paths written in a relative way from where it was initially executed (/Users/johnskott/blabla/some-folder) such as ../../../../../../../../../var/folders/3v/some-project/some-file.js.

So the graph is correctly computed but paths are ugly as the location of the folder is higher FS-wise to the location where skott was executed. Regarding that, we could achieve normalization (what you pasted there) by default and otherwise when using --includeBaseDir (which is currently disabled when using --cwd), paths would look exactly as it is now, because including the base directory in that case means that paths should contain the information where skott was run.

To come back a little bit on “process.chdir”, I found the problem with skott and I’ll land a fix (this is orthogonal to the subject just above)

It was indeed due to process.cwd() being eagerly executed there https://github.com/antoine-coulon/skott/blob/078096ec2cd394a4629a600beb7c7ee4d65d41da/packages/skott/src/config.ts#L55

so the following would work for you as expected and would provide the expected paths that is directly starting from the temp location:

process.chdir("/var/folders/3v/lr71j2ks5flgnjw2n82qf49w0000gn/T/skott_store/knex/knex@7363211");

skott().then(({ getStructure }) => {
  return getStructure();
})

Thanks for such a complete feedback @mattkindy!

This was only slightly confusing – in the docs, you mention this within the context of a monorepo, so this made sense. I originally tried to use cwd thinking it would behave differently, but ultimately, this is understood. (Side note, I do find it somewhat confusing given the lack of a “base directory” option).

I agree, as already stated I think it’s a bit misleading, cwd lets the user think that the working directory from a process perspective will be updated, but it’s not.

Consequently from an analysis standpoint, the behavior is strictly the same and the graph should end up being the same (if not then it’s a bug). So in the end it’s a matter of what type of node path you want, whether relative from the cwd or kind of absolute from skott script location.

It turns out that most of the time I found the DX to be better and the graph to be more visible when getting rid of all the relative path madness ../../module.js -> ../../../lib/whatever.js, but as you mentioned the solution might be to let the choice of what should be the base directory (so in the same spirit as when using an --entrypoint, you have the choice of keeping the base directory or to prune it) and it would kind of unify both behaviors (entrypoint vs root dir).

In addition to the option that should be available, there is also the fact of changing the current working directory outside of skott that you mentioned: using process.chdir. This one should normally work when not providing any cwd as the default runtime config takes process.cwd() as the default so it might be a bug indeed.

I won’t have the opportunity to dive in this week-end but I’ll be able to spend some serious time on this issue starting from the beginning of next week 😃 Also if you ever want to land PRs around these topics, feel free to do so!

For now I’m dealing with this with

    const normalizeId = (id: string) => path.relative(cwd, id);

    const { graph } = await api.getStructure();
    return Object.fromEntries(
      Object.entries(graph).map(([id, graph]) => {
        const basePath = normalizeId(id);
        return [
          basePath, 
          { 
            ...graph, 
            id: basePath, 
            adjacentTo: graph.adjacentTo.map(normalizeId) 
          }
        ];
      })
    );