Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add CI generation to create-nx-workspace #9611

Merged
merged 12 commits into from
Apr 20, 2022
10 changes: 10 additions & 0 deletions docs/generated/cli/create-nx-workspace.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ Type: string

The name of the application when a preset with pregenerated app is selected

### ci

Type: array

Choices: [github, circleci, azure]

Default: []

Generate a CI workflow file

### cli

Type: string
Expand Down
42 changes: 42 additions & 0 deletions docs/generated/packages/workspace.json
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,48 @@
"aliases": [],
"hidden": false,
"path": "/packages/workspace/src/generators/npm-package/schema.json"
},
{
"name": "ci-workflow",
"factory": "./src/generators/ci-workflow/ci-workflow#ciWorkflowGenerator",
"schema": {
"$schema": "http://json-schema.org/schema",
"$id": "NxWorkspaceNpmPackage",
AgentEnder marked this conversation as resolved.
Show resolved Hide resolved
"title": "Generate a CI workflow.",
"description": "Generate a CI workflow.",
"cli": "nx",
"type": "object",
"properties": {
"ci": {
"type": "string",
"description": "CI provider.",
"enum": ["github", "circleci", "azure"],
"x-prompt": {
"message": "What is your target CI provider?",
"type": "list",
"items": [
{ "value": "github", "label": "Github Actions" },
{ "value": "circleci", "label": "Circle CI" },
{ "value": "azure", "label": "Azure DevOps" }
]
}
},
"name": {
"type": "string",
"description": "Workflow name.",
"$default": { "$source": "argv", "index": 0 },
"x-prompt": "How should we name your workflow?",
"pattern": "^[a-zA-Z].*$"
}
},
"required": ["ci"],
"presets": []
},
"description": "Generete a CI workflow.",
"implementation": "/packages/workspace/src/generators/ci-workflow/ci-workflow#ciWorkflowGenerator.ts",
"aliases": [],
"hidden": false,
"path": "/packages/workspace/src/generators/ci-workflow/schema.json"
}
],
"executors": [
Expand Down
5 changes: 5 additions & 0 deletions docs/map.json
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,11 @@
"id": "workspace-generator",
"path": "/packages/workspace/generators/workspace-generator"
},
{
"name": "ci-workflow generator",
"id": "ci-workflow-generator",
"path": "/packages/workspace/generators/ci-workflow"
},
{
"name": "convert-to-nx-project generator",
"id": "convert-to-nx-project-generator",
Expand Down
3 changes: 2 additions & 1 deletion docs/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@
"workspace-generator",
"run-commands",
"convert-to-nx-project",
"npm-package"
"npm-package",
"ci-workflow"
]
}
}
Expand Down
17 changes: 11 additions & 6 deletions docs/shared/monorepo-ci-azure.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ variables:
value: $(git merge-base $(TARGET_BRANCH) HEAD)
${{ if ne(variables['Build.Reason'], 'PullRequest') }}:
value: $(git rev-parse HEAD~1)
- name: HEAD_SHA
value: $(git rev-parse HEAD)

jobs:
- job: main
Expand Down Expand Up @@ -88,12 +90,13 @@ variables:
value: $(git merge-base $(TARGET_BRANCH) HEAD)
${{ if ne(variables['Build.Reason'], 'PullRequest') }}:
value: $(git rev-parse HEAD~1)
- name: HEAD_SHA
value: $(git rev-parse HEAD)

jobs:
- job: agents
strategy:
matrix:
agent: [1, 2, 3]
parallel: 3
displayName: 'Agent $(imageName)'
pool:
vmImage: 'ubuntu-latest'
Expand All @@ -109,13 +112,15 @@ jobs:
- script: npx nx-cloud start-ci-run

- script: npx nx workspace-lint
- script: npx nx format:check
- script: npx nx affected --base=$(BASE_SHA) --target=lint --parallel=3
- script: npx nx affected --base=$(BASE_SHA) --target=test --parallel=3 --ci --code-coverage
- script: npx nx affected --base=$(BASE_SHA) --target=build --parallel=3
- script: npx nx format:check --base=$(BASE_SHA) --head=$(HEAD_SHA)
- script: npx nx affected --base=$(BASE_SHA) --head=$(HEAD_SHA) --target=lint --parallel=3
- script: npx nx affected --base=$(BASE_SHA) --head=$(HEAD_SHA) --target=test --parallel=3 --ci --code-coverage
- script: npx nx affected --base=$(BASE_SHA) --head=$(HEAD_SHA) --target=build --parallel=3

- script: npx nx-cloud stop-all-agents
condition: always()
```

You can also use our [ci-workflow generator](https://nx.app/packages/workspace/generators/ci-workflow) to generate the pipeline file.

Learn more about [configuring your CI](https://nx.app/docs/configuring-ci) environment using Nx Cloud with [Distributed Caching](https://nx.app/docs/distributed-caching) and [Distributed Task Execution](https://nx.app/docs/distributed-execution) in the Nx Cloud docs.
3 changes: 3 additions & 0 deletions docs/shared/monorepo-ci-circle-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ jobs:
- run: npx nx affected --base=$NX_BASE --head=$NX_HEAD --target=build --parallel=3

- run: npx nx-cloud stop-all-agents
when: always
workflows:
build:
jobs:
Expand All @@ -106,4 +107,6 @@ workflows:
- main
```

You can also use our [ci-workflow generator](https://nx.app/packages/workspace/generators/ci-workflow) to generate the configuration file.

Learn more about [configuring your CI](https://nx.app/docs/configuring-ci) environment using Nx Cloud with [Distributed Caching](https://nx.app/docs/distributed-caching) and [Distributed Task Execution](https://nx.app/docs/distributed-execution) in the Nx Cloud docs.
2 changes: 2 additions & 0 deletions docs/shared/monorepo-ci-github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,6 @@ jobs:
number-of-agents: 3
```

You can also use our [ci-workflow generator](https://nx.app/packages/workspace/generators/ci-workflow) to generate the workflow file.

Learn more about [configuring your CI](https://nx.app/docs/configuring-ci) environment using Nx Cloud with [Distributed Caching](https://nx.app/docs/distributed-caching) and [Distributed Task Execution](https://nx.app/docs/distributed-execution) in the Nx Cloud docs.
1 change: 1 addition & 0 deletions packages/create-nx-workspace/bin/ci.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ciList = ['github', 'circleci', 'azure'] as const;
161 changes: 133 additions & 28 deletions packages/create-nx-workspace/bin/create-nx-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { deduceDefaultBase } from './default-base';
import { stringifyCollection } from './utils';
import { yargsDecorator } from './decorator';
import chalk = require('chalk');
import { ciList } from './ci';

type Arguments = {
name: string;
Expand All @@ -30,6 +31,7 @@ type Arguments = {
allPrompts: boolean;
packageManager: string;
defaultBase: string;
ci: string[];
};

enum Preset {
Expand Down Expand Up @@ -162,6 +164,12 @@ export const commandsObject: yargs.Argv<Arguments> = yargs
describe: chalk.dim`Use Nx Cloud`,
type: 'boolean',
})
.option('ci', {
describe: `Generate a CI workflow file`,
choices: ciList,
defaultDescription: '[]',
type: 'array',
})
.option('allPrompts', {
alias: 'a',
describe: chalk.dim`Show all prompts`,
Expand Down Expand Up @@ -209,6 +217,7 @@ async function main(parsedArgs: yargs.Arguments<Arguments>) {
nxCloud,
packageManager,
defaultBase,
ci,
} = parsedArgs;

output.log({
Expand Down Expand Up @@ -238,6 +247,14 @@ async function main(parsedArgs: yargs.Arguments<Arguments>) {
packageManager as PackageManager
);
}
if (ci && ci.length) {
await setupCI(
name,
ci,
packageManager as PackageManager,
nxCloud && nxCloudInstallRes.code === 0
);
}

showNxWarning(name);
pointToTutorialAndCourse(preset as Preset);
Expand Down Expand Up @@ -266,6 +283,7 @@ async function getConfiguration(
const packageManager = await determinePackageManager(argv);
const defaultBase = await determineDefaultBase(argv);
const nxCloud = await determineNxCloud(argv);
const ci = await determineCI(argv, nxCloud);

Object.assign(argv, {
name,
Expand All @@ -276,6 +294,7 @@ async function getConfiguration(
nxCloud,
packageManager,
defaultBase,
ci,
});
} catch (e) {
console.error(e);
Expand Down Expand Up @@ -517,7 +536,7 @@ async function determineCli(
async function determineStyle(
preset: Preset,
parsedArgs: yargs.Arguments<Arguments>
) {
): Promise<string> {
if (
preset === Preset.Apps ||
preset === Preset.Core ||
Expand Down Expand Up @@ -605,6 +624,75 @@ async function determineStyle(
return Promise.resolve(parsedArgs.style);
}

async function determineNxCloud(
parsedArgs: yargs.Arguments<Arguments>
): Promise<boolean> {
if (parsedArgs.nxCloud === undefined) {
return enquirer
.prompt([
{
name: 'NxCloud',
message: `Use Nx Cloud? (It's free and doesn't require registration.)`,
type: 'select',
choices: [
{
name: 'Yes',
hint: 'Faster builds, run details, GitHub integration. Learn more at https://nx.app',
},

{
name: 'No',
},
],
initial: 'Yes' as any,
},
])
.then((a: { NxCloud: 'Yes' | 'No' }) => a.NxCloud === 'Yes');
} else {
return parsedArgs.nxCloud;
}
}

async function determineCI(
parsedArgs: yargs.Arguments<Arguments>,
nxCloud: boolean
): Promise<string[]> {
if (!nxCloud) {
if (parsedArgs.ci && parsedArgs.ci.length > 0) {
output.warn({
title: 'Invalid CI value',
bodyLines: [
`CI option only works when Nx Cloud is enabled.`,
`The value provided will be ignored.`,
],
});
}
return [];
}

if (parsedArgs.ci) {
return parsedArgs.ci;
}

if (parsedArgs.allPrompts) {
return enquirer
.prompt([
{
name: 'CI',
message: `Autogenerate CI workflow file (multi-select)?`,
type: 'multiselect',
choices: [
{ message: 'GitHub Actions', name: 'github' },
{ message: 'Circle CI', name: 'circleci' },
{ message: 'Azure DevOps', name: 'azure' },
],
AgentEnder marked this conversation as resolved.
Show resolved Hide resolved
},
])
.then((a: { CI: string[] }) => a.CI);
}
return [];
}

async function createSandbox(packageManager: string) {
const installSpinner = ora(
`Installing dependencies with ${packageManager}`
Expand Down Expand Up @@ -720,6 +808,50 @@ async function setupNxCloud(name: string, packageManager: PackageManager) {
}
}

async function setupCI(
name: string,
ci: string[],
packageManager: PackageManager,
nxCloudSuccessfullyInstalled: boolean
) {
if (!nxCloudSuccessfullyInstalled) {
output.error({
title: `CI workflow(s) generation skipped`,
bodyLines: [
`Nx Cloud was not (successfully) installed`,
`The autogenerated CI workflows require Nx Cloud to be set-up.`,
],
});
}
const ciSpinner = ora(`Generating CI workflow(s)`).start();
try {
const pmc = getPackageManagerCommand(packageManager);
// GENERATE WORKFLOWS HERE based on `ci` and `packageManager`
const res = Promise.allSettled(
ci.map(
async (provider) =>
await execAndWait(
`${pmc.exec} nx g @nrwl/workspace:ci-workflow --ci=${provider}`,
path.join(process.cwd(), name)
)
)
);
ciSpinner.succeed('CI workflow(s) have been generated successfully');
return res;
} catch (e) {
ciSpinner.fail();

output.error({
title: `Nx failed to generate CI workflow(s)`,
bodyLines: mapErrorToBodyLines(e),
});

process.exit(1);
} finally {
ciSpinner.stop();
}
}

function printNxCloudSuccessMessage(nxCloudOut: string) {
const bodyLines = nxCloudOut.split('Nx Cloud has been enabled')[1].trim();
output.note({
Expand Down Expand Up @@ -758,33 +890,6 @@ function execAndWait(command: string, cwd: string) {
});
}

async function determineNxCloud(parsedArgs: yargs.Arguments<Arguments>) {
if (parsedArgs.nxCloud === undefined) {
return enquirer
.prompt([
{
name: 'NxCloud',
message: `Use Nx Cloud? (It's free and doesn't require registration.)`,
type: 'select',
choices: [
{
name: 'Yes',
hint: 'Faster builds, run details, GitHub integration. Learn more at https://nx.app',
},

{
name: 'No',
},
],
initial: 'Yes' as any,
},
])
.then((a: { NxCloud: 'Yes' | 'No' }) => a.NxCloud === 'Yes');
} else {
return parsedArgs.nxCloud;
}
}

function pointToTutorialAndCourse(preset: Preset) {
const title = `First time using Nx? Check out this interactive Nx tutorial.`;
switch (preset) {
Expand Down