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

Enhanced README.md and tested workflow #7

Merged
12 commits merged into from
Mar 4, 2024
2 changes: 1 addition & 1 deletion .github/workflows/build-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::888641773886:role/GitHub
role-to-assume: arn:aws:iam::AWS_ACCOUNT:role/GitHub
role-duration-seconds: 3600 #adjust as needed for your build time
aws-region: us-east-1
- name: Set up Docker Buildx
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy-on-push-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::888641773886:role/GitHub
role-to-assume: arn:aws:iam::AWS_ACCOUNT:role/GitHub
role-duration-seconds: 3600 #adjust as needed for your build time
aws-region: us-east-1
- name: Set up Docker Buildx
Expand Down
41 changes: 31 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ An example full-stack serverless React.js app created with SST.

This sample also includes Amazon CodeCatalyst and GitHub workflow (CD pipelines) to deploy changes of the main branch to AWS automatically. As well as additional checks that can be enabled to protect the main branch, require PR reviews, require build to pass before merge, perform secrets scanning on the repository and prevent secrets from being pushed to GitHub.

This stack assumes that you have deployed the prod SST stage first which includes a relational database. Other non-prod stages refernce that relational database instead of creating their own. See [DBStack.ts](./stacks/DBStack.ts) for more details.

The template was designed this way to be __educational and to demonstrate the use of different stages (prod,dev,test) while keeping a single database for cost saving and simplification__ to be shared between them all. It is important to note that this is not a recommended setup for production uses.

## Steps to get started (one time per repo)

At a high level, these are the steps you need to take to use this template:
Expand Down Expand Up @@ -92,32 +96,43 @@ This template includes two sample workflow definitions under the speical [.githu

Additional checks can also be enabled to protect the main branch, require PR reviews, require build to pass before merge, perform secrets scanning on the repository and prevent secrets from being pushed to GitHub. Details are in the config steps section below.

In addition, the [OIDCForGitHubCI.ts](devops/OIDCForGitHubCI.ts) stack provides an automation that deploys the OIDC identity provider that allows GitHub workflows from your repository to access your AWS account for deployment.
In addition, the [OIDCForGitHubCI.ts](./stacks/devops/OIDCForGitHubCI.ts) stack provides an automation that deploys the OIDC identity provider that allows GitHub workflows from your repository to access your AWS account for deployment. See [SST - Going to Production](https://docs.sst.dev/going-to-production#stacks-setup) if you would like to understnad more about it.

#### Configuration steps for GitHub Workflow

1. Deploy the "devops-gh" stage
1. Pre-requisite for using SST
- Install AWS CDK NPM package

```bash
npm i aws-cdk-lib
```
- Ensure that [Docker](https://docs.docker.com/engine/install/) is installed on your machine

2. Update [OIDCForGitHubCI.ts](./stacks/devops/OIDCForGitHubCI.ts) [line 12](./stacks/devops/OIDCForGitHubCI.ts#L12), and [line 13](./stacks/devops/OIDCForGitHubCI.ts#L13) with your organization/repository and repository name respectively.

2. Deploy the "devops-gh" stage

```bash
npx sst deploy --stage devops-gh
```

This will deploy the Open ID Connect (OIDC) identity provider in your account.

2. Update the `role-to-assume:` and `aws-region:` attributes in both workflow yaml files to use the role created by the deployed stack. If you do not change the default values, the role name will be GitHub, and so only the AWS account ID will need to be updated in the yaml definition.
3. Update the `role-to-assume:` and `aws-region:` attributes in both workflow yaml files to use the role created by the deployed stack. If you do not change the default values, the role name will be GitHub, and so only the AWS account ID will need to be updated in the yaml definition.

```
build-test.yaml
deploy-on-push-main.yaml
```

3. (Re)Trigger a run of the workflows from the `actions` tab to report status checks of each workflow
4. (Re)Trigger a run of the workflows from the `actions` tab to report status checks of each workflow

Create a test/dummy PR to trigger the ci build test workflow.
1. Create a branch and perform some changes
2. Create a test/dummy PR to trigger the ci build test workflow.

This inital run is needed to be able to select the status check as part of the branch protection to prevent PR merges on branches that do not pass the build check (ci).
__This inital run is needed to be able to select the status check as part of the branch protection to prevent PR merges on branches that do not pass the build check (ci).__

4. Enable branch protection, Require Pull Request and Reviews
5. Enable branch protection, Require Pull Request and Reviews

Go to `Settings`>`Branches`>`Add rule`

Expand All @@ -131,9 +146,9 @@ Go to `Settings`>`Branches`>`Add rule`

`Save Changes`

5. Enable secret scanning and push protection
6. Enable secret scanning and push protection

Go to `Settings`>`Branches`>`Add rule`
Go to `Settings`>`Code security and analysis`>

- Secret scanning: Enable
- Push protection: Enable
Expand Down Expand Up @@ -234,4 +249,10 @@ Learn more.

The figure below shows the typical application/feature devlopment workflow that you can follow. Exchange CodeCatalyst with GitHub if that is what you are using.

![Feature Dev LifeCylce](./docs/diagrams/app-dev-lifecycle.drawio.png)
![Feature Dev LifeCylce](./docs/diagrams/app-dev-lifecycle.drawio.png)

## Live Lambda Development

The figure below shows the an SST features called Live Lambda which allows you to debug and test your Lambda functions locally.

![Live Lambda](./docs/diagrams/run-dev.drawio.png)
108 changes: 105 additions & 3 deletions docs/diagrams/app-dev-lifecycle.drawio
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<mxfile host="app.diagrams.net" modified="2024-02-25T17:32:53.417Z" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" version="23.1.6" etag="iWCmcTaLRcN695yvsIS1" type="device">
<diagram name="Page-1" id="aR5tRUA3gesEHeeJ2gO3">
<mxGraphModel dx="2943" dy="1876" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<mxfile host="app.diagrams.net" modified="2024-02-29T13:54:14.533Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" etag="DCdXTlRrwo-2giSba4AQ" version="23.1.6" type="device" pages="2">
<diagram name="app-lifecylce" id="aR5tRUA3gesEHeeJ2gO3">
<mxGraphModel dx="2816" dy="1666" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
Expand Down Expand Up @@ -127,4 +127,106 @@
</root>
</mxGraphModel>
</diagram>
<diagram name="run-dev" id="1JsmGDWWQK1IEeZ22dJO">
<mxGraphModel dx="2816" dy="1666" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="Fzf6os9t-jYp-12unjg--0" />
<mxCell id="Fzf6os9t-jYp-12unjg--1" parent="Fzf6os9t-jYp-12unjg--0" />
<mxCell id="Fzf6os9t-jYp-12unjg--6" value="Developer X" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="290" y="545" width="30" height="60" as="geometry" />
</mxCell>
<mxCell id="Fzf6os9t-jYp-12unjg--35" value="&lt;font style=&quot;font-size: 17px;&quot;&gt;&lt;b&gt;SST Live Lambda&lt;br&gt;&lt;/b&gt;&lt;div style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgb(59 130 246 / .5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; box-sizing: border-box; line-height: 1.5; margin-top: 0px; color: rgb(56, 55, 54); font-family: &amp;quot;Source Sans Pro&amp;quot;, sans-serif; text-align: start; background-color: rgb(255, 255, 255);&quot; class=&quot;text_EKBC&quot;&gt;&lt;p style=&quot;--tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-gradient-from-position: ; --tw-gradient-via-position: ; --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgb(59 130 246 / .5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; box-sizing: border-box; margin: 0 0 var(--ifm-paragraph-margin-bottom);&quot;&gt;SST features a local development environment that lets you debug and test your Lambda functions locally.&lt;/p&gt;&lt;/div&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-470" y="-70" width="820" height="80" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="Fzf6os9t-jYp-12unjg--1" source="UMWOH7P1pfTzg7cVETUd-0" target="UMWOH7P1pfTzg7cVETUd-1">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-0" value="Amazon&amp;nbsp;&lt;br&gt;API Gateway" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.api_gateway;fillColor=#D9A741;gradientColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-623" y="119" width="76.5" height="93" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="Fzf6os9t-jYp-12unjg--1" source="UMWOH7P1pfTzg7cVETUd-1" target="UMWOH7P1pfTzg7cVETUd-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-33" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="Fzf6os9t-jYp-12unjg--1" source="UMWOH7P1pfTzg7cVETUd-1" target="UMWOH7P1pfTzg7cVETUd-0">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="-213" y="90" />
<mxPoint x="-585" y="90" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-1" value="AWS Lambda" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.lambda_function;fillColor=#F58534;gradientColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-247.25" y="129.5" width="69" height="72" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-15" value="" style="group" vertex="1" connectable="0" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="470" y="200" width="250" height="480" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-5" value="" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="UMWOH7P1pfTzg7cVETUd-15">
<mxGeometry width="250" height="480" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-2" value="DynamoDB" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.dynamo_db;fillColor=#2E73B8;gradientColor=none;" vertex="1" parent="UMWOH7P1pfTzg7cVETUd-15">
<mxGeometry x="89" y="50" width="72" height="81" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-3" value="Amazon RDS" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.rds;fillColor=#2E73B8;gradientColor=none;" vertex="1" parent="UMWOH7P1pfTzg7cVETUd-15">
<mxGeometry x="89" y="200" width="72" height="81" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-8" value="Amazon S3" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.s3;fillColor=#E05243;gradientColor=none;" vertex="1" parent="UMWOH7P1pfTzg7cVETUd-15">
<mxGeometry x="86.75" y="347" width="76.5" height="93" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-7" value="&lt;b&gt;&lt;font style=&quot;font-size: 15px;&quot;&gt;Data tier&amp;nbsp;&lt;/font&gt;&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="UMWOH7P1pfTzg7cVETUd-15">
<mxGeometry x="80" y="10" width="90" height="30" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-35" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="Fzf6os9t-jYp-12unjg--1" source="UMWOH7P1pfTzg7cVETUd-17" target="UMWOH7P1pfTzg7cVETUd-5">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="290" y="540" />
<mxPoint x="290" y="440" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-17" value="Developer Laptop&lt;div&gt;&lt;br/&gt;&lt;/div&gt;" style="verticalLabelPosition=bottom;html=1;verticalAlign=top;align=center;strokeColor=none;fillColor=#00BEF2;shape=mxgraph.azure.laptop;pointerEvents=1;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-360" y="510" width="294.5" height="130" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-20" value="Developer editing Lambda functions&lt;br&gt;and running&amp;nbsp;&lt;br&gt;npx sst dev" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry y="575" width="210" height="60" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-21" value="1. Request is sent to your lambda function" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-546.5" y="138" width="250" height="30" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-23" value="2. Lambda function forwards the &lt;br&gt;request to your laptop" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;rotation=-90;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-330" y="340" width="190" height="40" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-24" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.92;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="Fzf6os9t-jYp-12unjg--1" source="Fzf6os9t-jYp-12unjg--6" target="UMWOH7P1pfTzg7cVETUd-17">
<mxGeometry relative="1" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-25" value="3. Laptop runs Lambda code&amp;nbsp;&lt;br&gt;locally and return a response" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-302.75" y="535" width="180" height="40" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-30" value="4. Your laptop will access other resources if needed to fulfill&amp;nbsp;&lt;br&gt;the request" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-60" y="505" width="340" height="40" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="Fzf6os9t-jYp-12unjg--1" source="UMWOH7P1pfTzg7cVETUd-17" target="UMWOH7P1pfTzg7cVETUd-1">
<mxGeometry relative="1" as="geometry">
<Array as="points">
<mxPoint x="-130" y="400" />
<mxPoint x="-45" y="400" />
<mxPoint x="-45" y="166" />
</Array>
</mxGeometry>
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-32" value="5. returned response from your laptop" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;rotation=-90;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-140" y="270" width="220" height="30" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-34" value="6. response returned from AWS Lambda" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-500" y="58" width="240" height="30" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-36" value="&lt;font style=&quot;font-size: 15px;&quot;&gt;&lt;b&gt;Quiz:&lt;br&gt;Think about scenarios where live Lambda would fail during development?&amp;nbsp;&lt;/b&gt;&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-70" y="79.5" width="560" height="50" as="geometry" />
</mxCell>
<mxCell id="UMWOH7P1pfTzg7cVETUd-40" value="" style="fillColor=none;strokeColor=#5A6C86;dashed=1;verticalAlign=top;fontStyle=0;fontColor=#5A6C86;whiteSpace=wrap;html=1;" vertex="1" parent="Fzf6os9t-jYp-12unjg--1">
<mxGeometry x="-770" y="-110" width="1590" height="850" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file added docs/diagrams/run-dev.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading