Skip to content

Commit

Permalink
feat(plugin): add createNodesV2
Browse files Browse the repository at this point in the history
  • Loading branch information
Phillip9587 committed Sep 24, 2024
1 parent e5b0b8f commit b9086a1
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 95 deletions.
2 changes: 1 addition & 1 deletion nx-stylelint/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@nx/dependency-checks": [
"error",
{
"ignoredDependencies": ["stylelint-config-standard", "stylelint-config-standard-scss"]
"ignoredDependencies": ["stylelint-config-standard", "stylelint-config-standard-scss", "nx"]
}
]
}
Expand Down
2 changes: 1 addition & 1 deletion nx-stylelint/plugin.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { createNodes, createDependencies, type StylelintPluginOptions } from './src/plugins/plugin';
export { createNodes, createNodesV2, type StylelintPluginOptions } from './src/plugins/plugin';
69 changes: 42 additions & 27 deletions nx-stylelint/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CreateNodesContext } from '@nx/devkit';
import { vol } from 'memfs';
import { createNodes } from './plugin';
import { createNodesV2 } from './plugin';

jest.mock('node:fs', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand All @@ -16,7 +16,7 @@ jest.mock('fs/promises', () => {
});

describe('nx-stylelint/plugin', () => {
const createNodesFunction = createNodes[1];
const createNodesFunction = createNodesV2[1];
let context: CreateNodesContext;

beforeEach(async () => {
Expand Down Expand Up @@ -59,37 +59,52 @@ describe('nx-stylelint/plugin', () => {
''
);

const nodes = await createNodesFunction('apps/my-app/.stylelintrc.json', {}, context);
const nodes = await createNodesFunction(['apps/my-app/.stylelintrc.json'], {}, context);

expect(nodes).toMatchInlineSnapshot(`
{
"projects": {
"apps/my-app": {
"root": "apps/my-app",
"targets": {
"stylelint": {
"cache": true,
"command": "stylelint "**/*.css"",
"inputs": [
"default",
"^default",
"{projectRoot}/.stylelintrc.json",
"{workspaceRoot}/.stylelintrc.json",
"{workspaceRoot}/apps/.stylelintrc.yaml",
{
"externalDependencies": [
"stylelint",
[
[
"apps/my-app/.stylelintrc.json",
{
"projects": {
"apps/my-app": {
"root": "apps/my-app",
"targets": {
"stylelint": {
"cache": true,
"command": "stylelint "**/*.css"",
"inputs": [
"default",
"^default",
"{projectRoot}/.stylelintrc.json",
"{workspaceRoot}/.stylelintrc.json",
"{workspaceRoot}/apps/.stylelintrc.yaml",
{
"externalDependencies": [
"stylelint",
],
},
],
"metadata": {
"description": "Runs Stylelint on project",
"help": {
"command": "npx stylelint --help",
"example": {},
},
"technologies": [
"stylelint",
],
},
"options": {
"cwd": "apps/my-app",
},
},
],
"options": {
"cwd": "apps/my-app",
},
},
},
},
},
}
],
]
`);
});

Expand All @@ -105,8 +120,8 @@ describe('nx-stylelint/plugin', () => {
''
);

const nodes = await createNodesFunction('apps/my-app/.stylelintrc.json', {}, context);
const nodes = await createNodesFunction(['apps/my-app/.stylelintrc.json'], {}, context);

expect(nodes).toStrictEqual({});
expect(nodes).toStrictEqual([['apps/my-app/.stylelintrc.json', {}]]);
});
});
165 changes: 99 additions & 66 deletions nx-stylelint/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,96 +1,134 @@
import {
CreateDependencies,
CreateNodes,
CreateNodesContext,
CreateNodesV2,
ProjectConfiguration,
TargetConfiguration,
cacheDir,
createNodesFromFiles,
getPackageManagerCommand,
logger,
readJsonFile,
writeJsonFile,
} from '@nx/devkit';
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
import { existsSync } from 'node:fs';
import { existsSync, readdirSync } from 'node:fs';
import * as nodePath from 'node:path';
import { getInputConfigFiles } from '../utils/config-file';
import { hashObject } from 'nx/src/hasher/file-hasher';
import { workspaceDataDirectory } from 'nx/src/utils/cache-directory';

const pmc = getPackageManagerCommand();

const STYLELINT_CONFIG_FILES_GLOB =
'**/{.stylelintrc,.stylelintrc.{json,yml,yaml,js,cjs,mjs,ts,cts,mts},stylelint.config.{js,cjs,mjs,ts,cts,mts}}';

export interface StylelintPluginOptions {
targetName?: string;
extensions?: string[];
}

const cachePath = nodePath.join(cacheDir, 'stylelint.hash');
const targetsCache = existsSync(cachePath) ? readTargetsCache() : {};

const calculatedTargets: Record<string, Record<string, TargetConfiguration>> = {};

function readTargetsCache(): Record<string, Record<string, TargetConfiguration>> {
return readJsonFile(cachePath);
function readTargetsCache(cachePath: string): Record<string, TargetConfiguration> {
return existsSync(cachePath) ? readJsonFile(cachePath) : {};
}

function writeTargetsToCache(targets: Record<string, Record<string, TargetConfiguration>>) {
writeJsonFile(cachePath, targets);
function writeTargetsToCache(cachePath: string, results: Record<string, TargetConfiguration>) {
writeJsonFile(cachePath, results);
}

export const createDependencies: CreateDependencies = () => {
writeTargetsToCache(calculatedTargets);
return [];
};
export const createNodesV2: CreateNodesV2<StylelintPluginOptions> = [
STYLELINT_CONFIG_FILES_GLOB,
async (projectConfigurationFiles, options, context) => {
const optionsHash = hashObject(options ?? {});
const cachePath = nodePath.join(workspaceDataDirectory, `jest-${optionsHash}.hash`);
const targetsCache = readTargetsCache(cachePath);

try {
return await createNodesFromFiles(
(configFile, options, context) => createNodesInternal(configFile, options, context, targetsCache),
projectConfigurationFiles,
options,
context
);
} finally {
writeTargetsToCache(cachePath, targetsCache);
}
},
];

export const createNodes: CreateNodes<StylelintPluginOptions> = [
'**/.stylelintrc.{json,yml,yaml,js,cjs,mjs,ts}',
STYLELINT_CONFIG_FILES_GLOB,
async (configFilePath, options, context) => {
const projectRoot = nodePath.dirname(configFilePath);

// Do not create a project if package.json and project.json isn't there.
if (
!existsSync(nodePath.join(context.workspaceRoot, projectRoot, 'package.json')) &&
!existsSync(nodePath.join(context.workspaceRoot, projectRoot, 'project.json'))
)
return {};

const isStandaloneWorkspace =
projectRoot === '.' &&
existsSync(nodePath.join(context.workspaceRoot, projectRoot, 'src')) &&
existsSync(nodePath.join(context.workspaceRoot, projectRoot, 'package.json'));

if (projectRoot === '.' && !isStandaloneWorkspace) return {};

const normalizedOptions = normalizeOptions(options);
const hash = await calculateHashForCreateNodes(projectRoot, normalizedOptions, context);
const targets = targetsCache[hash] ?? (await buildStylelintTargets(configFilePath, projectRoot, normalizedOptions));

calculatedTargets[hash] = targets;

return {
projects: {
[projectRoot]: {
root: projectRoot,
targets: targets,
},
},
};
logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);
return createNodesInternal(configFilePath, options, context, {});
},
];

async function buildStylelintTargets(
async function createNodesInternal(
configFilePath: string,
options: StylelintPluginOptions | undefined,
context: CreateNodesContext,
targetsCache: Record<string, TargetConfiguration>
) {
const projectRoot = nodePath.dirname(configFilePath);
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = readdirSync(nodePath.join(context.workspaceRoot, projectRoot));
if (!siblingFiles.includes('package.json') && !siblingFiles.includes('project.json')) {
return {};
}

const normalizedOptions = normalizeOptions(options);

const hash = (await calculateHashForCreateNodes(projectRoot, normalizedOptions, context)) + configFilePath;

targetsCache[hash] ??= await stylelintTarget(configFilePath, projectRoot, normalizedOptions);
const target = targetsCache[hash];

const project: ProjectConfiguration = {
root: projectRoot,
targets: {
[normalizedOptions.targetName]: target,
},
};

return {
projects: {
[projectRoot]: project,
},
};
}

async function stylelintTarget(
configFilePath: string,
projectRoot: string,
options: Required<StylelintPluginOptions>
): Promise<Record<string, TargetConfiguration>> {
): Promise<TargetConfiguration> {
const inputConfigFiles = await getInputConfigFiles(configFilePath, projectRoot);

const glob =
options.extensions.length === 1 ? `**/*.${options.extensions[0]}` : `**/*.{${options.extensions.join(',')}}`;

return {
[options.targetName]: {
command: `stylelint "${getLintFileGlob(options.extensions)}"`,
cache: true,
options: {
cwd: projectRoot,
command: `stylelint "${glob}"`,
cache: true,
options: {
cwd: projectRoot,
},
inputs: [
'default',
// Certain lint rules can be impacted by changes to dependencies
'^default',
...inputConfigFiles,
{ externalDependencies: ['stylelint'] },
],
metadata: {
technologies: ['stylelint'],
description: 'Runs Stylelint on project',
help: {
command: `${pmc.exec} stylelint --help`,
example: {},
},
inputs: [
'default',
// Certain lint rules can be impacted by changes to dependencies
'^default',
...inputConfigFiles,
{ externalDependencies: ['stylelint'] },
],
},
};
}
Expand All @@ -107,8 +145,3 @@ function normalizeOptions(options: StylelintPluginOptions | undefined): Required
extensions: extensions ?? ['css'],
};
}

function getLintFileGlob(extensions: string[]): string {
if (extensions.length === 1) return `**/*.${extensions[0]}`;
return `**/*.{${extensions.join(',')}}`;
}
4 changes: 4 additions & 0 deletions nx-stylelint/src/utils/globs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function combineGlobPatterns(...patterns: (string | string[])[]) {
const p = patterns.flat();
return p.length > 1 ? '{' + p.join(',') + '}' : p.length === 1 ? p[0] : '';
}

0 comments on commit b9086a1

Please sign in to comment.