diff --git a/docs/generated/manifests/menus.json b/docs/generated/manifests/menus.json index 06db5726f620e7..a562fe89e7e69e 100644 --- a/docs/generated/manifests/menus.json +++ b/docs/generated/manifests/menus.json @@ -7626,6 +7626,14 @@ "children": [], "isExternal": false, "disableCollapsible": false + }, + { + "id": "ci-workflow", + "path": "/nx-api/gradle/generators/ci-workflow", + "name": "ci-workflow", + "children": [], + "isExternal": false, + "disableCollapsible": false } ], "isExternal": false, diff --git a/docs/generated/manifests/nx-api.json b/docs/generated/manifests/nx-api.json index ce13570984cbcc..fa8eed5cadfd35 100644 --- a/docs/generated/manifests/nx-api.json +++ b/docs/generated/manifests/nx-api.json @@ -1068,6 +1068,15 @@ "originalFilePath": "/packages/gradle/src/generators/init/schema.json", "path": "/nx-api/gradle/generators/init", "type": "generator" + }, + "/nx-api/gradle/generators/ci-workflow": { + "description": "Setup a CI Workflow to run Nx in CI", + "file": "generated/packages/gradle/generators/ci-workflow.json", + "hidden": false, + "name": "ci-workflow", + "originalFilePath": "/packages/gradle/src/generators/ci-workflow/schema.json", + "path": "/nx-api/gradle/generators/ci-workflow", + "type": "generator" } }, "path": "/nx-api/gradle" diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json index 8e5cab6fd09478..a3cd428162ad71 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.json @@ -1052,6 +1052,15 @@ "originalFilePath": "/packages/gradle/src/generators/init/schema.json", "path": "gradle/generators/init", "type": "generator" + }, + { + "description": "Setup a CI Workflow to run Nx in CI", + "file": "generated/packages/gradle/generators/ci-workflow.json", + "hidden": false, + "name": "ci-workflow", + "originalFilePath": "/packages/gradle/src/generators/ci-workflow/schema.json", + "path": "gradle/generators/ci-workflow", + "type": "generator" } ], "githubRoot": "https://github.com/nrwl/nx/blob/master", diff --git a/docs/generated/packages/gradle/generators/ci-workflow.json b/docs/generated/packages/gradle/generators/ci-workflow.json new file mode 100644 index 00000000000000..c192c1ff626d3b --- /dev/null +++ b/docs/generated/packages/gradle/generators/ci-workflow.json @@ -0,0 +1,42 @@ +{ + "name": "ci-workflow", + "factory": "./src/generators/ci-workflow/generator", + "schema": { + "$schema": "https://json-schema.org/schema", + "$id": "NxGradleCiWorkflowSchema", + "title": "Gradle CI Workflow Generator", + "description": "Sets up a CI Workflow to run Gradle projects", + "type": "object", + "properties": { + "ci": { + "type": "string", + "description": "CI provider.", + "enum": ["github", "circleci"], + "x-prompt": { + "message": "What is your target CI provider?", + "type": "list", + "items": [ + { "value": "github", "label": "GitHub Actions" }, + { "value": "circleci", "label": "Circle CI" } + ] + } + }, + "name": { + "type": "string", + "description": "Workflow name.", + "$default": { "$source": "argv", "index": 0 }, + "default": "CI", + "x-prompt": "How should we name your workflow?", + "pattern": "^[a-zA-Z].*$" + } + }, + "required": ["ci", "name"], + "presets": [] + }, + "description": "Setup a CI Workflow to run Nx in CI", + "implementation": "/packages/gradle/src/generators/ci-workflow/generator.ts", + "aliases": [], + "hidden": false, + "path": "/packages/gradle/src/generators/ci-workflow/schema.json", + "type": "generator" +} diff --git a/docs/shared/reference/sitemap.md b/docs/shared/reference/sitemap.md index 301e17e17966a8..53061ae2ef6fb6 100644 --- a/docs/shared/reference/sitemap.md +++ b/docs/shared/reference/sitemap.md @@ -441,6 +441,7 @@ - [Overview](/nx-api/gradle/documents/overview) - [generators](/nx-api/gradle/generators) - [init](/nx-api/gradle/generators/init) + - [ci-workflow](/nx-api/gradle/generators/ci-workflow) - [jest](/nx-api/jest) - [documents](/nx-api/jest/documents) - [Overview](/nx-api/jest/documents/overview) diff --git a/packages/gradle/generators.json b/packages/gradle/generators.json index fd37905102647f..2370ed9cf8c64d 100644 --- a/packages/gradle/generators.json +++ b/packages/gradle/generators.json @@ -6,6 +6,11 @@ "factory": "./src/generators/init/init#initGenerator", "schema": "./src/generators/init/schema.json", "description": "Initializes a Gradle project in the current workspace" + }, + "ci-workflow": { + "factory": "./src/generators/ci-workflow/generator", + "schema": "./src/generators/ci-workflow/schema.json", + "description": "Setup a CI Workflow to run Nx in CI" } } } diff --git a/packages/gradle/src/generators/ci-workflow/__snapshots__/generator.spec.ts.snap b/packages/gradle/src/generators/ci-workflow/__snapshots__/generator.spec.ts.snap new file mode 100644 index 00000000000000..a90603d45e0b7e --- /dev/null +++ b/packages/gradle/src/generators/ci-workflow/__snapshots__/generator.spec.ts.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ci-workflow generator circleci pipeline should match snapshot 1`] = ` +"version: 2.1 + +orbs: + nx: nrwl/nx@1.6.2 + +jobs: + main: + environment: + # Configure the JVM and Gradle to avoid OOM errors + _JAVA_OPTIONS: '-Xmx3g' + GRADLE_OPTS: '-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2' + docker: + - image: cimg/openjdk:21.0-node + steps: + - checkout + + # Connect your workspace on my.nx.app and uncomment this to enable task distribution. + # The "--stop-agents-after" is optional, but allows idle agents to shut down once the "build" targets have been requested + # - run: npx nx-cloud start-ci-run --distribute-on="5 linux-medium-jvm" --stop-agents-after="build" + + - nx/set-shas: + main-branch-name: 'main' + + # Required for nx affected if we're on a branch + - when: + condition: eq(variables['Build.Reason'], 'PullRequest') + steps: + - run: git branch --track main origin/main + + - run: ./nx affected --base=$NX_BASE --head=$NX_HEAD -t test build + +workflows: + version: 2 + + ci: + jobs: + - main +" +`; + +exports[`ci-workflow generator github pipeline should match snapshot 1`] = ` +"name: CI + +on: + push: + branches: + - main + pull_request: + +permissions: + actions: read + contents: read + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Connect your workspace on my.nx.app and uncomment this to enable task distribution. + # The "--stop-agents-after" is optional, but allows idle agents to shut down once the "build" targets have been requested + # - run: npx nx-cloud start-ci-run --distribute-on="5 linux-medium-jvm" --stop-agents-after="build" + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - uses: nrwl/nx-set-shas@v4 + + - run: git branch --track main origin/main + if: \${{ github.event_name == 'pull_request' }} + + - run: ./nx affected -t test build +" +`; diff --git a/packages/gradle/src/generators/ci-workflow/files/circleci/.circleci/config.yml.template b/packages/gradle/src/generators/ci-workflow/files/circleci/.circleci/config.yml.template new file mode 100644 index 00000000000000..cdab50f243a916 --- /dev/null +++ b/packages/gradle/src/generators/ci-workflow/files/circleci/.circleci/config.yml.template @@ -0,0 +1,37 @@ +version: 2.1 + +orbs: + nx: nrwl/nx@1.6.2 + +jobs: + main: + environment: + # Configure the JVM and Gradle to avoid OOM errors + _JAVA_OPTIONS: "-Xmx3g" + GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.workers.max=2" + docker: + - image: cimg/openjdk:21.0-node + steps: + - checkout + + # Connect your workspace on <%= nxCloudHost %> and uncomment this to enable task distribution. + # The "--stop-agents-after" is optional, but allows idle agents to shut down once the "build" targets have been requested + # - run: <%= packageManagerPrefix %> nx-cloud start-ci-run --distribute-on="5 linux-medium-jvm" --stop-agents-after="build" + + - nx/set-shas: + main-branch-name: '<%= mainBranch %>' + + # Required for nx affected if we're on a branch + - when: + condition: eq(variables['Build.Reason'], 'PullRequest') + steps: + - run: git branch --track main origin/main +<% for (const command of commands) { %> + - run: <%= command %><% } %> + +workflows: + version: 2 + + <%= workflowFileName %>: + jobs: + - main diff --git a/packages/gradle/src/generators/ci-workflow/files/github/.github/workflows/__workflowFileName__.yml.template b/packages/gradle/src/generators/ci-workflow/files/github/.github/workflows/__workflowFileName__.yml.template new file mode 100644 index 00000000000000..595604c115ffae --- /dev/null +++ b/packages/gradle/src/generators/ci-workflow/files/github/.github/workflows/__workflowFileName__.yml.template @@ -0,0 +1,33 @@ +name: <%= workflowName %> + +on: + push: + branches: + - <%= mainBranch %> + pull_request: + +permissions: + actions: read + contents: read + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Connect your workspace on <%= nxCloudHost %> and uncomment this to enable task distribution. + # The "--stop-agents-after" is optional, but allows idle agents to shut down once the "build" targets have been requested + # - run: <%= packageManagerPrefix %> nx-cloud start-ci-run --distribute-on="5 linux-medium-jvm" --stop-agents-after="build" + + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + + - uses: nrwl/nx-set-shas@v4 + + - run: git branch --track main origin/main + if: ${{ github.event_name == 'pull_request' }} +<% for (const command of commands) { %> + - run: <%= command %><% } %> diff --git a/packages/gradle/src/generators/ci-workflow/generator.spec.ts b/packages/gradle/src/generators/ci-workflow/generator.spec.ts new file mode 100644 index 00000000000000..5ab336ce1b7551 --- /dev/null +++ b/packages/gradle/src/generators/ci-workflow/generator.spec.ts @@ -0,0 +1,35 @@ +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import { Tree, updateJson, NxJsonConfiguration } from '@nx/devkit'; + +import { ciWorkflowGenerator } from './generator'; + +describe('ci-workflow generator', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + beforeEach(() => { + updateJson(tree, 'nx.json', (json) => { + return { + ...json, + nxCloudAccessToken: 'xxxx-xxx-xxxx', + nxCloudUrl: 'https://my.nx.app', + }; + }); + }); + + describe.each([ + ['github', '.github/workflows/ci.yml'], + ['circleci', '.circleci/config.yml'], + ] as const)(`%s pipeline`, (ciProvider, output) => { + it('should match snapshot', async () => { + await ciWorkflowGenerator(tree, { + name: 'CI', + ci: ciProvider, + }); + expect(tree.read(output, 'utf-8')).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/gradle/src/generators/ci-workflow/generator.ts b/packages/gradle/src/generators/ci-workflow/generator.ts new file mode 100644 index 00000000000000..f8649ef149b550 --- /dev/null +++ b/packages/gradle/src/generators/ci-workflow/generator.ts @@ -0,0 +1,86 @@ +import { + Tree, + names, + generateFiles, + getPackageManagerCommand, + NxJsonConfiguration, + formatFiles, + detectPackageManager, + readNxJson, +} from '@nx/devkit'; +import { join } from 'path'; +import { getNxCloudUrl, isNxCloudUsed } from 'nx/src/utils/nx-cloud-utils'; +import { deduceDefaultBase } from 'nx/src/utils/default-base'; + +function getCiCommands(ci: Schema['ci'], mainBranch: string): string[] { + switch (ci) { + case 'circleci': { + return [`./nx affected --base=$NX_BASE --head=$NX_HEAD -t test build`]; + } + default: { + return [`./nx affected -t test build`]; + } + } +} + +export interface Schema { + name: string; + ci: 'github' | 'circleci'; + packageManager?: null; + commands?: string[]; +} + +export async function ciWorkflowGenerator(tree: Tree, schema: Schema) { + const ci = schema.ci; + + const nxJson: NxJsonConfiguration = readNxJson(tree); + const nxCloudUsed = isNxCloudUsed(nxJson); + if (!nxCloudUsed) { + throw new Error('This workspace is not connected to Nx Cloud.'); + } + + const options = getTemplateData(schema, nxJson); + generateFiles(tree, join(__dirname, 'files', ci), '', options); + await formatFiles(tree); +} + +interface Substitutes { + mainBranch: string; + workflowName: string; + workflowFileName: string; + packageManager: string; + packageManagerPrefix: string; + commands: string[]; + nxCloudHost: string; +} + +function getTemplateData( + options: Schema, + nxJson: NxJsonConfiguration +): Substitutes { + const { name: workflowName, fileName: workflowFileName } = names( + options.name + ); + const packageManager = detectPackageManager(); + const { exec: packageManagerPrefix } = + getPackageManagerCommand(packageManager); + + const nxCloudUrl = getNxCloudUrl(nxJson); + const nxCloudHost = new URL(nxCloudUrl).host; + + const mainBranch = deduceDefaultBase(); + + const commands = options.commands ?? getCiCommands(options.ci, mainBranch); + + return { + workflowName, + workflowFileName, + packageManager, + packageManagerPrefix, + commands, + mainBranch, + nxCloudHost, + }; +} + +export default ciWorkflowGenerator; diff --git a/packages/gradle/src/generators/ci-workflow/schema.json b/packages/gradle/src/generators/ci-workflow/schema.json new file mode 100644 index 00000000000000..7a15ca445db69d --- /dev/null +++ b/packages/gradle/src/generators/ci-workflow/schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/schema", + "$id": "NxGradleCiWorkflowSchema", + "title": "Gradle CI Workflow Generator", + "description": "Setup a CI Workflow to run Nx in CI.", + "type": "object", + "properties": { + "ci": { + "type": "string", + "description": "CI provider.", + "enum": ["github", "circleci"], + "x-prompt": { + "message": "What is your target CI provider?", + "type": "list", + "items": [ + { + "value": "github", + "label": "GitHub Actions" + }, + { + "value": "circleci", + "label": "Circle CI" + } + ] + } + }, + "name": { + "type": "string", + "description": "Workflow name.", + "$default": { + "$source": "argv", + "index": 0 + }, + "default": "CI", + "x-prompt": "How should we name your workflow?", + "pattern": "^[a-zA-Z].*$" + } + }, + "required": ["ci", "name"] +}