initNetwork(graphConfig)}
+ groupedGraphAvailable={!!appStore.getState().data.groupedGraph}
+ graphMode={graphMode}
+ onGraphModeChange={(graph) => toggleGraph(appStore)({ graph })}
/>
(config: O.Option>): void {
if (O.isSome(config)) {
- const { entrypoint, includeBaseDir, cwd } = config.value;
+ const { entrypoint, includeBaseDir, cwd, groups } = config.value;
if (!entrypoint && includeBaseDir) {
raiseIllegalConfigException(
@@ -33,7 +33,65 @@ function checkIllegalConfigs(config: O.Option>): void {
"`cwd` can't be customized when providing an entrypoint"
);
}
+
+ if (groups) {
+ const list = Object.entries(groups);
+
+ for (const [groupName, group] of list) {
+ for (const [otherGroupName, otherGroup] of list) {
+ if (groupName === otherGroupName) {
+ continue;
+ }
+
+ const resolvedPath = resolveGroupPath(group);
+ const otherResolvedPath = resolveGroupPath(otherGroup);
+
+ if (resolvedPath && otherResolvedPath) {
+ if (
+ resolvedPath === otherResolvedPath ||
+ resolvedPath.includes(otherResolvedPath) ||
+ otherResolvedPath.includes(resolvedPath)
+ ) {
+ raiseIllegalConfigException(
+ `Overlapping groups: ${groupName}, ${otherGroupName}`
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+function resolveGroupPath(
+ group: Exclude["groups"], undefined>[string]
+): string {
+ let resolvedPath: string = "";
+
+ if (typeof group === "string") {
+ resolvedPath = group;
+ } else if ("basePath" in group) {
+ resolvedPath = group.basePath;
}
+
+ /* trim stuff */
+ if (resolvedPath.startsWith(".")) {
+ resolvedPath = resolvedPath.slice(1);
+ }
+
+ if (resolvedPath.startsWith("/")) {
+ resolvedPath = resolvedPath.slice(1);
+ }
+
+ if (resolvedPath.endsWith("*")) {
+ resolvedPath = resolvedPath.slice(0, -1);
+ }
+
+ if (resolvedPath.endsWith("/")) {
+ resolvedPath = resolvedPath.slice(0, -1);
+ }
+
+ return resolvedPath;
}
export default async function skott(
diff --git a/packages/skott/src/config.ts b/packages/skott/src/config.ts
index c0af9ea6d..88f424986 100644
--- a/packages/skott/src/config.ts
+++ b/packages/skott/src/config.ts
@@ -27,6 +27,12 @@ const config = D.struct({
typeOnly: D.boolean
})
),
+ groups: withDefaultValue(defaultConfig.groups)({
+ /**
+ * Temporary placeholder, will be replaced by a proper decoder
+ */
+ decode: (v) => v as any
+ }),
fileExtensions: withDefaultValue(defaultConfig.fileExtensions)(
D.array(D.literal(".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"))
),
diff --git a/packages/skott/src/skott.ts b/packages/skott/src/skott.ts
index 302d4239e..f0d024545 100644
--- a/packages/skott/src/skott.ts
+++ b/packages/skott/src/skott.ts
@@ -64,10 +64,32 @@ export interface SkottConfig {
incremental: boolean;
manifestPath: string;
tsConfigPath: string;
+ /**
+ * Grouping rules, which will be used to create compacted "grouped" vesrion of the graph
+ *
+ * Group can be either a string, string pattern or a configuration object
+ */
+ groups?: {
+ [key: string]:
+ | string
+ | {
+ /**
+ * Scope of the group
+ */
+ basePath: string;
+ /**
+ *
+ * @param modulePath path to the module
+ * @returns group name, to which the module belongs or null if it does not belong to the group
+ */
+ getGroup: (modulePath: string) => string | null;
+ };
+ };
}
export interface SkottStructure {
graph: Record>;
+ groupedGraph?: Record>;
files: string[];
}
@@ -103,7 +125,8 @@ export const defaultConfig = {
includeBaseDir: false,
incremental: false,
manifestPath: "package.json",
- tsConfigPath: "tsconfig.json"
+ tsConfigPath: "tsconfig.json",
+ groups: undefined
};
export interface WorkspaceConfiguration {
@@ -115,6 +138,9 @@ export interface WorkspaceConfiguration {
export class Skott {
#cacheHandler: SkottCacheHandler;
#projectGraph = new DiGraph>();
+ #projectCompactGraph: DiGraph> | null =
+ null;
+ #groupResolver: (node: string) => string | null = () => null;
#visitedNodes = new Set();
#baseDir = ".";
#workspaceConfiguration: WorkspaceConfiguration = {
@@ -136,6 +162,13 @@ export class Skott {
this.config,
this.logger
);
+
+ if (config.groups) {
+ this.#projectCompactGraph = new DiGraph<
+ SkottNode
+ >();
+ this.#groupResolver = this.getGroupResolver();
+ }
}
public getStructureCache(): SkottCache {
@@ -210,17 +243,148 @@ export class Skott {
return normalizedNodePath;
}
+ /**
+ *
+ * Converts `config.groups` into a single function that will return group name for provided path
+ *
+ * @returns (node: string) => string
+ */
+ private getGroupResolver(): (node: string) => string | null {
+ const groups = this.config.groups;
+
+ if (!groups) {
+ return (node) => node;
+ }
+
+ const groupResolvers = Object.entries(groups).map(([groupName, group]) => {
+ if (typeof group === "string") {
+ /**
+ * Group is a string pattern like `src/core/*`,
+ * where `/*` will tell that all subdirectories of `src/core` are subgroups of `groupName`
+ */
+ if (group.endsWith("/*")) {
+ return (node: string) => {
+ const resolvedNodePath = this.resolveNodePath(node);
+ const resolvedGroupBasePath = group.slice(0, -2);
+
+ if (resolvedNodePath.includes(resolvedGroupBasePath)) {
+ const subPathPos =
+ resolvedNodePath.indexOf(resolvedGroupBasePath) +
+ resolvedGroupBasePath.length +
+ 1;
+ const subGroupName = resolvedNodePath.slice(
+ subPathPos,
+ resolvedNodePath.indexOf("/", subPathPos)
+ );
+
+ return `${groupName}/${subGroupName}`;
+ }
+
+ return null;
+ };
+ }
+
+ /**
+ * Group is a simple string pattern like `src/core`
+ */
+ return (node: string) => {
+ const resolvedNodePath = this.resolveNodePath(node);
+ const resolvedGroupBasePath = this.resolveNodePath(group);
+ if (resolvedNodePath.includes(resolvedGroupBasePath)) {
+ return groupName;
+ }
+
+ return null;
+ };
+ }
+
+ /**
+ * Group is a configuration object like `{ basePath: "src/core", getGroup: (node) => "core" }`
+ */
+ return (node: string) => {
+ const resolvedBasePath = this.resolveNodePath(group.basePath);
+ const resolvedNodePath = this.resolveNodePath(node);
+
+ if (resolvedNodePath.includes(resolvedBasePath)) {
+ const subGroupName = group.getGroup(resolvedNodePath);
+
+ if (!subGroupName) {
+ /**
+ * If `getGroup` returns null, it means that the node does not belong to the group
+ */
+ return null;
+ }
+
+ return `${groupName}/${subGroupName}`;
+ }
+
+ return null;
+ };
+ });
+
+ /**
+ * Universal group resolver that will try to resolve node using all group resolvers
+ */
+ return (node: string) => {
+ for (const resolver of groupResolvers) {
+ const resolvedNode = resolver(node);
+
+ if (resolvedNode) {
+ return resolvedNode;
+ }
+ }
+
+ return null;
+ };
+ }
+
private async addNode(node: string): Promise {
+ const size = await this.fileReader.stats(node);
this.#projectGraph.addVertex({
id: this.resolveNodePath(node),
adjacentTo: [],
// @ts-ignore
body: {
- size: await this.fileReader.stats(node),
+ size,
thirdPartyDependencies: [],
builtinDependencies: []
}
});
+
+ if (this.config.groups) {
+ const groupName = this.#groupResolver(node);
+
+ if (groupName) {
+ if (this.#projectCompactGraph!.hasVertex(groupName)) {
+ /**
+ * Group vertex already exists, we need to add up the size of the new node
+ */
+ this.#projectCompactGraph!.mergeVertexBody(groupName, (body) => {
+ if (!body.contains.includes(node)) {
+ body.size += size;
+ body.contains.push(node);
+ }
+ });
+ } else {
+ /**
+ * Group vertex does not exist yet, we need to create it
+ *
+ * Initial size is the size of the first node
+ */
+ this.#projectCompactGraph!.addVertex({
+ id: groupName,
+ adjacentTo: [],
+ // @ts-expect-error
+ body: {
+ size,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [node]
+ }
+ });
+ }
+ }
+ }
}
private async linkNodes({
@@ -237,6 +401,18 @@ export class Skott {
from: this.resolveNodePath(from, !useRelativeResolution),
to: this.resolveNodePath(to)
});
+
+ if (this.config.groups) {
+ const fromGroup = this.#groupResolver(from);
+ const toGroup = this.#groupResolver(to);
+
+ if (fromGroup && toGroup) {
+ this.#projectCompactGraph!.addEdge({
+ from: fromGroup,
+ to: toGroup
+ });
+ }
+ }
}
private async findModuleDeclarations(
@@ -493,6 +669,7 @@ export class Skott {
return {
graph: projectStructure,
+ groupedGraph: this.#projectCompactGraph?.toDict(),
files: Object.keys(projectStructure)
};
}
diff --git a/packages/skott/test/integration/api.spec.ts b/packages/skott/test/integration/api.spec.ts
index e318b9474..b4773aa4b 100644
--- a/packages/skott/test/integration/api.spec.ts
+++ b/packages/skott/test/integration/api.spec.ts
@@ -44,6 +44,94 @@ describe("When running Skott using all real dependencies", () => {
"Illegal configuration: `cwd` can't be customized when providing an entrypoint"
);
});
+
+ describe("When providing a groups configuration", () => {
+ test("Should allow to provide a simple groups", async () => {
+ expect(
+ await skott({
+ groups: {
+ groupA: "src/groupA",
+ groupB: "src/groupB"
+ }
+ })
+ ).toBeDefined();
+ });
+ test("Should allow to provide a pattern based groups", async () => {
+ expect(
+ await skott({
+ groups: {
+ features: "src/features/*",
+ widgets: "src/widgets/*"
+ }
+ })
+ ).toBeDefined();
+ });
+ test("Should allow to provide a configuration based groups", async () => {
+ expect(
+ await skott({
+ groups: {
+ features: {
+ basePath: "src/features",
+ getGroup: (filePath) => filePath.split("/")[2]
+ },
+ widgets: "src/widgets/*"
+ }
+ })
+ ).toBeDefined();
+ });
+ test("Should allow to hide a specific group", async () => {
+ expect(
+ await skott({
+ groups: {
+ features: {
+ hide: true
+ },
+ widgets: "src/widgets/*"
+ }
+ })
+ ).toBeDefined();
+ });
+
+ test("Should not allow overlapping group paths", async () => {
+ await expect(
+ skott({
+ groups: {
+ widgetsA: "src/team-a/widgets/*",
+ teamA: "src/team-a/*"
+ }
+ })
+ ).rejects.toThrow(
+ "Illegal configuration: Overlapping groups: widgetsA, teamA"
+ );
+ });
+ test("Should not allow overlapping group paths with config based groups", async () => {
+ await expect(
+ skott({
+ groups: {
+ widgetsA: {
+ basePath: "src/team-a/widgets",
+ getGroup: (filePath) => filePath.split("/")[2]
+ },
+ teamA: "src/team-a/*"
+ }
+ })
+ ).rejects.toThrow(
+ "Illegal configuration: Overlapping groups: widgetsA, teamA"
+ );
+ });
+ test("Should not allow the same path twice", async () => {
+ await expect(
+ skott({
+ groups: {
+ widgetsA: "src/team-a/widgets/*",
+ teamA: "src/team-a/widgets/*"
+ }
+ })
+ ).rejects.toThrow(
+ "Illegal configuration: Overlapping groups: widgetsA, teamA"
+ );
+ });
+ });
});
describe("When traversing files", () => {
@@ -86,6 +174,428 @@ describe("When running Skott using all real dependencies", () => {
});
});
+ describe("When using groups", () => {
+ const fsRootDir = `skott-ignore-temp-fs`;
+
+ const runSandbox = createRealFileSystem(fsRootDir, {
+ "skott-ignore-temp-fs/src/core/index.js": `export const N = 42;`,
+ "skott-ignore-temp-fs/src/features/feature-a/index.js": `import { N } from "../../core"; export const A = N;`,
+ "skott-ignore-temp-fs/src/features/feature-b/index.js": `import { N } from "../../core"; import { A } from "../feature-a"; console.log(A); export const B = N;`,
+ "skott-ignore-temp-fs/src/features/feature-c/a.js": `import { N } from "../../core"; import { A } from "../feature-a"; export { N, A };`,
+ "skott-ignore-temp-fs/src/features/feature-c/b.js": `import { B } from "./src/features/feature-b"; export { B };`,
+ "skott-ignore-temp-fs/src/features/feature-c/c.js": `import { N, A } from "./a"; import { B } from "./b"; export { N, A, B };`,
+ "skott-ignore-temp-fs/src/features/feature-c/index.js": `export { N as CN, A as CA, B as CB } from "./c";`,
+ "skott-ignore-temp-fs/index.js": `import { N } from "./src/core"; import { A } from "./src/features/feature-a"; import { B } from "./src/features/feature-b"; import * as C from "./src/features/feature-c"; console.log(N, A, B, C);`
+ });
+
+ test("Simple string pattern group should work", async () => {
+ expect.assertions(1);
+
+ const skott = new Skott(
+ {
+ ...defaultConfig,
+ groups: {
+ core: "src/core",
+ featureA: "src/features/feature-a",
+ featureB: "src/features/feature-b",
+ featureC: "src/features/feature-c"
+ }
+ },
+ new FileSystemReader({ cwd: fsRootDir, ignorePattern: "" }),
+ new InMemoryFileWriter(),
+ new ModuleWalkerSelector(),
+ new FakeLogger()
+ );
+
+ await runSandbox(async () => {
+ const { groupedGraph } = await skott
+ .initialize()
+ .then(({ getStructure }) => getStructure());
+
+ expect(groupedGraph).toEqual({
+ core: {
+ id: "core",
+ adjacentTo: [],
+ body: {
+ size: 20,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: ["skott-ignore-temp-fs/src/core/index.js"]
+ }
+ },
+ featureA: {
+ id: "featureA",
+ adjacentTo: ["core"],
+ body: {
+ size: 51,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-a/index.js"
+ ]
+ }
+ },
+ featureB: {
+ id: "featureB",
+ adjacentTo: ["core", "featureA"],
+ body: {
+ size: 101,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-b/index.js"
+ ]
+ }
+ },
+ featureC: {
+ id: "featureC",
+ adjacentTo: ["core", "featureA", "featureB"],
+ body: {
+ size: 261,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-c/index.js",
+ "skott-ignore-temp-fs/src/features/feature-c/c.js",
+ "skott-ignore-temp-fs/src/features/feature-c/a.js",
+ "skott-ignore-temp-fs/src/features/feature-c/b.js"
+ ]
+ }
+ }
+ });
+ });
+ });
+
+ test("String pattern with subgroups should work", async () => {
+ expect.assertions(1);
+
+ const skott = new Skott(
+ {
+ ...defaultConfig,
+ groups: {
+ core: "src/core",
+ features: "src/features/*"
+ }
+ },
+ new FileSystemReader({ cwd: fsRootDir, ignorePattern: "" }),
+ new InMemoryFileWriter(),
+ new ModuleWalkerSelector(),
+ new FakeLogger()
+ );
+
+ await runSandbox(async () => {
+ const { groupedGraph } = await skott
+ .initialize()
+ .then(({ getStructure }) => getStructure());
+
+ expect(groupedGraph).toEqual({
+ core: {
+ id: "core",
+ adjacentTo: [],
+ body: {
+ size: 20,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: ["skott-ignore-temp-fs/src/core/index.js"]
+ }
+ },
+ "features/feature-a": {
+ id: "features/feature-a",
+ adjacentTo: ["core"],
+ body: {
+ size: 51,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-a/index.js"
+ ]
+ }
+ },
+ "features/feature-b": {
+ id: "features/feature-b",
+ adjacentTo: ["core", "features/feature-a"],
+ body: {
+ size: 101,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-b/index.js"
+ ]
+ }
+ },
+ "features/feature-c": {
+ id: "features/feature-c",
+ adjacentTo: ["core", "features/feature-a", "features/feature-b"],
+ body: {
+ size: 261,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-c/index.js",
+ "skott-ignore-temp-fs/src/features/feature-c/c.js",
+ "skott-ignore-temp-fs/src/features/feature-c/a.js",
+ "skott-ignore-temp-fs/src/features/feature-c/b.js"
+ ]
+ }
+ }
+ });
+ });
+ });
+
+ describe("Configuration based group", () => {
+ test("Configuration based group should work (base case)", async () => {
+ expect.assertions(1);
+
+ const skott = new Skott(
+ {
+ ...defaultConfig,
+ groups: {
+ core: "src/core",
+ features: {
+ basePath: "src/features",
+ getGroup: (filePath) => filePath.split("/")[3]
+ }
+ }
+ },
+ new FileSystemReader({ cwd: fsRootDir, ignorePattern: "" }),
+ new InMemoryFileWriter(),
+ new ModuleWalkerSelector(),
+ new FakeLogger()
+ );
+
+ await runSandbox(async () => {
+ const { groupedGraph } = await skott
+ .initialize()
+ .then(({ getStructure }) => getStructure());
+
+ expect(groupedGraph).toEqual({
+ core: {
+ id: "core",
+ adjacentTo: [],
+ body: {
+ size: 20,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: ["skott-ignore-temp-fs/src/core/index.js"]
+ }
+ },
+ "features/feature-a": {
+ id: "features/feature-a",
+ adjacentTo: ["core"],
+ body: {
+ size: 51,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-a/index.js"
+ ]
+ }
+ },
+ "features/feature-b": {
+ id: "features/feature-b",
+ adjacentTo: ["core", "features/feature-a"],
+ body: {
+ size: 101,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-b/index.js"
+ ]
+ }
+ },
+ "features/feature-c": {
+ id: "features/feature-c",
+ adjacentTo: [
+ "core",
+ "features/feature-a",
+ "features/feature-b"
+ ],
+ body: {
+ size: 261,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-c/index.js",
+ "skott-ignore-temp-fs/src/features/feature-c/c.js",
+ "skott-ignore-temp-fs/src/features/feature-c/a.js",
+ "skott-ignore-temp-fs/src/features/feature-c/b.js"
+ ]
+ }
+ }
+ });
+ });
+ });
+
+ test("If `getGroup` does not return group name, then node should be filtered out", async () => {
+ expect.assertions(1);
+
+ const skott = new Skott(
+ {
+ ...defaultConfig,
+ groups: {
+ core: "src/core",
+ features: {
+ basePath: "src/features",
+ getGroup: (filePath) => {
+ if (filePath.includes("feature-a")) {
+ return null;
+ }
+
+ return filePath.split("/")[3];
+ }
+ }
+ }
+ },
+ new FileSystemReader({ cwd: fsRootDir, ignorePattern: "" }),
+ new InMemoryFileWriter(),
+ new ModuleWalkerSelector(),
+ new FakeLogger()
+ );
+
+ await runSandbox(async () => {
+ const { groupedGraph } = await skott
+ .initialize()
+ .then(({ getStructure }) => getStructure());
+
+ expect(groupedGraph).toEqual({
+ core: {
+ id: "core",
+ adjacentTo: [],
+ body: {
+ size: 20,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: ["skott-ignore-temp-fs/src/core/index.js"]
+ }
+ },
+ "features/feature-b": {
+ id: "features/feature-b",
+ adjacentTo: ["core"],
+ body: {
+ size: 101,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-b/index.js"
+ ]
+ }
+ },
+ "features/feature-c": {
+ id: "features/feature-c",
+ adjacentTo: ["core", "features/feature-b"],
+ body: {
+ size: 261,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-c/index.js",
+ "skott-ignore-temp-fs/src/features/feature-c/c.js",
+ "skott-ignore-temp-fs/src/features/feature-c/a.js",
+ "skott-ignore-temp-fs/src/features/feature-c/b.js"
+ ]
+ }
+ }
+ });
+ });
+ });
+ });
+
+ test("Combination of different group configurations should work", async () => {
+ expect.assertions(1);
+
+ const skott = new Skott(
+ {
+ ...defaultConfig,
+ groups: {
+ core: "src/core",
+ featureA: "src/features/feature-a",
+ features: {
+ basePath: "src/features",
+ getGroup: (filePath) => {
+ if (filePath.includes("feature-b")) {
+ return null;
+ }
+
+ return filePath.split("/")[3];
+ }
+ },
+ /**
+ * If node is not matched by previous group,
+ * it should be tried against next one
+ */
+ featuresBySubGroupPattern: "src/features/*"
+ }
+ },
+ new FileSystemReader({ cwd: fsRootDir, ignorePattern: "" }),
+ new InMemoryFileWriter(),
+ new ModuleWalkerSelector(),
+ new FakeLogger()
+ );
+
+ await runSandbox(async () => {
+ const { groupedGraph } = await skott
+ .initialize()
+ .then(({ getStructure }) => getStructure());
+
+ expect(groupedGraph).toEqual({
+ core: {
+ id: "core",
+ adjacentTo: [],
+ body: {
+ size: 20,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: ["skott-ignore-temp-fs/src/core/index.js"]
+ }
+ },
+ featureA: {
+ id: "featureA",
+ adjacentTo: ["core"],
+ body: {
+ size: 51,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-a/index.js"
+ ]
+ }
+ },
+ "featuresBySubGroupPattern/feature-b": {
+ id: "featuresBySubGroupPattern/feature-b",
+ adjacentTo: ["core", "featureA"],
+ body: {
+ size: 101,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-b/index.js"
+ ]
+ }
+ },
+ "features/feature-c": {
+ id: "features/feature-c",
+ adjacentTo: [
+ "core",
+ "featureA",
+ "featuresBySubGroupPattern/feature-b"
+ ],
+ body: {
+ size: 261,
+ thirdPartyDependencies: [],
+ builtinDependencies: [],
+ contains: [
+ "skott-ignore-temp-fs/src/features/feature-c/index.js",
+ "skott-ignore-temp-fs/src/features/feature-c/c.js",
+ "skott-ignore-temp-fs/src/features/feature-c/a.js",
+ "skott-ignore-temp-fs/src/features/feature-c/b.js"
+ ]
+ }
+ }
+ });
+ });
+ });
+ });
+
describe("When using ignore pattern", () => {
describe("When running bulk analysis", () => {
test("Should discard files with pattern relative to an absolute directory path", async () => {