Skip to content

Commit

Permalink
feat(core): add ability to add metadata to projects
Browse files Browse the repository at this point in the history
  • Loading branch information
FrozenPandaz committed Mar 13, 2024
1 parent c01b566 commit 5e305a2
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 22 deletions.
14 changes: 14 additions & 0 deletions docs/generated/devkit/ProjectConfiguration.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Project configuration

- [generators](../../devkit/documents/ProjectConfiguration#generators): Object
- [implicitDependencies](../../devkit/documents/ProjectConfiguration#implicitdependencies): string[]
- [metadata](../../devkit/documents/ProjectConfiguration#metadata): Object
- [name](../../devkit/documents/ProjectConfiguration#name): string
- [namedInputs](../../devkit/documents/ProjectConfiguration#namedinputs): Object
- [projectType](../../devkit/documents/ProjectConfiguration#projecttype): ProjectType
Expand Down Expand Up @@ -53,6 +54,19 @@ List of projects which are added as a dependency

---

### metadata

`Optional` **metadata**: `Object`

#### Type declaration

| Name | Type |
| :-------------- | :------------------------------- |
| `targetGroups?` | `Record`\<`string`, `string`[]\> |
| `technologies?` | `string`[] |

---

### name

`Optional` **name**: `string`
Expand Down
8 changes: 8 additions & 0 deletions packages/cypress/src/plugins/plugin.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ describe('@nx/cypress/plugin', () => {
{
"projects": {
".": {
"metadata": {
"targetGroups": {
".:e2e-ci": null,
},
"technologies": [
"cypress",
],
},
"projectType": "application",
"targets": {
"e2e": {
Expand Down
45 changes: 27 additions & 18 deletions packages/cypress/src/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { dirname, join, relative } from 'path';

import { getLockFileName } from '@nx/js';

import { CypressExecutorOptions } from '../executors/cypress/cypress.impl';
import { getNamedInputs } from '@nx/devkit/src/utils/get-named-inputs';
import { existsSync, readdirSync } from 'fs';
import { globWithWorkspaceContext } from 'nx/src/utils/workspace-context';
Expand All @@ -30,24 +29,16 @@ export interface CypressPluginOptions {
const cachePath = join(projectGraphCacheDirectory, 'cypress.hash');
const targetsCache = existsSync(cachePath) ? readTargetsCache() : {};

const calculatedTargets: Record<
string,
Record<string, TargetConfiguration>
> = {};
const calculatedTargets: CypressTargets = {
targets: {},
ciTestingGroup: null,
};

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

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

Expand Down Expand Up @@ -75,7 +66,7 @@ export const createNodes: CreateNodes<CypressPluginOptions> = [
getLockFileName(detectPackageManager(context.workspaceRoot)),
]);

const targets = targetsCache[hash]
const { targets, ciTestingGroup } = targetsCache[hash]
? targetsCache[hash]
: await buildCypressTargets(
configFilePath,
Expand All @@ -91,6 +82,12 @@ export const createNodes: CreateNodes<CypressPluginOptions> = [
[projectRoot]: {
projectType: 'application',
targets,
metadata: {
technologies: ['cypress'],
targetGroups: {
[`${projectRoot}:e2e-ci`]: ciTestingGroup,
},
},
},
},
};
Expand Down Expand Up @@ -145,12 +142,17 @@ function getOutputs(
return outputs;
}

interface CypressTargets {
targets: Record<string, TargetConfiguration>;
ciTestingGroup: string[];
}

async function buildCypressTargets(
configFilePath: string,
projectRoot: string,
options: CypressPluginOptions,
context: CreateNodesContext
) {
): Promise<CypressTargets> {
const cypressConfig = await loadConfigFile(
join(context.workspaceRoot, configFilePath)
);
Expand All @@ -167,6 +169,7 @@ async function buildCypressTargets(
const namedInputs = getNamedInputs(projectRoot, context);

const targets: Record<string, TargetConfiguration> = {};
let ciTestingGroup: string[] = [];

if ('e2e' in cypressConfig) {
targets[options.targetName] = {
Expand Down Expand Up @@ -216,6 +219,8 @@ async function buildCypressTargets(
for (const file of specFiles) {
const relativeSpecFilePath = relative(projectRoot, file);
const targetName = options.ciTargetName + '--' + relativeSpecFilePath;

ciTestingGroup.push(targetName);
targets[targetName] = {
outputs,
inputs,
Expand Down Expand Up @@ -254,7 +259,11 @@ async function buildCypressTargets(
};
}

return targets;
if (ciTestingGroup.length === 0) {
ciTestingGroup = null;
}

return { targets, ciTestingGroup };
}

function normalizeOptions(options: CypressPluginOptions): CypressPluginOptions {
Expand Down
1 change: 1 addition & 0 deletions packages/nx/src/adapter/compat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const allowedProjectExtensions = [
'projectType',
'release',
'includedScripts',
'metadata',
] as const;

// If we pass props on the workspace that angular doesn't know about,
Expand Down
4 changes: 4 additions & 0 deletions packages/nx/src/config/workspace-json-project-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ export interface ProjectConfiguration {
'generator' | 'generatorOptions'
>;
};
metadata?: {
technologies?: string[];
targetGroups?: Record<string, string[]>;
};
}

export interface TargetDependencyConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,90 @@ describe('project-configuration-utils', () => {
`);
});

describe('metadata', () => {
it('should be set if not previously defined', () => {
const rootMap = new RootMapBuilder()
.addProject({
root: 'libs/lib-a',
name: 'lib-a',
})
.getRootMap();
const sourceMap: ConfigurationSourceMaps = {
'libs/lib-a': {},
};
mergeProjectConfigurationIntoRootMap(
rootMap,
{
root: 'libs/lib-a',
name: 'lib-a',
metadata: {
technologies: ['technology'],
},
},
sourceMap,
['dummy', 'dummy.ts']
);

expect(rootMap.get('libs/lib-a').metadata).toEqual({
technologies: ['technology'],
});
expect(sourceMap['libs/lib-a']['metadata.technologies']).toEqual([
'dummy',
'dummy.ts',
]);
expect(sourceMap['libs/lib-a']['metadata.technologies.0']).toEqual([
'dummy',
'dummy.ts',
]);
});

it('should concat arrays', () => {
const rootMap = new RootMapBuilder()
.addProject({
root: 'libs/lib-a',
name: 'lib-a',
metadata: {
technologies: ['technology1'],
},
})
.getRootMap();
const sourceMap: ConfigurationSourceMaps = {
'libs/lib-a': {
'metadata.technologies': ['existing', 'existing.ts'],
'metadata.technologies.0': ['existing', 'existing.ts'],
},
};
mergeProjectConfigurationIntoRootMap(
rootMap,
{
root: 'libs/lib-a',
name: 'lib-a',
metadata: {
technologies: ['technology2'],
},
},
sourceMap,
['dummy', 'dummy.ts']
);

expect(rootMap.get('libs/lib-a').metadata).toEqual({
technologies: ['technology1', 'technology2'],
});
expect(sourceMap['libs/lib-a']['metadata.technologies']).toEqual([
'existing',
'existing.ts',
]);
expect(sourceMap['libs/lib-a']['metadata.technologies.0']).toEqual([
'existing',
'existing.ts',
]);
expect(sourceMap['libs/lib-a']['metadata.technologies.1']).toEqual([
'dummy',
'dummy.ts',
]);
});
});

describe('source map', () => {
it('should add new project info', () => {
const rootMap = new RootMapBuilder().getRootMap();
Expand Down
86 changes: 82 additions & 4 deletions packages/nx/src/project-graph/utils/project-configuration-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,23 @@ export function mergeProjectConfigurationIntoRootMap(
// a project.json in which case it was already updated above.
const updatedProjectConfiguration = {
...matchingProject,
...project,
};

if (sourceMap) {
for (const property in project) {
sourceMap[`${property}`] = sourceInformation;
for (const k in project) {
if (
[
'projectType',
'root',
'name',
'$schema',
'sourceRoot',
'prefix',
].includes(k)
) {
updatedProjectConfiguration[k] = project[k];
if (sourceMap) {
sourceMap[`${k}`] = sourceInformation;
}
}
}

Expand All @@ -76,6 +87,7 @@ export function mergeProjectConfigurationIntoRootMap(
);

if (sourceMap) {
sourceMap['tags'] ??= sourceInformation;
project.tags.forEach((tag) => {
sourceMap[`tags.${tag}`] = sourceInformation;
});
Expand All @@ -88,6 +100,7 @@ export function mergeProjectConfigurationIntoRootMap(
).concat(project.implicitDependencies);

if (sourceMap) {
sourceMap['implicitDependencies'] ??= sourceInformation;
project.implicitDependencies.forEach((implicitDependency) => {
sourceMap[`implicitDependencies.${implicitDependency}`] =
sourceInformation;
Expand All @@ -100,6 +113,7 @@ export function mergeProjectConfigurationIntoRootMap(
updatedProjectConfiguration.generators = { ...project.generators };

if (sourceMap) {
sourceMap['generators'] ??= sourceInformation;
for (const generator in project.generators) {
sourceMap[`generators.${generator}`] = sourceInformation;
for (const property in project.generators[generator]) {
Expand Down Expand Up @@ -127,6 +141,7 @@ export function mergeProjectConfigurationIntoRootMap(
};

if (sourceMap) {
sourceMap['namedInputs'] ??= sourceInformation;
for (const namedInput in project.namedInputs) {
sourceMap[`namedInputs.${namedInput}`] = sourceInformation;
}
Expand All @@ -137,6 +152,9 @@ export function mergeProjectConfigurationIntoRootMap(
// We merge the targets with special handling, so clear this back to the
// targets as defined originally before merging.
updatedProjectConfiguration.targets = matchingProject?.targets ?? {};
if (sourceMap) {
sourceMap['targets'] ??= sourceInformation;
}

// For each target defined in the new config
for (const targetName in project.targets) {
Expand Down Expand Up @@ -176,6 +194,66 @@ export function mergeProjectConfigurationIntoRootMap(
}
}

if (project.metadata) {
if (sourceMap) {
sourceMap['targets'] ??= sourceInformation;
}
for (const [metadataKey, value] of Object.entries({
...project.metadata,
})) {
const existingValue = matchingProject.metadata?.[metadataKey];

if (Array.isArray(value) && Array.isArray(existingValue)) {
for (const item of [...value]) {
const newLength =
updatedProjectConfiguration.metadata[metadataKey].push(item);
if (sourceMap) {
sourceMap[`metadata.${metadataKey}.${newLength - 1}`] =
sourceInformation;
}
}
} else if (Array.isArray(value) && existingValue === undefined) {
updatedProjectConfiguration.metadata ??= {};
updatedProjectConfiguration.metadata[metadataKey] ??= value;
if (sourceMap) {
sourceMap[`metadata.${metadataKey}`] = sourceInformation;
}
for (let i = 0; i < value.length; i++) {
if (sourceMap) {
sourceMap[`metadata.${metadataKey}.${i}`] = sourceInformation;
}
}
} else if (
typeof value === 'object' &&
typeof existingValue === 'object'
) {
for (const key in value) {
const existingValue = matchingProject.metadata?.[metadataKey]?.[key];

if (Array.isArray(value[key]) && Array.isArray(existingValue)) {
for (const item of value[key]) {
const i =
updatedProjectConfiguration.metadata[metadataKey].push(item);
if (sourceMap) {
sourceMap[`metadata.${metadataKey}.${i}`] = sourceInformation;
}
}
} else {
updatedProjectConfiguration.metadata[metadataKey] = value;
if (sourceMap) {
sourceMap[`metadata.${metadataKey}`] = sourceInformation;
}
}
}
} else {
updatedProjectConfiguration.metadata[metadataKey] = value;
if (sourceMap) {
sourceMap[`metadata.${metadataKey}`] = sourceInformation;
}
}
}
}

projectRootMap.set(
updatedProjectConfiguration.root,
updatedProjectConfiguration
Expand Down

0 comments on commit 5e305a2

Please sign in to comment.