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

Adds a monorepo example using Nx #1604

Merged
merged 1 commit into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions nx-monorepo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.nx/cache
generated-website
dist
195 changes: 195 additions & 0 deletions nx-monorepo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
# Nx Monorepo

This example shows how to use Nx to organize a mono repo and track dependencies.

The example consists of the following components:

```
- packages:
- s3folder: ComponentResource that manages a S3 bucket
- website-deploy: ComponentResource resource that manages files in a S3 bucket
- website-builder: Mock website generator that creates HTML output
- infra: Pulumi program that uses the s3folder and website ComponentResources to deploy a website
```

To deploy the latest version of the website, we need to respect the following dependencies:

- website-builder needs to be compiled before we can use it to generate the HTML output.
- s3folder and website-deploy need to be compiled before we can build infra.
- We need to generate HTML output before we can deploy the infra.
- infra needs to be compiled before we can deploy.

These dependecies can be defined using Nx, for example in [infra/package.json](./infra//package.json) we declare that the `deploy` needs its dependencies to be built, and the HTML to generated:

```
...
"nx": {
"targets": {
"deploy": {
"dependsOn": [
"build",
"website-builder:generate"
]
}
}
}
```

Nx can visualize the dependencies for us using `npx nx deploy infra --graph`

![Dependency Graph](./dependency-graph.png)

## Deploying

### Prerequisites

1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/)
2. [Configure AWS Credentials](https://www.pulumi.com/docs/intro/cloud-providers/aws/setup/)

### Steps

Since Nx manages the interdependencies, all we have to do is to install our node dependencies

```bash
npm install
```

and then run nx:

```bash
npx nx deploy infra
```

```
✔ 4/4 dependent project tasks succeeded [0 read from cache]

Hint: you can run the command with --verbose to see the full dependent project outputs

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————


> nx run infra:build


> [email protected] build
> tsc


> nx run infra:deploy


> [email protected] deploy
> pulumi up --stack dev

The stack 'dev' does not exist.

If you would like to create this stack now, please press <ENTER>, otherwise press ^C:
Created stack 'dev'
Previewing update (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/julienp/nx-monorepo/dev/previews/fc7630fd-7dc4-4c7e-baa0-3d6e014fc90a

Type Name Plan
+ pulumi:pulumi:Stack nx-monorepo-dev create
+ ├─ pulumi:examples:WebsiteDeploy my-website create
+ │ └─ aws:s3:BucketObject index.html create
+ └─ pulumi:examples:S3Folder my-folder create
+ ├─ aws:s3:Bucket my-folder create
+ ├─ aws:s3:BucketPublicAccessBlock public-access-block create
+ └─ aws:s3:BucketPolicy bucketPolicy create

Outputs:
websiteUrl: output<string>

Resources:
+ 7 to create

Do you want to perform this update? yes
Updating (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/julienp/nx-monorepo/dev/updates/1

Type Name Status
+ pulumi:pulumi:Stack nx-monorepo-dev created (6s)
+ ├─ pulumi:examples:S3Folder my-folder created (5s)
+ │ ├─ aws:s3:Bucket my-folder created (1s)
+ │ ├─ aws:s3:BucketPublicAccessBlock public-access-block created (0.76s)
+ │ └─ aws:s3:BucketPolicy bucketPolicy created (0.85s)
+ └─ pulumi:examples:WebsiteDeploy my-website created (2s)
+ └─ aws:s3:BucketObject index.html created (0.80s)

Outputs:
websiteUrl: "my-folder-a64ab3c.s3-website.eu-central-1.amazonaws.com"

Resources:
+ 7 created

Duration: 9s


———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

NX Successfully ran target deploy for project infra and 5 tasks it depends on (42s)
```

To destroy the stack, we run:

```
npx nx destroy infra
```

```
> nx run infra:destroy


> [email protected] destroy
> pulumi destroy --stack dev

Previewing destroy (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/julienp/nx-monorepo/dev/previews/b640cce7-a9df-49a5-b004-3fbdbe65c4eb

Type Name Plan
- pulumi:pulumi:Stack nx-monorepo-dev delete
- ├─ pulumi:examples:S3Folder my-folder delete
- │ ├─ aws:s3:BucketPolicy bucketPolicy delete
- │ ├─ aws:s3:BucketPublicAccessBlock public-access-block delete
- │ └─ aws:s3:Bucket my-folder delete
- └─ pulumi:examples:WebsiteDeploy my-website delete
- └─ aws:s3:BucketObject index.html delete

Outputs:
- websiteUrl: "my-folder-a64ab3c.s3-website.eu-central-1.amazonaws.com"

Resources:
- 7 to delete

Do you want to perform this destroy? yes
Destroying (dev)

View in Browser (Ctrl+O): https://app.pulumi.com/julienp/nx-monorepo/dev/updates/2

Type Name Status
- pulumi:pulumi:Stack nx-monorepo-dev deleted (0.31s)
- ├─ pulumi:examples:WebsiteDeploy my-website deleted (0.60s)
- │ └─ aws:s3:BucketObject index.html deleted (0.88s)
- └─ pulumi:examples:S3Folder my-folder deleted (0.82s)
- ├─ aws:s3:BucketPolicy bucketPolicy deleted (0.96s)
- ├─ aws:s3:BucketPublicAccessBlock public-access-block deleted (0.91s)
- └─ aws:s3:Bucket my-folder deleted (0.74s)

Outputs:
- websiteUrl: "my-folder-a64ab3c.s3-website.eu-central-1.amazonaws.com"

Resources:
- 7 deleted

Duration: 7s

The resources in the stack have been deleted, but the history and configuration associated with the stack are still maintained.
If you want to remove the stack completely, run `pulumi stack rm dev`.

———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

NX Successfully ran target destroy for project infra (18s)
```
Binary file added nx-monorepo/dependency-graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions nx-monorepo/infra/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: nx-monorepo
description: A project using an Nx monorepo.
main: dist/index.js
runtime:
name: nodejs
options:
typescript: false
11 changes: 11 additions & 0 deletions nx-monorepo/infra/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as path from "path"
import * as s3folder from "s3folder"
import * as websiteDeploy from "website-deploy"

// Create the folder to hold our website files
const folder = new s3folder.S3Folder("my-folder", {})
export const websiteUrl = folder.websiteUrl

// Deploy the website to the folder
const generatedWebsite = path.join("..", "..", "generated-website")
const website = new websiteDeploy.WebsiteDeploy("my-website", folder.bucket, generatedWebsite, {})
26 changes: 26 additions & 0 deletions nx-monorepo/infra/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "infra",
"main": "dist/index.js",
"version": "1.0.0",
"dependencies": {
"@pulumi/pulumi": "latest",
"s3folder": "*",
"website-deploy": "*",
"website-builder": "*"
},
"scripts": {
"build": "tsc",
"deploy": "pulumi up --stack dev",
"destroy": "pulumi destroy --stack dev"
},
"nx": {
"targets": {
"deploy": {
"dependsOn": [
"build",
"website-builder:generate"
]
}
}
}
}
6 changes: 6 additions & 0 deletions nx-monorepo/infra/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "dist"
}
}
12 changes: 12 additions & 0 deletions nx-monorepo/nx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "nx/presets/npm.json",
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": [
"^build"
]
}
}
}
15 changes: 15 additions & 0 deletions nx-monorepo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "nx-repo",
"version": "1.0.0",
"scripts": {},
"private": true,
"devDependencies": {
"@nx/js": "18.0.5",
"nx": "18.0.5",
"typescript": "^5.3.3"
},
"workspaces": [
"packages/*",
"infra"
]
}
54 changes: 54 additions & 0 deletions nx-monorepo/packages/s3folder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as aws from "@pulumi/aws";
import * as pulumi from "@pulumi/pulumi";

export class S3Folder extends pulumi.ComponentResource {
readonly bucket: pulumi.Output<aws.s3.Bucket>;
readonly websiteUrl: pulumi.Output<string>;

constructor(bucketName: string, opts: pulumi.ComponentResourceOptions) {
super("pulumi:examples:S3Folder", bucketName, {}, opts);

// Create a bucket and expose a website index document
let siteBucket = new aws.s3.Bucket(bucketName, {
website: {
indexDocument: "index.html",
},
}, { parent: this }); // specify resource parent

const publicAccessBlock = new aws.s3.BucketPublicAccessBlock("public-access-block", {
bucket: siteBucket.id,
blockPublicAcls: false,
}, { parent: this });

// Set the access policy for the bucket so all objects are readable
let bucketPolicy = new aws.s3.BucketPolicy("bucketPolicy", {
bucket: siteBucket.bucket,
policy: siteBucket.bucket.apply(this.publicReadPolicyForBucket),
}, { parent: this, dependsOn: publicAccessBlock }); // specify resource parent

this.bucket = pulumi.output(siteBucket);
this.websiteUrl = siteBucket.websiteEndpoint;

// Register output properties for this component
this.registerOutputs({
bucket: this.bucket,
websiteUrl: this.websiteUrl,
});
}

publicReadPolicyForBucket(bucketName: string) {
return JSON.stringify({
Version: "2012-10-17",
Statement: [{
Effect: "Allow",
Principal: "*",
Action: [
"s3:GetObject"
],
Resource: [
`arn:aws:s3:::${bucketName}/*`
]
}]
});
}
}
12 changes: 12 additions & 0 deletions nx-monorepo/packages/s3folder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "s3folder",
"main": "dist/index.js",
"version": "1.0.0",
"dependencies": {
"@pulumi/pulumi": "latest",
"@pulumi/aws": "^6.23.0"
},
"scripts": {
"build": "tsc"
}
}
6 changes: 6 additions & 0 deletions nx-monorepo/packages/s3folder/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "dist"
}
}
9 changes: 9 additions & 0 deletions nx-monorepo/packages/website-builder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

import * as fs from 'fs';
import * as path from "path";

const outDir = path.join("..", "..", "generated-website");
const indexPath = path.join(outDir, "index.html")

fs.mkdirSync(outDir, { recursive: true })
fs.writeFileSync(indexPath, `Hello, world! ${new Date().toISOString()}`)
22 changes: 22 additions & 0 deletions nx-monorepo/packages/website-builder/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "website-builder",
"main": "dist/index.js",
"version": "1.0.0",
"dependencies": {},
"scripts": {
"build": "tsc",
"generate": "node ./dist/index.js"
},
"nx": {
"targets": {
"generate": {
"outputs": [
"{workspaceRoot}/generated-website"
],
"dependsOn": [
"website-builder:build"
]
}
}
}
}
Loading
Loading