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 ( )
1615export 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}
0 commit comments