Skip to content

Commit 475a65b

Browse files
committed
Add PowerShell Observable deployment article
1 parent b39ab9f commit 475a65b

1 file changed

Lines changed: 199 additions & 0 deletions

File tree

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
---
2+
title: "Run PowerShell Data Loaders on Azure Static Web Apps"
3+
description: "Use PowerShell data loaders in Observable Framework and deploy the generated static site to Azure Static Web Apps without relying on the platform's automatic build environment."
4+
author: Andrey Vernigora
5+
authors:
6+
- Andrey Vernigora
7+
date: 2026-07-24T00:00:00+00:00
8+
categories:
9+
- Tools
10+
tags:
11+
- powershell
12+
- observable-framework
13+
- azure-static-web-apps
14+
- github-actions
15+
- data-visualization
16+
---
17+
18+
Observable Framework can run PowerShell scripts during a build and expose their
19+
output to interactive pages as static data. The part that needs extra attention is
20+
deployment: the automatic Azure Static Web Apps build does not know about your custom
21+
PowerShell interpreter.
22+
23+
This article shows how to register `.ps1` data loaders, build the site in GitHub
24+
Actions where PowerShell is available, and ask Azure Static Web Apps to deploy the
25+
already-generated files.
26+
27+
## How Observable data loaders work
28+
29+
[Observable Framework](https://observablehq.com/framework/) is a static site
30+
generator for data apps, dashboards, and reports. A page can load a CSV file like
31+
this:
32+
33+
```js
34+
const movies = FileAttachment("movies.csv").csv({typed: true});
35+
```
36+
37+
If `movies.csv` does not exist, Framework looks for a data loader with a double
38+
extension, such as `movies.csv.py` or `movies.csv.js`. It runs the loader during the
39+
build, saves its standard output as a static snapshot, and makes that snapshot
40+
available to the page as `movies.csv`.
41+
42+
Framework supports several interpreters by default and lets us register additional
43+
ones. Add PowerShell to `observablehq.config.js`:
44+
45+
```js
46+
export default {
47+
root: "src",
48+
interpreters: {
49+
".ps1": ["pwsh"]
50+
}
51+
};
52+
```
53+
54+
The `pwsh` executable must be installed and available on `PATH` wherever the site is
55+
built.
56+
57+
## Create a PowerShell data loader
58+
59+
Create `src/movies.csv.ps1`:
60+
61+
```powershell
62+
$uri = 'https://raw.githubusercontent.com/vega/vega/main/docs/data/movies.json'
63+
$movies = Invoke-RestMethod -Uri $uri
64+
65+
$movies |
66+
Select-Object -Property Title, 'Worldwide Gross', 'US Gross', 'IMDB Rating' |
67+
ConvertTo-Csv -NoTypeInformation
68+
```
69+
70+
The script retrieves JSON, selects the columns needed by the page, and writes CSV to
71+
standard output. That last detail is important: standard output becomes the generated
72+
file. Send diagnostics to the information, warning, or error streams so they do not
73+
corrupt the CSV.
74+
75+
You can test the loader independently:
76+
77+
```powershell
78+
pwsh ./src/movies.csv.ps1
79+
```
80+
81+
Then use it from `src/index.md`:
82+
83+
````markdown
84+
```js
85+
const movies = FileAttachment("movies.csv").csv({typed: true});
86+
```
87+
88+
```js
89+
Inputs.table(movies)
90+
```
91+
````
92+
93+
Run the normal Framework build:
94+
95+
```powershell
96+
npm run build
97+
```
98+
99+
Framework executes the PowerShell loader, caches its result, and includes the
100+
generated CSV in the static output.
101+
102+
## Why the default Azure build can fail
103+
104+
Azure Static Web Apps normally checks out the source and invokes its automatic build
105+
environment. That works until the application requires an interpreter or native tool
106+
that the environment does not provide or configure.
107+
108+
Instead of teaching the automatic builder about every dependency, build the site in
109+
an ordinary GitHub Actions step and deploy only the output directory. This also makes
110+
the failing stage obvious: if a PowerShell loader breaks, the `npm run build` step
111+
fails before deployment starts.
112+
113+
## Keep the Azure configuration in the output
114+
115+
Azure looks for `staticwebapp.config.json` in the deployed directory. If you keep that
116+
file at the project root, copy it after the Observable build.
117+
118+
For example, these scripts in `package.json` build the site into `dist` and copy the
119+
configuration:
120+
121+
```json
122+
{
123+
"scripts": {
124+
"build": "rimraf dist && observable build",
125+
"postbuild": "cp staticwebapp.config.json dist/staticwebapp.config.json"
126+
}
127+
}
128+
```
129+
130+
The `postbuild` script runs automatically after `npm run build`. For a
131+
cross-platform project, replace `cp` with a small Node.js script.
132+
133+
## Build first, then deploy
134+
135+
The relevant part of the GitHub Actions workflow looks like this:
136+
137+
```yaml
138+
jobs:
139+
build_and_deploy:
140+
runs-on: ubuntu-latest
141+
steps:
142+
- name: Check out the repository
143+
uses: actions/checkout@v4
144+
145+
- name: Set up Node.js
146+
uses: actions/setup-node@v4
147+
with:
148+
node-version: 20
149+
cache: npm
150+
151+
- name: Verify PowerShell
152+
shell: pwsh
153+
run: $PSVersionTable.PSVersion
154+
155+
- name: Install dependencies
156+
run: npm ci
157+
158+
- name: Build the Observable site
159+
run: npm run build
160+
161+
- name: Deploy the prebuilt site
162+
uses: Azure/static-web-apps-deploy@v1
163+
with:
164+
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
165+
repo_token: ${{ secrets.GITHUB_TOKEN }}
166+
action: upload
167+
app_location: dist
168+
api_location: ""
169+
output_location: ""
170+
skip_app_build: true
171+
```
172+
173+
The last four settings are the key:
174+
175+
- `app_location` points directly to the generated `dist` directory;
176+
- `output_location` is empty because no build happens inside the Azure action;
177+
- `skip_app_build` prevents the Azure action from invoking its automatic builder;
178+
- `staticwebapp.config.json` is already inside `dist`.
179+
180+
Keep the pull-request close job and deployment token generated for your Static Web
181+
App; only replace the build-and-upload portion of the workflow.
182+
183+
## The resulting build pipeline
184+
185+
The finished pipeline has a simple division of responsibility:
186+
187+
1. GitHub Actions provides Node.js and PowerShell.
188+
2. Observable Framework runs `.ps1` data loaders and produces static files.
189+
3. Azure Static Web Apps deploys those files without rebuilding them.
190+
191+
This pattern is not limited to PowerShell. It also works when an Observable project
192+
depends on another interpreter, command-line tool, or native library that is easier to
193+
control in a dedicated build step.
194+
195+
For more detail, see the Observable Framework documentation for
196+
[data loaders](https://observablehq.com/framework/data-loaders) and
197+
[custom interpreters](https://observablehq.com/framework/config#interpreters), plus
198+
the Azure documentation for
199+
[deploying a prebuilt application](https://learn.microsoft.com/azure/static-web-apps/build-configuration#skip-building-front-end-app).

0 commit comments

Comments
 (0)