Skip to content

Commit

Permalink
Adds a monorepo example using Nx
Browse files Browse the repository at this point in the history
  • Loading branch information
julienp committed Mar 1, 2024
1 parent 267aba1 commit f5c660d
Show file tree
Hide file tree
Showing 19 changed files with 439 additions and 0 deletions.
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
> infra@1.0.0 build
> tsc
> nx run infra:deploy
> infra@1.0.0 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
> infra@1.0.0 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

0 comments on commit f5c660d

Please sign in to comment.