Skip to content

Commit 8e22b81

Browse files
feat: add PR preview deployments via Azure Static Web Apps
Closes #64 - Add apiBaseUrlInterceptor: prepends apiBaseUrl to /api/* requests, allowing the Angular app to call the production backend from a different origin in preview builds - Add environment files: environment.ts (empty = same-origin, used in dev and prod) and environment.preview.ts (production App Service URL) - Add preview build configuration in project.json with fileReplacements to swap in environment.preview.ts - Add staticwebapp.config.json for SPA fallback routing in Azure SWA - Add CORS policy in Program.cs allowing *.azurestaticapps.net origins - Add preview.yml workflow: builds Angular with the preview config, deploys to Azure SWA on PR open/sync, closes staging env on PR close Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d60bc3f commit 8e22b81

8 files changed

Lines changed: 105 additions & 1 deletion

File tree

.github/workflows/preview.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Preview
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, closed]
6+
branches: [main]
7+
8+
concurrency:
9+
group: preview-${{ github.event.pull_request.number }}
10+
cancel-in-progress: true
11+
12+
jobs:
13+
deploy_preview:
14+
if: github.event.action != 'closed'
15+
name: Deploy Preview
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: pnpm/action-setup@v4
21+
with:
22+
version: 10
23+
24+
- uses: actions/setup-node@v4
25+
with:
26+
node-version: 24
27+
cache: pnpm
28+
29+
- name: Install dependencies
30+
run: pnpm install --frozen-lockfile
31+
32+
- name: Build web app (preview)
33+
run: pnpm nx build web-app --configuration preview
34+
35+
- name: Copy SWA routing config
36+
run: cp apps/web-app/src/staticwebapp.config.json dist/apps/web-app/browser/
37+
38+
- name: Deploy to Azure Static Web Apps
39+
uses: Azure/static-web-apps-deploy@v1
40+
with:
41+
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
42+
repo_token: ${{ secrets.GITHUB_TOKEN }}
43+
action: upload
44+
app_location: dist/apps/web-app/browser
45+
skip_app_build: true
46+
47+
close_preview:
48+
if: github.event.action == 'closed'
49+
name: Close Preview
50+
runs-on: ubuntu-latest
51+
steps:
52+
- name: Close staging environment
53+
uses: Azure/static-web-apps-deploy@v1
54+
with:
55+
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
56+
action: close

apps/api/Api/Program.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@
4141

4242
builder.Services.AddControllers();
4343

44+
builder.Services.AddCors(options =>
45+
options.AddPolicy("SwaPreview", policy =>
46+
policy
47+
.SetIsOriginAllowed(origin =>
48+
Uri.TryCreate(origin, UriKind.Absolute, out var uri)
49+
&& uri.Host.EndsWith(".azurestaticapps.net"))
50+
.AllowAnyMethod()
51+
.AllowAnyHeader()
52+
)
53+
);
54+
4455
var app = builder.Build();
4556

4657
if (app.Environment.IsDevelopment())
@@ -85,6 +96,8 @@
8596

8697
app.UseRouting();
8798

99+
app.UseCors("SwaPreview");
100+
88101
app.UseAuthorization();
89102

90103
app.MapControllers();

apps/web-app/project.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@
5050
"extractLicenses": false,
5151
"sourceMap": true,
5252
"namedChunks": true
53+
},
54+
"preview": {
55+
"fileReplacements": [
56+
{
57+
"replace": "apps/web-app/src/environments/environment.ts",
58+
"with": "apps/web-app/src/environments/environment.preview.ts"
59+
}
60+
],
61+
"outputHashing": "all"
5362
}
5463
},
5564
"defaultConfiguration": "production"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { HttpInterceptorFn } from '@angular/common/http';
2+
3+
import { environment } from '../environments/environment';
4+
5+
export const apiBaseUrlInterceptor: HttpInterceptorFn = (req, next) => {
6+
if (!environment.apiBaseUrl || !req.url.startsWith('/api')) {
7+
return next(req);
8+
}
9+
return next(req.clone({ url: environment.apiBaseUrl + req.url }));
10+
};

apps/web-app/src/app/app.config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@ import {
1919
import { provideServiceWorker } from '@angular/service-worker';
2020
import { authInterceptor } from '@myorg/auth';
2121

22+
import { apiBaseUrlInterceptor } from './api-base-url.interceptor';
2223
import { routes } from './app.routes';
2324

2425
export const appConfig: ApplicationConfig = {
2526
providers: [
2627
provideZonelessChangeDetection(),
27-
provideHttpClient(withFetch(), withInterceptors([authInterceptor])),
28+
provideHttpClient(
29+
withFetch(),
30+
withInterceptors([apiBaseUrlInterceptor, authInterceptor]),
31+
),
2832
provideRouter(
2933
routes,
3034
withComponentInputBinding(),
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const environment = {
2+
apiBaseUrl: 'https://angularclinetcorengrxstarter.azurewebsites.net',
3+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const environment = {
2+
apiBaseUrl: '',
3+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"navigationFallback": {
3+
"rewrite": "/index.html",
4+
"exclude": ["/*.{css,js,png,gif,ico,jpg,svg,webmanifest,woff,woff2,txt}"]
5+
}
6+
}

0 commit comments

Comments
 (0)