Skip to content

Commit e08d538

Browse files
committed
Add github action
1 parent 1e9d59e commit e08d538

File tree

3 files changed

+209
-77
lines changed

3 files changed

+209
-77
lines changed

.dagger/src/index.ts

Lines changed: 182 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
object,
66
func,
77
argument,
8-
File,
98
Secret,
109
} from "@dagger.io/dagger";
1110

@@ -14,65 +13,25 @@ const RUST_IMAGE = "rust:bookworm";
1413

1514
@object()
1615
export class AtomicServer {
17-
/**
18-
* Publish the application container after building and testing it on-the-fly
19-
*/
2016
@func()
21-
async publish(
22-
@argument({ defaultPath: "/" }) source: Directory
23-
): Promise<string> {
24-
await this.test(source);
25-
return await this.build(source).publish(
26-
"ttl.sh/hello-dagger-" + Math.floor(Math.random() * 10000000)
27-
);
28-
}
29-
30-
/**
31-
* Build the application container
32-
*/
33-
@func()
34-
build(@argument({ defaultPath: "/" }) source: Directory): Container {
35-
const build = this.buildEnv(source)
36-
.withExec(["npm", "run", "build"])
37-
.directory("./dist");
38-
return dag
39-
.container()
40-
.from("nginx:1.25-alpine")
41-
.withDirectory("/usr/share/nginx/html", build)
42-
.withExposedPort(80);
43-
}
44-
45-
/**
46-
* Return the result of running unit tests
47-
*/
48-
@func()
49-
async test(
50-
@argument({ defaultPath: "/" }) source: Directory
51-
): Promise<string> {
52-
return this.buildEnv(source)
53-
.withExec(["npm", "run", "test:unit", "run"])
54-
.stdout();
55-
}
56-
57-
/**
58-
* Build a ready-to-use development environment
59-
*/
60-
@func()
61-
buildEnv(@argument({ defaultPath: "/" }) source: Directory): Container {
62-
const nodeCache = dag.cacheVolume("node");
63-
return dag
64-
.container()
65-
.from(NODE_IMAGE)
66-
.withDirectory("/src", source)
67-
.withMountedCache("/root/.npm", nodeCache)
68-
.withWorkdir("/src")
69-
.withExec(["npm", "install"]);
17+
async ci(
18+
@argument() netlifyAuthToken: Secret
19+
): Promise<(string | Container)[]> {
20+
return Promise.all([
21+
this.docsPublish(netlifyAuthToken),
22+
this.buildBrowser(),
23+
this.lintBrowser(),
24+
this.testBrowser(),
25+
this.ee(netlifyAuthToken),
26+
this.rustTest(),
27+
this.rustClippy(),
28+
this.rustFmt(),
29+
]);
7030
}
7131

7232
@func()
73-
buildBrowser(
74-
@argument({ defaultPath: "/browser" }) source: Directory
75-
): Container {
33+
buildBrowser(): Container {
34+
const source = dag.currentModule().source().directory("browser");
7635
const depsContainer = this.getDeps(source.directory("."));
7736

7837
const buildContainer = depsContainer
@@ -83,9 +42,8 @@ export class AtomicServer {
8342
}
8443

8544
@func()
86-
async lintBrowser(
87-
@argument({ defaultPath: "/browser" }) source: Directory
88-
): Promise<string> {
45+
async lintBrowser(): Promise<string> {
46+
const source = dag.currentModule().source().directory("browser");
8947
const depsContainer = this.getDeps(source.directory("."));
9048
return depsContainer
9149
.withWorkdir("/app")
@@ -94,9 +52,8 @@ export class AtomicServer {
9452
}
9553

9654
@func()
97-
async testBrowser(
98-
@argument({ defaultPath: "/browser" }) source: Directory
99-
): Promise<string> {
55+
async testBrowser(): Promise<string> {
56+
const source = dag.currentModule().source().directory("browser");
10057
const depsContainer = this.getDeps(source.directory("."));
10158
return depsContainer
10259
.withWorkdir("/app")
@@ -105,16 +62,14 @@ export class AtomicServer {
10562
}
10663

10764
@func()
108-
docsPublish(
109-
@argument({ defaultPath: "/docs" }) source: Directory,
110-
@argument() netlifyAuthToken: Secret
111-
): Promise<string> {
65+
docsPublish(@argument() netlifyAuthToken: Secret): Promise<string> {
66+
const builtDocsHtml = this.docsFolder();
11267
return dag
11368
.container()
11469
.from(NODE_IMAGE)
11570
.withExec(["npm", "install", "-g", "netlify-cli"])
11671
.withSecretVariable("NETLIFY_AUTH_TOKEN", netlifyAuthToken)
117-
.withDirectory("/html", this.docsFolder(source.directory(".")))
72+
.withDirectory("/html", builtDocsHtml)
11873
.withWorkdir("/html")
11974
.withExec([
12075
"sh",
@@ -126,26 +81,25 @@ export class AtomicServer {
12681
}
12782

12883
@func()
129-
docsFolder(@argument({ defaultPath: "/docs" }) source: Directory): Directory {
84+
docsFolder(): Directory {
85+
const moduleSource = dag.currentModule().source();
86+
const actualDocsDirectory = moduleSource.directory("docs");
87+
13088
const docsContainer = dag
13189
.container()
13290
.from(RUST_IMAGE)
13391
.withExec(["cargo", "install", "mdbook"])
13492
.withExec(["cargo", "install", "mdbook-linkcheck"]);
135-
// We skip installing mdbook-sitemap-generator because it's broken
13693
return docsContainer
137-
.withDirectory("/docs", source)
94+
.withDirectory("/docs", actualDocsDirectory) // Use the derived path
13895
.withWorkdir("/docs")
13996
.withExec(["mdbook", "build"])
14097
.directory("/docs/book/html");
14198
}
142-
14399
@func()
144-
typedocPublish(
145-
@argument({ defaultPath: "/browser" }) source: Directory,
146-
@argument() netlifyAuthToken: Secret
147-
): Promise<string> {
148-
const browserDir = this.buildBrowser(source.directory("."));
100+
typedocPublish(@argument() netlifyAuthToken: Secret): Promise<string> {
101+
const source = dag.currentModule().source().directory("browser");
102+
const browserDir = this.buildBrowser();
149103
return browserDir
150104
.withWorkdir("/app")
151105
.withSecretVariable("NETLIFY_AUTH_TOKEN", netlifyAuthToken)
@@ -188,4 +142,156 @@ export class AtomicServer {
188142
// Copy the rest of the source
189143
return depsContainer.withDirectory("/app", source);
190144
}
145+
146+
@func()
147+
rustBuild(): Container {
148+
const source = dag.currentModule().source();
149+
const cargoCache = dag.cacheVolume("cargo");
150+
151+
const rustContainer = dag
152+
.container()
153+
.from(RUST_IMAGE)
154+
.withExec(["apt-get", "update", "-qq"])
155+
.withExec(["apt", "install", "-y", "nasm"])
156+
.withExec(["rustup", "component", "add", "clippy"])
157+
.withExec(["rustup", "component", "add", "rustfmt"])
158+
.withExec(["cargo", "install", "cross"])
159+
.withExec(["cargo", "install", "cargo-nextest"])
160+
.withMountedCache("/usr/local/cargo/registry", cargoCache);
161+
162+
// Copy source files like in Earthfile, but more selectively
163+
const sourceContainer = rustContainer
164+
.withFile("/code/Cargo.toml", source.file("Cargo.toml"))
165+
.withFile("/code/Cargo.lock", source.file("Cargo.lock"))
166+
.withFile("/code/Cross.toml", source.file("Cross.toml"))
167+
.withDirectory("/code/server", source.directory("server"))
168+
.withDirectory("/code/lib", source.directory("lib"))
169+
.withDirectory("/code/cli", source.directory("cli"))
170+
.withMountedCache("/code/target", dag.cacheVolume("rust-target"))
171+
.withWorkdir("/code")
172+
.withExec(["cargo", "fetch"]);
173+
174+
return sourceContainer
175+
.withExec(["cargo", "build", "--release"])
176+
.withExec(["./target/release/atomic-server", "--version"]);
177+
}
178+
179+
@func()
180+
rustTest(): Promise<string> {
181+
return this.rustBuild().withExec(["cargo", "nextest", "run"]).stdout();
182+
}
183+
184+
@func()
185+
rustClippy(): Promise<string> {
186+
const source = dag.currentModule().source();
187+
const rustContainer = dag
188+
.container()
189+
.from(RUST_IMAGE)
190+
.withExec(["rustup", "component", "add", "clippy"])
191+
.withDirectory("/code", source)
192+
.withWorkdir("/code");
193+
194+
return rustContainer
195+
.withExec([
196+
"cargo",
197+
"clippy",
198+
"--no-deps",
199+
"--all-features",
200+
"--all-targets",
201+
])
202+
.stdout();
203+
}
204+
205+
@func()
206+
rustFmt(): Promise<string> {
207+
const source = dag.currentModule().source();
208+
const rustContainer = dag
209+
.container()
210+
.from(RUST_IMAGE)
211+
.withExec(["rustup", "component", "add", "rustfmt"])
212+
.withDirectory("/code", source)
213+
.withWorkdir("/code");
214+
215+
return rustContainer.withExec(["cargo", "fmt", "--check"]).stdout();
216+
}
217+
218+
@func()
219+
rustCrossBuild(@argument() target: string): Container {
220+
const source = dag.currentModule().source();
221+
const rustContainer = dag
222+
.container()
223+
.from(RUST_IMAGE)
224+
.withExec(["cargo", "install", "cross"])
225+
.withDirectory("/code", source)
226+
.withWorkdir("/code");
227+
228+
return rustContainer
229+
.withExec(["cross", "build", "--target", target, "--release"])
230+
.withExec([`./target/${target}/release/atomic-server`, "--version"]);
231+
}
232+
233+
@func()
234+
ee(@argument() netlifyAuthToken: Secret): Promise<string> {
235+
const source = dag.currentModule().source();
236+
// Setup Playwright container - debug and fix package manager
237+
const playwrightContainer = dag
238+
.container()
239+
.from("mcr.microsoft.com/playwright:v1.48.1-noble")
240+
.withExec([
241+
"/bin/sh",
242+
"-c",
243+
'curl -fsSL https://get.pnpm.io/install.sh | env PNPM_VERSION=9.3.0 ENV="$HOME/.shrc" SHELL="$(which sh)" sh - && export PATH=/root/.local/share/pnpm:$PATH && /bin/apt update && /bin/apt install -y zip',
244+
])
245+
.withEnvVariable(
246+
"PATH",
247+
"/root/.local/share/pnpm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
248+
)
249+
.withExec(["pnpm", "dlx", "playwright", "install", "--with-deps"])
250+
.withExec(["npm", "install", "-g", "netlify-cli"]);
251+
252+
// Get the atomic-server binary from the build
253+
const atomicServerBinary = this.rustBuild().file(
254+
"/code/target/release/atomic-server"
255+
);
256+
257+
// Setup e2e test environment
258+
const e2eContainer = playwrightContainer
259+
.withFile(
260+
"/app/e2e/package.json",
261+
source.file("browser/e2e/package.json")
262+
)
263+
.withWorkdir("/app/e2e")
264+
.withExec(["pnpm", "install"])
265+
.withDirectory("/app", source.directory("browser/e2e"))
266+
.withExec(["pnpm", "install"])
267+
.withEnvVariable("LANGUAGE", "en_GB")
268+
.withEnvVariable("DELETE_PREVIOUS_TEST_DRIVES", "false")
269+
.withEnvVariable("FRONTEND_URL", "http://localhost:9883")
270+
.withFile("/atomic-server-bin", atomicServerBinary, {
271+
permissions: 0o755,
272+
})
273+
.withSecretVariable("NETLIFY_AUTH_TOKEN", netlifyAuthToken);
274+
275+
// Run the tests with atomic-server in background
276+
const testResult = e2eContainer
277+
.withExec([
278+
"/bin/sh",
279+
"-c",
280+
"nohup /atomic-server-bin --initialize & pnpm run test-e2e && zip -r test.zip /app/e2e/playwright-report",
281+
])
282+
.withExec(["unzip", "-o", "test.zip", "-d", "/artifact"]);
283+
284+
// Upload test results to Netlify
285+
return testResult
286+
.withExec([
287+
"netlify",
288+
"deploy",
289+
"--dir",
290+
"/artifact/app/e2e/playwright-report",
291+
"--prod",
292+
"--site",
293+
"atomic-tests",
294+
])
295+
.stdout();
296+
}
191297
}

.github/workflows/dagger.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: dagger
2+
on:
3+
push:
4+
branches: [main, dagger]
5+
jobs:
6+
build-publish:
7+
runs-on: ubuntu-latest
8+
permissions:
9+
contents: read
10+
packages: write
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
- name: Call Dagger Function to build and publish to ghcr.io
15+
uses: dagger/[email protected]
16+
with:
17+
version: "latest"
18+
verb: call
19+
args: ci
20+
cloud-token: ${{ secrets.DAGGER_CLOUD_TOKEN }}

dagger.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@
1010
"build",
1111
".env",
1212
".git",
13+
".temp",
14+
".cargo",
15+
"tmp",
16+
"book",
1317
".github",
1418
".husky",
15-
".vscode"
19+
".vscode",
20+
"target/**",
21+
"artifact/**"
1622
],
1723
"source": ".dagger"
1824
}

0 commit comments

Comments
 (0)