Skip to content

Commit f3ca2ae

Browse files
committed
feat: add github action ci workflow
1 parent 62924ee commit f3ca2ae

File tree

8 files changed

+239
-2
lines changed

8 files changed

+239
-2
lines changed

.github/workflows/ci.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: CI/CD Angular + Docker + Vercel
2+
3+
on:
4+
push:
5+
branches:
6+
- develop
7+
- release-*
8+
- master
9+
10+
env:
11+
NODE_VERSION: 24
12+
APP_NAME: angular-bb
13+
14+
jobs:
15+
lint:
16+
name: Lint Code
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: 1. Checkout Source
20+
uses: actions/checkout@v4
21+
22+
- name: 2. Setup Node.js
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: ${{ env.NODE_VERSION }}
26+
27+
- name: 3. Cache npm
28+
uses: actions/cache@v4
29+
with:
30+
path: ~/.npm
31+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
32+
restore-keys: |
33+
${{ runner.os }}-node-
34+
35+
- name: 4. Install Dependencies
36+
run: npm ci
37+
38+
- name: 5. Run Linter
39+
run: npm run lint
40+
41+
test:
42+
name: Run Unit Tests
43+
runs-on: ubuntu-latest
44+
steps:
45+
- name: 1. Checkout Source
46+
uses: actions/checkout@v4
47+
48+
- name: 2. Setup Node.js
49+
uses: actions/setup-node@v4
50+
with:
51+
node-version: ${{ env.NODE_VERSION }}
52+
53+
- name: 3. Cache npm
54+
uses: actions/cache@v4
55+
with:
56+
path: ~/.npm
57+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
58+
restore-keys: |
59+
${{ runner.os }}-node-
60+
61+
- name: 4. Install Dependencies
62+
run: npm ci
63+
64+
- name: 5. Run Tests
65+
run: npm run test
66+
67+
build-frontend:
68+
name: Build Angular Frontend
69+
runs-on: ubuntu-latest
70+
needs: [lint, test]
71+
steps:
72+
- name: 1. Checkout Source
73+
uses: actions/checkout@v4
74+
75+
- name: 2. Setup Node.js
76+
uses: actions/setup-node@v4
77+
with:
78+
node-version: ${{ env.NODE_VERSION }}
79+
80+
- name: 3. Cache npm
81+
uses: actions/cache@v4
82+
with:
83+
path: ~/.npm
84+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
85+
restore-keys: |
86+
${{ runner.os }}-node-
87+
88+
- name: 4. Install Dependencies
89+
run: npm ci
90+
91+
- name: 5. Build Angular App
92+
run: npm run build -- --configuration production
93+
94+
- name: 6. Upload Angular Dist Artifact
95+
uses: actions/upload-artifact@v4
96+
with:
97+
name: dist
98+
path: dist
99+
100+
build-image:
101+
name: Build & Push Docker Image
102+
runs-on: ubuntu-latest
103+
needs: build-frontend
104+
env:
105+
DH_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
106+
DH_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
107+
steps:
108+
- name: 1. Checkout Source
109+
uses: actions/checkout@v4
110+
111+
- name: 2. Set up Docker Buildx
112+
uses: docker/setup-buildx-action@v3
113+
with:
114+
driver: docker-container
115+
buildkitd-flags: --debug
116+
117+
- name: 3. Log in to DockerHub
118+
uses: docker/login-action@v3
119+
with:
120+
username: ${{ env.DH_USERNAME }}
121+
password: ${{ env.DH_TOKEN }}
122+
123+
- name: 4. Build & Push Docker Image
124+
uses: docker/build-push-action@v6
125+
with:
126+
context: .
127+
push: true
128+
tags: ${{ env.DH_USERNAME }}/angular-topics:latest
129+
cache-from: type=registry,ref=${{ env.DH_USERNAME }}/angular-topics:latest
130+
cache-to: type=registry,ref=${{ env.DH_USERNAME }}/angular-topics:latest,mode=max
131+
132+
deploy:
133+
name: Deploy to Vercel
134+
runs-on: ubuntu-latest
135+
needs: build-frontend
136+
steps:
137+
- name: 1. Download Angular Dist Artifact
138+
uses: actions/download-artifact@v4
139+
with:
140+
name: dist
141+
path: dist
142+
143+
- name: 2. Install Vercel CLI
144+
run: npm install -g vercel
145+
146+
- name: 3. Deploy to Vercel
147+
run: vercel dist/${{ env.APP_NAME }}/browser --prod --yes --token=${{ secrets.VERCEL_TOKEN }}

Dockerfile

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Stage 1: Build app
2+
FROM node:24 AS build
3+
WORKDIR /app
4+
5+
COPY package*.json ./
6+
RUN npm install
7+
8+
COPY . .
9+
RUN npm run build -- --configuration production
10+
11+
# Stage 2: Nginx serve static files
12+
FROM nginx:stable-alpine
13+
14+
COPY --from=build /app/dist/angular-bb/browser /usr/share/nginx/html
15+
16+
COPY nginx.conf /etc/nginx/conf.d/default.conf
17+
18+
EXPOSE 80
19+
20+
CMD ["nginx", "-g", "daemon off;"]

docker-compose.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
services:
2+
angular-bb:
3+
build: .
4+
image: angular-bb:latest
5+
ports:
6+
- "4200:80"
7+
container_name: angular-bb

karma.config.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module.exports = function (config) {
2+
config.set({
3+
browsers: ['ChromeHeadless'],
4+
customLaunchers: {
5+
ChromeHeadlessCI: {
6+
base: 'ChromeHeadless',
7+
flags: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage']
8+
}
9+
}
10+
});
11+
};

nginx.conf

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
server {
2+
listen 80;
3+
server_name localhost;
4+
5+
root /usr/share/nginx/html;
6+
7+
index index.html;
8+
9+
location / {
10+
try_files $uri $uri/ /index.html;
11+
}
12+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"build:dev": "ng build --configuration=development",
1010
"analyze": "ng build && source-map-explorer dist/angular-bb/**/*.js",
1111
"knip": "npx knip --cache --reporter symbols --performance",
12-
"test": "ng test",
12+
"test": "ng test --watch=false --browsers=ChromeHeadless",
1313
"commitlint": "commitlint",
1414
"lint": "eslint --fix",
1515
"format": "prettier --write",

src/app/app.component.spec.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ComponentFixture, TestBed } from "@angular/core/testing";
2+
import { AppComponent } from "./app.component";
3+
4+
describe("AppComponent (Standalone)", () => {
5+
let fixture: ComponentFixture<AppComponent>;
6+
let component: AppComponent;
7+
let compiled: HTMLElement;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [AppComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(AppComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
compiled = fixture.nativeElement;
18+
});
19+
20+
it("should create the app", () => {
21+
expect(component).toBeTruthy();
22+
});
23+
24+
it(`should have title 'Angular Bootstrap Boilerplate'`, () => {
25+
expect(component.title).toBe("Angular Bootstrap Boilerplate");
26+
});
27+
28+
it("should render title in h1", () => {
29+
const h1 = compiled.querySelector("#title");
30+
expect(h1?.textContent).toContain("Angular Bootstrap Boilerplate");
31+
});
32+
33+
it("should contain router outlet", () => {
34+
const routerOutlet = compiled.querySelector("router-outlet");
35+
expect(routerOutlet).toBeTruthy();
36+
});
37+
});

src/app/app.component.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import { RouterOutlet } from "@angular/router";
66
imports: [RouterOutlet],
77
standalone: true,
88
template: `
9+
<h1 id="title">{{ title }}</h1>
910
<div class="!max-w-[1200px] mx-auto">
1011
<router-outlet></router-outlet>
1112
</div>
1213
`,
1314
})
14-
export class AppComponent {}
15+
export class AppComponent {
16+
title = "Angular Bootstrap Boilerplate";
17+
}

0 commit comments

Comments
 (0)