Skip to content

Commit

Permalink
fix(nx): handle workspace.json with project paths instead of config (#18
Browse files Browse the repository at this point in the history
)

* fix(nx): handle workspace.json with project paths instead of configuration

* feat(nx): prefer configuration from `project.json` files

* test(nx): add tests for handling nx workspace with mixed configuration

* refactor: rename variables, return with spread

* chore: remove `console.log` statements

* fix: check for undefined instead of null

* test(nx): remove asserts for console.log usage
  • Loading branch information
arkus7 authored Aug 9, 2023
1 parent f49cdec commit d4e388e
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "proj1",
"sourceRoot": "projectSourceRoot"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"projects": {
"proj1": {
"name": "proj1",
"sourceRoot": "workspaceSourceRoot"
},
"proj2": {
"name": "proj2"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "proj1"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"projects": {
"proj1": "proj1",
"proj2": {
"name": "proj2"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "proj1"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "proj2"
}
6 changes: 6 additions & 0 deletions libs/nx/src/__fixtures__/nx-workspace-paths/workspace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"projects": {
"proj1": "proj1",
"proj2": "proj2"
}
}
8 changes: 8 additions & 0 deletions libs/nx/src/mocks.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
export const workspaceCwd = 'libs/nx/src/__fixtures__/nx-workspace';
export const projectCwd = 'libs/nx/src/__fixtures__/nx-project';
export const workspaceWithPathsCwd =
'libs/nx/src/__fixtures__/nx-workspace-paths';

export const workspaceMixedCwd = {
noDuplicates: 'libs/nx/src/__fixtures__/nx-workspace-mixed/no-duplicates',
duplicatedConfig:
'libs/nx/src/__fixtures__/nx-workspace-mixed/duplicated-config',
} as const;
94 changes: 88 additions & 6 deletions libs/nx/src/nx.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { getNxProjects, getNxTrueAffectedProjects } from './nx';
import * as globby from 'globby';
import * as fs from 'fs';
import { projectCwd, workspaceCwd } from './mocks';
import {
projectCwd,
workspaceCwd,
workspaceMixedCwd,
workspaceWithPathsCwd,
} from './mocks';

jest.mock('globby', () => ({
globby: jest.fn(),
Expand Down Expand Up @@ -33,6 +38,88 @@ describe('nx', () => {
});
});

describe('nx workspace with a path to projects instead of configuration', () => {
it('should return return projects configuration using nested `project.json` files', async () => {
jest
.spyOn(globby, 'globby')
.mockResolvedValue(['./proj1/project.json', './proj2/project.json']);

const projects = await getNxProjects(workspaceWithPathsCwd);

expect(projects).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'proj1',
project: {
name: 'proj1',
},
}),
expect.objectContaining({
name: 'proj2',
project: {
name: 'proj2',
},
}),
])
);
});
});

describe('nx workspace with mixed configuration and paths', () => {
it('should return both `project.json` and `workspace.json` projects when there are no duplicates', async () => {
jest
.spyOn(globby, 'globby')
.mockResolvedValue(['./proj1/project.json']);

const projects = await getNxProjects(workspaceMixedCwd.noDuplicates);

expect(projects).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'proj1',
project: {
name: 'proj1',
},
}),
expect.objectContaining({
name: 'proj2',
project: {
name: 'proj2',
},
}),
])
);
});

it('should return configuration from `project.json` file when there is a duplicated configuration in `workspace.json` file', async () => {
jest
.spyOn(globby, 'globby')
.mockResolvedValue(['./proj1/project.json']);

const projects = await getNxProjects(
workspaceMixedCwd.duplicatedConfig
);

expect(projects).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'proj1',
project: {
name: 'proj1',
sourceRoot: 'projectSourceRoot',
},
}),
expect.objectContaining({
name: 'proj2',
project: {
name: 'proj2',
},
}),
])
);
});
});

describe('nx workspace with nested project.json', () => {
beforeEach(() => {
jest
Expand Down Expand Up @@ -64,13 +151,8 @@ describe('nx', () => {

describe('no nx workspace and no nested project.json', () => {
it('should return an empty array', async () => {
const logSpy = jest
.spyOn(console, 'log')
.mockImplementationOnce(() => '');

const projects = await getNxProjects('.');

expect(logSpy).toHaveBeenCalled();
expect(projects).toEqual([]);
});
});
Expand Down
99 changes: 60 additions & 39 deletions libs/nx/src/nx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface NxProject {
}

interface WorkspaceJsonConfiguration {
projects: Record<string, NxProject>;
projects: Record<string, NxProject | string>;
}

interface WorkspaceProject {
Expand All @@ -28,51 +28,72 @@ interface WorkspaceProject {
}

export async function getNxProjects(cwd: string): Promise<WorkspaceProject[]> {
const nxProjects = await getNxProjectJsonProjects(cwd);
const workspaceProjects = await getNxWorkspaceProjects(cwd);

const relevantWorkspaceProjects = workspaceProjects.filter(
(proj) =>
nxProjects.find((nested) => nested.name === proj.name) === undefined
);

return [...nxProjects, ...relevantWorkspaceProjects];
}

async function getNxWorkspaceProjects(
cwd: string
): Promise<WorkspaceProject[]> {
try {
const path = resolve(cwd, 'workspace.json');
const file = await readFile(path, 'utf-8');
const workspace = JSON.parse(file) as WorkspaceJsonConfiguration;

return Object.entries(workspace.projects).map(([name, project]) => ({
name,
project,
}));
} catch {
try {
const staticIgnores = ['node_modules', '**/node_modules', 'dist', '.git'];

const projectGlobPatterns: string[] = [`project.json`, `**/project.json`];

const combinedProjectGlobPattern =
'{' + projectGlobPatterns.join(',') + '}';

const files = await globby(combinedProjectGlobPattern, {
ignore: staticIgnores,
ignoreFiles: ['.nxignore'],
absolute: true,
cwd,
dot: true,
suppressErrors: true,
gitignore: true,
});

const projectFiles = [];

for (const file of files) {
const project = JSON.parse(
await readFile(resolve(cwd, file), 'utf-8')
) as NxProject;
projectFiles.push({
name: project.name,
project,
});
}
return Object.entries(workspace.projects)
.filter(([_, project]) => typeof project === 'object')
.map(([name, project]) => ({
name,
project: project as NxProject,
}));
} catch (e) {
return [];
}
}

return projectFiles;
} catch (e) {
console.log(e);
return [];
async function getNxProjectJsonProjects(
cwd: string
): Promise<WorkspaceProject[]> {
try {
const staticIgnores = ['node_modules', '**/node_modules', 'dist', '.git'];

const projectGlobPatterns: string[] = [`project.json`, `**/project.json`];

const combinedProjectGlobPattern =
'{' + projectGlobPatterns.join(',') + '}';

const files = await globby(combinedProjectGlobPattern, {
ignore: staticIgnores,
ignoreFiles: ['.nxignore'],
absolute: true,
cwd,
dot: true,
suppressErrors: true,
gitignore: true,
});

const projectFiles = [];

for (const file of files) {
const project = JSON.parse(
await readFile(resolve(cwd, file), 'utf-8')
) as NxProject;
projectFiles.push({
name: project.name,
project,
});
}

return projectFiles;
} catch (e) {
return [];
}
}

Expand Down

0 comments on commit d4e388e

Please sign in to comment.