Skip to content

Commit

Permalink
feat(workspace-plugin): implement clean executor (#32401)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hotell authored Aug 28, 2024
1 parent ce57deb commit a494e48
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 14 deletions.
2 changes: 1 addition & 1 deletion tools/workspace-plugin/executors.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"clean": {
"implementation": "./src/executors/clean/executor",
"schema": "./src/executors/clean/schema.json",
"description": "clean executor"
"description": "clean executor - remove build artifacts."
}
}
}
91 changes: 88 additions & 3 deletions tools/workspace-plugin/src/executors/clean/executor.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,103 @@
import { ExecutorContext } from '@nx/devkit';
import { ExecutorContext, logger } from '@nx/devkit';

import { CleanExecutorSchema } from './schema';
import executor from './executor';
import { join } from 'node:path';
import { rm } from 'node:fs/promises';
import { mkdirSync, rmSync, writeFileSync, existsSync } from 'node:fs';

jest.mock('node:fs/promises', () => {
return {
...jest.requireActual('node:fs/promises'),
rm: jest.fn(() => Promise.resolve()),
};
});

const options: CleanExecutorSchema = {};
const context: ExecutorContext = {
root: '',
root: join(__dirname, '__fixtures__'),
projectName: 'proj',
projectsConfigurations: {
projects: { proj: { root: 'proj' } },
version: 2,
},
cwd: process.cwd(),
isVerbose: false,
isVerbose: true,
};

const noop = () => {
return;
};
const fixtureRoot = context.root;
const projRoot = join(fixtureRoot, 'proj');

function prepareFixture() {
if (!existsSync(fixtureRoot)) {
mkdirSync(fixtureRoot);
mkdirSync(projRoot);
mkdirSync(join(projRoot, 'dist'));
writeFileSync(join(projRoot, 'dist', 'file.txt'), 'file', 'utf-8');
mkdirSync(join(projRoot, 'lib'));
writeFileSync(join(projRoot, 'lib', 'file.txt'), 'file', 'utf-8');
}
return () => {
rmSync(fixtureRoot, { recursive: true, force: true });
};
}

describe('Clean Executor', () => {
let cleanup = noop;

beforeAll(() => {
cleanup = prepareFixture();
});
afterAll(() => {
cleanup();
});

beforeEach(() => {
jest.spyOn(logger, 'info').mockImplementation(noop);
jest.spyOn(logger, 'error').mockImplementation(noop);
});

it('can run', async () => {
const rmMock = rm as jest.Mock;
const output = await executor(options, context);
expect(output.success).toBe(true);

expect(rmMock.mock.calls.flat()).toEqual([
expect.stringContaining('tools/workspace-plugin/src/executors/clean/__fixtures__/proj/dist'),
{
force: true,
recursive: true,
},
expect.stringContaining('tools/workspace-plugin/src/executors/clean/__fixtures__/proj/lib'),
{
force: true,
recursive: true,
},
]);
});

it('accepts custom paths that override default ones', async () => {
const rmMock = rm as jest.Mock;
mkdirSync(join(projRoot, 'foo-bar'));
mkdirSync(join(projRoot, 'mr-wick'));

const output = await executor({ ...options, paths: ['foo-bar', 'mr-wick'] }, context);
expect(output.success).toBe(true);

expect(rmMock.mock.calls.flat()).toEqual([
expect.stringContaining('tools/workspace-plugin/src/executors/clean/__fixtures__/proj/foo-bar'),
{
force: true,
recursive: true,
},
expect.stringContaining('tools/workspace-plugin/src/executors/clean/__fixtures__/proj/mr-wick'),
{
force: true,
recursive: true,
},
]);
});
});
57 changes: 50 additions & 7 deletions tools/workspace-plugin/src/executors/clean/executor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,54 @@
import { PromiseExecutor } from '@nx/devkit';
import { CleanExecutorSchema } from './schema';
import { type ExecutorContext, type PromiseExecutor, logger } from '@nx/devkit';
import { type CleanExecutorSchema } from './schema';
import { join } from 'node:path';
import { rm } from 'node:fs/promises';
import { existsSync } from 'node:fs';

const runExecutor: PromiseExecutor<CleanExecutorSchema> = async options => {
console.log('Executor ran for Clean', options);
return {
success: true,
};
const runExecutor: PromiseExecutor<CleanExecutorSchema> = async (schema, context) => {
const options = normalizeOptions(schema, context);

const success = await runClean(options, context);

return { success };
};

interface NormalizedOptions extends ReturnType<typeof normalizeOptions> {}

async function runClean(options: NormalizedOptions, context: ExecutorContext): Promise<boolean> {
const projectAbsoluteRootPath = join(context.root, options.project.root);

const results = options.paths.map(dir => {
const dirPath = join(projectAbsoluteRootPath, dir);
if (existsSync(dirPath)) {
verboseLog(`removing "${dirPath}"`);
return rm(dirPath, { force: true, recursive: true });
}
return Promise.resolve();
});

return Promise.all(results)
.then(() => {
return true;
})
.catch(err => {
logger.error(err);
return false;
});
}

export default runExecutor;

function normalizeOptions(schema: CleanExecutorSchema, context: ExecutorContext) {
const defaults = {
paths: ['temp', 'dist', 'dist-storybook', 'storybook-static', 'lib', 'lib-amd', 'lib-commonjs', 'coverage'],
};
const project = context.projectsConfigurations!.projects[context.projectName!];

return { ...defaults, ...schema, project };
}

function verboseLog(message: string, kind: keyof typeof logger = 'info') {
if (process.env.NX_VERBOSE_LOGGING === 'true') {
logger[kind](message);
}
}
7 changes: 6 additions & 1 deletion tools/workspace-plugin/src/executors/clean/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export interface CleanExecutorSchema {} // eslint-disable-line
export interface CleanExecutorSchema {
/**
* Files/Directories to remove (provide relative paths to project root)
*/
paths?: string[];
}
12 changes: 10 additions & 2 deletions tools/workspace-plugin/src/executors/clean/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
"$schema": "https://json-schema.org/schema",
"version": 2,
"title": "Clean executor",
"description": "",
"description": "Remove build artifacts.",
"type": "object",
"properties": {},
"properties": {
"paths": {
"type": "array",
"items": {
"type": "string"
},
"description": "Files/Directories to remove (provide relative paths to project root)"
}
},
"required": []
}

0 comments on commit a494e48

Please sign in to comment.