diff --git a/package.json b/package.json index 79433647db837..fbb5c76226189 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "@xstate/immer": "^0.2.0", "@xstate/inspect": "^0.5.1", "@xstate/react": "^1.6.3", + "ajv": "^8.11.0", "angular": "1.8.0", "autoprefixer": "^10.2.5", "babel-jest": "27.5.1", @@ -305,4 +306,4 @@ "**/xmlhttprequest-ssl": "~1.6.2", "minimist": "^1.2.6" } -} \ No newline at end of file +} diff --git a/packages/angular/src/generators/ng-add/utilities/e2e.migrator.spec.ts b/packages/angular/src/generators/ng-add/utilities/e2e.migrator.spec.ts index e47e9c39909e6..5906e811cbb63 100644 --- a/packages/angular/src/generators/ng-add/utilities/e2e.migrator.spec.ts +++ b/packages/angular/src/generators/ng-add/utilities/e2e.migrator.spec.ts @@ -331,6 +331,7 @@ describe('e2e migrator', () => { expect(angularJson.projects['app1-e2e']).toBe('apps/app1-e2e'); const e2eProject = readProjectConfiguration(tree, 'app1-e2e'); expect(e2eProject).toStrictEqual({ + $schema: '../../node_modules/nx/schemas/project-schema.json', root: 'apps/app1-e2e', sourceRoot: 'apps/app1-e2e/src', projectType: 'application', @@ -457,6 +458,7 @@ describe('e2e migrator', () => { expect(angularJson.projects['app1-e2e']).toBe('apps/app1-e2e'); const e2eProject = readProjectConfiguration(tree, 'app1-e2e'); expect(e2eProject).toStrictEqual({ + $schema: '../../node_modules/nx/schemas/project-schema.json', root: 'apps/app1-e2e', sourceRoot: 'apps/app1-e2e/src', projectType: 'application', @@ -521,6 +523,7 @@ describe('e2e migrator', () => { const e2eProject = readProjectConfiguration(tree, 'app1-e2e'); expect(e2eProject).toStrictEqual({ + $schema: '../../node_modules/nx/schemas/project-schema.json', root: 'apps/app1-e2e', sourceRoot: 'apps/app1-e2e/src', projectType: 'application', @@ -573,6 +576,7 @@ describe('e2e migrator', () => { const e2eProject = readProjectConfiguration(tree, 'app1-e2e'); expect(e2eProject).toStrictEqual({ + $schema: '../../node_modules/nx/schemas/project-schema.json', root: 'apps/app1-e2e', sourceRoot: 'apps/app1-e2e/src', projectType: 'application', diff --git a/packages/angular/src/migrations/update-13-9-0/set-build-libs-from-source.spec.ts b/packages/angular/src/migrations/update-13-9-0/set-build-libs-from-source.spec.ts index 74f345e1b4dfa..1b10fe668378c 100644 --- a/packages/angular/src/migrations/update-13-9-0/set-build-libs-from-source.spec.ts +++ b/packages/angular/src/migrations/update-13-9-0/set-build-libs-from-source.spec.ts @@ -27,6 +27,9 @@ describe('set-build-libs-from-source migration', () => { }; addProjectConfiguration(tree, 'app1', project); + // add $schema to projectConfig manually + project['$schema'] = '../../node_modules/nx/schemas/project-schema.json'; + await migration(tree); const resultingProject = readProjectConfiguration(tree, 'app1'); diff --git a/packages/next/src/migrations/update-14-0-0/add-default-development-configurations.spec.ts b/packages/next/src/migrations/update-14-0-0/add-default-development-configurations.spec.ts index 6688550355fdc..bb30ba83601c0 100644 --- a/packages/next/src/migrations/update-14-0-0/add-default-development-configurations.spec.ts +++ b/packages/next/src/migrations/update-14-0-0/add-default-development-configurations.spec.ts @@ -58,6 +58,7 @@ describe('React default development configuration', () => { const config = readProjectConfiguration(tree, 'example'); expect(config).toEqual({ + $schema: '../../node_modules/nx/schemas/project-schema.json', root: 'apps/example', projectType: 'application', }); diff --git a/packages/nx/migrations.json b/packages/nx/migrations.json index da1e1a3474a3d..8b4e0438fa079 100644 --- a/packages/nx/migrations.json +++ b/packages/nx/migrations.json @@ -5,6 +5,12 @@ "version": "14.0.6", "description": "Remove root property from project.json files", "factory": "./src/migrations/update-14-0-6/remove-roots" + }, + "14-2-0-add-json-schema": { + "cli": "nx", + "version": "14.2.0-beta.0", + "description": "Add JSON Schema to Nx configuration files", + "factory": "./src/migrations/update-14-2-0/add-json-schema" } } } diff --git a/packages/nx/migrations.spec.ts b/packages/nx/migrations.spec.ts index 8a0daaa421175..baa1cc284673a 100644 --- a/packages/nx/migrations.spec.ts +++ b/packages/nx/migrations.spec.ts @@ -1,7 +1,7 @@ import path = require('path'); import json = require('./migrations.json'); -describe('Node migrations', () => { +describe('Nx migrations', () => { it('should have valid paths', () => { Object.values(json.generators).forEach((m) => { expect(() => diff --git a/packages/nx/schemas/nx-schema.json b/packages/nx/schemas/nx-schema.json new file mode 100644 index 0000000000000..a6d4921725f56 --- /dev/null +++ b/packages/nx/schemas/nx-schema.json @@ -0,0 +1,127 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://nx.dev/core-concepts/configuration#nxjson", + "title": "JSON schema for Nx configuration", + "type": "object", + "properties": { + "implicitDependencies": { + "type": "object", + "description": "Map of files to projects that implicitly depend on them." + }, + "affected": { + "type": "object", + "description": "Default options for `nx affected`.", + "properties": { + "defaultBase": { + "type": "string", + "description": "Default based branch used by affected commands." + } + }, + "additionalProperties": false + }, + "npmScope": { + "type": "string", + "description": "NPM Scope that the workspace uses." + }, + "tasksRunnerOptions": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/tasksRunnerOptions" + } + }, + "targetDependencies": { + "type": "object", + "description": "Dependencies between different target names across all projects.", + "additionalProperties": { + "$ref": "#/definitions/targetDependencyConfig" + } + }, + "workspaceLayout": { + "type": "object", + "description": "Where new apps + libs should be placed.", + "properties": { + "libsDir": { + "type": "string", + "description": "Default folder name for libs." + }, + "appsDir": { + "type": "string", + "description": "Default folder name for apps." + } + }, + "additionalProperties": false + }, + "cli": { + "$ref": "#/definitions/cliOptions" + }, + "generators": { + "$ref": "#/definitions/generatorOptions" + }, + "plugins": { + "type": "array", + "description": "Plugins for extending the project graph.", + "items": { + "type": "string" + } + }, + "defaultProject": { + "type": "string", + "description": "Default project. When project isn't provided, the default project will be used." + } + }, + "definitions": { + "cliOptions": { + "type": "object", + "description": "Default generator collection.", + "properties": { + "packageManager": { + "type": "string", + "description": "The default package manager to use.", + "enum": ["yarn", "pnpm", "npm"] + }, + "defaultCollection": { + "type": "string", + "description": "The default schematics collection to use." + } + } + }, + "generatorOptions": { + "type": "object", + "description": "List of default values used by generators." + }, + "tasksRunnerOptions": { + "type": "object", + "description": "Available Task Runners.", + "properties": { + "runner": { + "type": "string", + "description": "Path to resolve the runner." + }, + "options": { + "type": "object", + "description": "Default options for the runner." + } + }, + "additionalProperties": false + }, + "targetDependencyConfig": { + "type": "array", + "description": "Target dependency.", + "items": { + "type": "object", + "properties": { + "projects": { + "type": "string", + "description": "The projects that the targets belong to.", + "enum": ["self", "dependencies"] + }, + "target": { + "type": "string", + "description": "The name of the target." + } + }, + "additionalProperties": false + } + } + } +} diff --git a/packages/nx/schemas/project-schema.json b/packages/nx/schemas/project-schema.json new file mode 100644 index 0000000000000..e65afbc92a620 --- /dev/null +++ b/packages/nx/schemas/project-schema.json @@ -0,0 +1,68 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://nx.dev/project-schema", + "title": "JSON schema for Nx projects", + "type": "object", + "properties": { + "targets": { + "type": "object", + "description": "Configures all the targets which define what tasks you can run against the project", + "additionalProperties": { + "type": "object", + "properties": { + "executor": { + "description": "The function that Nx will invoke when you run this target", + "type": "string" + }, + "options": { + "type": "object" + }, + "outputs": { + "type": "array", + "items": { + "type": "string" + } + }, + "configurations": { + "type": "object", + "description": "provides extra sets of values that will be merged into the options map", + "additionalProperties": { + "type": "object" + } + }, + "dependsOn": { + "type": "array", + "description": "Target dependency.", + "items": { + "type": "object", + "properties": { + "projects": { + "type": "string", + "description": "The projects that the targets belong to.", + "enum": ["self", "dependencies"] + }, + "target": { + "type": "string", + "description": "The name of the target." + } + }, + "additionalProperties": false + } + } + } + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "implicitDependencies": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/packages/nx/schemas/workspace-schema.json b/packages/nx/schemas/workspace-schema.json new file mode 100644 index 0000000000000..ae8cfbeec0928 --- /dev/null +++ b/packages/nx/schemas/workspace-schema.json @@ -0,0 +1,134 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://nx.dev", + "title": "JSON schema for Nx workspaces", + "type": "object", + "properties": { + "version": { + "type": "number", + "enum": [1, 2] + } + }, + "allOf": [ + { + "if": { + "properties": { "version": { "const": 2 } }, + "required": ["version"] + }, + "then": { + "properties": { + "projects": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "targets": { + "type": "object", + "description": "Configures all the targets which define what tasks you can run against the project", + "additionalProperties": { + "type": "object", + "properties": { + "executor": { + "description": "The function that Nx will invoke when you run this target", + "type": "string" + }, + "options": { + "type": "object" + }, + "outputs": { + "type": "array", + "items": { + "type": "string" + } + }, + "configurations": { + "type": "object", + "description": "provides extra sets of values that will be merged into the options map", + "additionalProperties": { + "type": "object" + } + }, + "dependsOn": { + "type": "array", + "description": "Target dependency.", + "items": { + "type": "object", + "properties": { + "projects": { + "type": "string", + "description": "The projects that the targets belong to.", + "enum": ["self", "dependencies"] + }, + "target": { + "type": "string", + "description": "The name of the target." + } + }, + "additionalProperties": false + } + } + } + } + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "implicitDependencies": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + ] + } + } + } + }, + "else": { + "properties": { + "projects": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "architect": { + "type": "object", + "description": "Configures all the targets which define what tasks you can run against the project", + "additionalProperties": { + "type": "object", + "properties": { + "builder": { + "description": "The function that Nx will invoke when you run this architect", + "type": "string" + }, + "options": { + "type": "object" + }, + "configurations": { + "type": "object", + "description": "provides extra sets of values that will be merged into the options map", + "additionalProperties": { + "type": "object" + } + } + } + } + } + } + } + } + } + } + } + ] +} diff --git a/packages/nx/src/generators/utils/project-configuration.spec.ts b/packages/nx/src/generators/utils/project-configuration.spec.ts index 378837a4ff246..ba3c14502cd07 100644 --- a/packages/nx/src/generators/utils/project-configuration.spec.ts +++ b/packages/nx/src/generators/utils/project-configuration.spec.ts @@ -1,3 +1,4 @@ +import Ajv from 'ajv'; import { Tree } from '../tree'; import { ProjectConfiguration } from '../../config/workspace-json-project-json'; @@ -15,6 +16,8 @@ import { WorkspaceConfiguration, } from './project-configuration'; +import * as projectSchema from '../../../schemas/project-schema.json'; + type ProjectConfigurationV1 = Pick< ProjectConfiguration, 'root' | 'sourceRoot' @@ -336,9 +339,33 @@ describe('project configuration', () => { addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true); addProjectConfiguration(tree, 'test2', baseTestProjectConfigV2, false); const configurations = getProjects(tree); - expect(configurations.get('test')).toEqual(baseTestProjectConfigV2); + expect(configurations.get('test')).toEqual({ + $schema: '../../node_modules/nx/schemas/project-schema.json', + ...baseTestProjectConfigV2, + }); expect(configurations.get('test2')).toEqual(baseTestProjectConfigV2); }); + + describe('JSON schema', () => { + it('should have JSON $schema in project configuration for standalone projects', () => { + addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true); + const projectJson = readJson(tree, 'libs/test/project.json'); + expect(projectJson['$schema']).toBeTruthy(); + expect(projectJson['$schema']).toEqual( + '../../node_modules/nx/schemas/project-schema.json' + ); + }); + + it('should match project configuration with JSON $schema', () => { + const ajv = new Ajv(); + const validate = ajv.compile(projectSchema); + + addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true); + const projectJson = readJson(tree, 'libs/test/project.json'); + + expect(validate(projectJson)).toEqual(true); + }); + }); }); describe('updateWorkspaceConfiguration', () => { @@ -515,7 +542,10 @@ describe('project configuration', () => { const configurations = getProjects(tree); - expect(configurations.get('test')).toEqual(baseTestProjectConfigV2); + expect(configurations.get('test')).toEqual({ + $schema: '../../node_modules/nx/schemas/project-schema.json', + ...baseTestProjectConfigV2, + }); expect(configurations.get('test2')).toEqual(baseTestProjectConfigV2); }); }); diff --git a/packages/nx/src/generators/utils/project-configuration.ts b/packages/nx/src/generators/utils/project-configuration.ts index 3ed3b6d8062e5..7b6613dc99c39 100644 --- a/packages/nx/src/generators/utils/project-configuration.ts +++ b/packages/nx/src/generators/utils/project-configuration.ts @@ -1,3 +1,10 @@ +import { basename, dirname, join, relative } from 'path'; +import type { NxJsonConfiguration } from '../../config/nx-json'; +import { + ProjectConfiguration, + RawWorkspaceJsonConfiguration, + WorkspaceJsonConfiguration, +} from '../../config/workspace-json-project-json'; import { buildWorkspaceConfigurationFromGlobs, deduplicateProjectFiles, @@ -5,18 +12,11 @@ import { reformattedWorkspaceJsonOrNull, toNewFormat, } from '../../config/workspaces'; -import { basename, dirname, relative } from 'path'; - -import { readJson, updateJson, writeJson } from './json'; +import { joinPathFragments } from '../../utils/path'; import type { Tree } from '../tree'; -import type { NxJsonConfiguration } from '../../config/nx-json'; -import { joinPathFragments } from '../../utils/path'; -import { - ProjectConfiguration, - RawWorkspaceJsonConfiguration, - WorkspaceJsonConfiguration, -} from '../../config/workspace-json-project-json'; + +import { readJson, updateJson, writeJson } from './json'; export type WorkspaceConfiguration = Omit< WorkspaceJsonConfiguration, @@ -314,6 +314,16 @@ function setProjectConfiguration( ); } +export function getRelativeProjectJsonSchemaPath( + tree: Tree, + project: ProjectConfiguration +): string { + return relative( + join(tree.root, project.root), + join(tree.root, 'node_modules/nx/schemas/project-schema.json') + ); +} + function addProjectToWorkspaceJson( tree: Tree, projectName: string, @@ -342,6 +352,10 @@ function addProjectToWorkspaceJson( (mode === 'create' && standalone) || !workspaceConfigPath ? joinPathFragments(project.root, 'project.json') : getProjectFileLocation(tree, projectName); + const jsonSchema = + configFile && mode === 'create' + ? { $schema: getRelativeProjectJsonSchemaPath(tree, project) } + : {}; if (configFile) { if (mode === 'delete') { @@ -352,8 +366,13 @@ function addProjectToWorkspaceJson( if (workspaceConfigPath && mode === 'create') { workspaceJson.projects[projectName] = project.root; } + // update the project.json file - writeJson(tree, configFile, { ...project, root: undefined }); + writeJson(tree, configFile, { + ...jsonSchema, + ...project, + root: undefined, + }); } } else if (mode === 'delete') { delete workspaceJson.projects[projectName]; diff --git a/packages/nx/src/migrations/update-14-2-0/add-json-schema.spec.ts b/packages/nx/src/migrations/update-14-2-0/add-json-schema.spec.ts new file mode 100644 index 0000000000000..d49883b4bf0ff --- /dev/null +++ b/packages/nx/src/migrations/update-14-2-0/add-json-schema.spec.ts @@ -0,0 +1,60 @@ +import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace'; +import type { Tree } from '../../generators/tree'; +import { readJson } from '../../generators/utils/json'; +import { addProjectConfiguration } from '../../generators/utils/project-configuration'; +import addJsonSchema from './add-json-schema'; + +describe('add-json-schema >', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(2); + }); + + it('should update nx.json $schema', async () => { + const nxJson = readJson(tree, 'nx.json'); + delete nxJson['$schema']; + + await addJsonSchema(tree); + expect(readJson(tree, 'nx.json')['$schema']).toEqual( + './node_modules/nx/schemas/nx-schema.json' + ); + }); + + it('should update workspace.json $schema', async () => { + const workspaceJson = readJson(tree, 'workspace.json'); + delete workspaceJson['$schema']; + + await addJsonSchema(tree); + expect(readJson(tree, 'workspace.json')['$schema']).toEqual( + './node_modules/nx/schemas/workspace-schema.json' + ); + }); + + it('should update project.json $schema', async () => { + addProjectConfiguration( + tree, + 'test', + { root: 'libs/test', sourceRoot: 'libs/test/src', targets: {} }, + true + ); + addProjectConfiguration( + tree, + 'test-two', + { + root: 'libs/nested/test-two', + sourceRoot: 'libs/nested/test-two/src', + targets: {}, + }, + true + ); + + await addJsonSchema(tree); + expect(readJson(tree, 'libs/test/project.json')['$schema']).toEqual( + '../../node_modules/nx/schemas/project-schema.json' + ); + expect( + readJson(tree, 'libs/nested/test-two/project.json')['$schema'] + ).toEqual('../../../node_modules/nx/schemas/project-schema.json'); + }); +}); diff --git a/packages/nx/src/migrations/update-14-2-0/add-json-schema.ts b/packages/nx/src/migrations/update-14-2-0/add-json-schema.ts new file mode 100644 index 0000000000000..51e1f2128a5b3 --- /dev/null +++ b/packages/nx/src/migrations/update-14-2-0/add-json-schema.ts @@ -0,0 +1,46 @@ +import { ProjectConfiguration } from '../../config/workspace-json-project-json'; +import type { Tree } from '../../generators/tree'; +import { updateJson } from '../../generators/utils/json'; +import { + getProjects, + getRelativeProjectJsonSchemaPath, + updateProjectConfiguration, +} from '../../generators/utils/project-configuration'; + +export default async function (tree: Tree) { + // update nx.json $schema + const isNxJsonExist = tree.exists('nx.json'); + if (isNxJsonExist) { + updateJson(tree, 'nx.json', (json) => { + if (!json['$schema']) { + json['$schema'] = './node_modules/nx/schemas/nx-schema.json'; + } + return json; + }); + } + + // update workspace.json $schema + const isWorkspaceJsonExist = tree.exists('workspace.json'); + if (isWorkspaceJsonExist) { + updateJson(tree, 'workspace.json', (json) => { + if (!json['$schema']) { + json['$schema'] = './node_modules/nx/schemas/workspace-schema.json'; + } + return json; + }); + } + + // update projects $schema + for (const [projName, projConfig] of getProjects(tree)) { + if (projConfig['$schema']) continue; + + const relativeProjectJsonSchemaPath = getRelativeProjectJsonSchemaPath( + tree, + projConfig + ); + updateProjectConfiguration(tree, projName, { + $schema: relativeProjectJsonSchemaPath, + ...projConfig, + } as ProjectConfiguration); + } +} diff --git a/packages/react/src/migrations/update-14-0-0/add-default-development-configurations.spec.ts b/packages/react/src/migrations/update-14-0-0/add-default-development-configurations.spec.ts index 5e251188a6bad..ae706d57812af 100644 --- a/packages/react/src/migrations/update-14-0-0/add-default-development-configurations.spec.ts +++ b/packages/react/src/migrations/update-14-0-0/add-default-development-configurations.spec.ts @@ -62,6 +62,7 @@ describe('React default development configuration', () => { const config = readProjectConfiguration(tree, 'example'); expect(config).toEqual({ + $schema: '../../node_modules/nx/schemas/project-schema.json', root: 'apps/example', projectType: 'application', }); diff --git a/packages/workspace/src/generators/move/move.spec.ts b/packages/workspace/src/generators/move/move.spec.ts index d13b192dbb3eb..ca9afbc43ca81 100644 --- a/packages/workspace/src/generators/move/move.spec.ts +++ b/packages/workspace/src/generators/move/move.spec.ts @@ -1,4 +1,4 @@ -import { Tree } from '@nrwl/devkit'; +import { readJson, Tree } from '@nrwl/devkit'; import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; import { moveGenerator } from './move'; import { libraryGenerator } from '../library/library'; @@ -6,7 +6,7 @@ import { libraryGenerator } from '../library/library'; describe('move', () => { let tree: Tree; beforeEach(() => { - tree = createTreeWithEmptyWorkspace(); + tree = createTreeWithEmptyWorkspace(2); }); it('should update jest config when moving down directories', async () => { @@ -44,4 +44,25 @@ describe('move', () => { "coverageDirectory: '../../coverage/libs/my-lib-new'" ); }); + + it('should update $schema path when move', async () => { + await libraryGenerator(tree, { name: 'my-lib', standaloneConfig: true }); + + let projectJson = readJson(tree, 'libs/my-lib/project.json'); + expect(projectJson['$schema']).toEqual( + '../../node_modules/nx/schemas/project-schema.json' + ); + + await moveGenerator(tree, { + projectName: 'my-lib', + importPath: '@proj/shared-mylib', + updateImportPath: true, + destination: 'shared/my-lib-new', + }); + + projectJson = readJson(tree, 'libs/shared/my-lib-new/project.json'); + expect(projectJson['$schema']).toEqual( + '../../../node_modules/nx/schemas/project-schema.json' + ); + }); }); diff --git a/packages/workspace/src/generators/new/__snapshots__/new.spec.ts.snap b/packages/workspace/src/generators/new/__snapshots__/new.spec.ts.snap index 3cff60b9c3e3e..c7b61d7543c91 100644 --- a/packages/workspace/src/generators/new/__snapshots__/new.spec.ts.snap +++ b/packages/workspace/src/generators/new/__snapshots__/new.spec.ts.snap @@ -71,6 +71,7 @@ Object { exports[`new should generate an empty nx.json 1`] = ` Object { + "$schema": "./node_modules/nx/schemas/nx-schema.json", "affected": Object { "defaultBase": "main", }, @@ -111,6 +112,7 @@ Object { exports[`new should generate an empty workspace.json 1`] = ` Object { + "$schema": "./node_modules/nx/schemas/workspace-schema.json", "projects": Object {}, "version": 2, } diff --git a/packages/workspace/src/generators/workspace/files/__workspaceFile__.json__tmpl__ b/packages/workspace/src/generators/workspace/files/__workspaceFile__.json__tmpl__ index 3701672da5cc5..2aacd396f2dd6 100644 --- a/packages/workspace/src/generators/workspace/files/__workspaceFile__.json__tmpl__ +++ b/packages/workspace/src/generators/workspace/files/__workspaceFile__.json__tmpl__ @@ -1,4 +1,5 @@ { + "$schema": "./node_modules/nx/schemas/workspace-schema.json", "version": 2, "projects": {} } diff --git a/packages/workspace/src/generators/workspace/files/nx.json__tmpl__ b/packages/workspace/src/generators/workspace/files/nx.json__tmpl__ index 4d1143fb5b201..30e905eb9108d 100644 --- a/packages/workspace/src/generators/workspace/files/nx.json__tmpl__ +++ b/packages/workspace/src/generators/workspace/files/nx.json__tmpl__ @@ -1,4 +1,5 @@ { + "$schema": "./node_modules/nx/schemas/nx-schema.json", "npmScope": "<%= npmScope %>", "affected": { "defaultBase": "<%= defaultBase %>" diff --git a/packages/workspace/src/generators/workspace/workspace.spec.ts b/packages/workspace/src/generators/workspace/workspace.spec.ts index 0bf368779e071..0b07a51d66823 100644 --- a/packages/workspace/src/generators/workspace/workspace.spec.ts +++ b/packages/workspace/src/generators/workspace/workspace.spec.ts @@ -1,8 +1,11 @@ import { readJson } from '@nrwl/devkit'; import type { Tree, NxJsonConfiguration } from '@nrwl/devkit'; +import Ajv from 'ajv'; import { workspaceGenerator } from './workspace'; import { createTree } from '@nrwl/devkit/testing'; import { Preset } from '../utils/presets'; +import * as nxSchema from '../../../../nx/schemas/nx-schema.json'; +import * as workspaceSchema from '../../../../nx/schemas/workspace-schema.json'; describe('@nrwl/workspace:workspace', () => { let tree: Tree; @@ -25,7 +28,9 @@ describe('@nrwl/workspace:workspace', () => { expect(tree.exists('/proj/.prettierignore')).toBe(true); }); - it('should create nx.json', async () => { + it('should create nx.json and workspace.json', async () => { + const ajv = new Ajv(); + await workspaceGenerator(tree, { name: 'proj', directory: 'proj', @@ -35,6 +40,7 @@ describe('@nrwl/workspace:workspace', () => { }); const nxJson = readJson(tree, '/proj/nx.json'); expect(nxJson).toEqual({ + $schema: './node_modules/nx/schemas/nx-schema.json', npmScope: 'proj', affected: { defaultBase: 'main', @@ -66,6 +72,17 @@ describe('@nrwl/workspace:workspace', () => { ], }, }); + const validateNxJson = ajv.compile(nxSchema); + expect(validateNxJson(nxJson)).toEqual(true); + + const workspaceJson = readJson(tree, '/proj/workspace.json'); + expect(workspaceJson).toEqual({ + $schema: './node_modules/nx/schemas/workspace-schema.json', + version: 2, + projects: {}, + }); + const validateWorkspaceJson = ajv.compile(workspaceSchema); + expect(validateWorkspaceJson(workspaceJson)).toEqual(true); }); it('should create a prettierrc file', async () => { diff --git a/packages/workspace/src/migrations/update-13-3-0/update-tsc-executor-location.spec.ts b/packages/workspace/src/migrations/update-13-3-0/update-tsc-executor-location.spec.ts index 60a6b120dafc1..9bc03cd637ee0 100644 --- a/packages/workspace/src/migrations/update-13-3-0/update-tsc-executor-location.spec.ts +++ b/packages/workspace/src/migrations/update-13-3-0/update-tsc-executor-location.spec.ts @@ -43,6 +43,7 @@ describe('add `defaultBase` in nx.json', () => { const projects = Object.fromEntries(getProjects(tree).entries()); expect(projects).toEqual({ 'tsc-project': { + $schema: '../../node_modules/nx/schemas/project-schema.json', root: 'projects/tsc-project', targets: { build: { @@ -54,6 +55,7 @@ describe('add `defaultBase` in nx.json', () => { }, }, 'other-project': { + $schema: '../../node_modules/nx/schemas/project-schema.json', root: 'projects/other-project', targets: { build: { diff --git a/yarn.lock b/yarn.lock index 782116f0dd61a..99f5b27366485 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5878,7 +5878,7 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.3, ajv@^6.12.4, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.8.0: +ajv@^8.0.0, ajv@^8.11.0, ajv@^8.8.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==