22 * Runs zbugs' backend for the Swift app, in development and in the
33 * end-to-end tests: a fresh Postgres database with zbugs' migrations and
44 * seed data, zbugs' API server (apps/zbugs/api), and zero-cache, all from
5- * this repository's sources.
5+ * this repository's sources. With `ZBUGS_KEEP=1` it keeps the database, the
6+ * replica, and its signing keys from the last run instead, so an app keeps
7+ * its local data and its token across restarts.
68 *
79 * node apps/zbugs-swift/server/server.ts
810 *
2224 * ZBUGS_PORT zero-cache port (default 4848)
2325 * ZBUGS_API_PORT API server port (default 5173)
2426 * ZBUGS_DIR working directory for the replica (default a temp dir)
27+ * ZBUGS_KEEP 1 to keep the last run's database, and the replica and
28+ * keys in ZBUGS_DIR, which it needs
2529 */
2630import { execFileSync , spawn } from 'node:child_process' ;
27- import { mkdtempSync , readdirSync } from 'node:fs' ;
31+ import {
32+ existsSync ,
33+ mkdtempSync ,
34+ readFileSync ,
35+ readdirSync ,
36+ rmSync ,
37+ writeFileSync ,
38+ } from 'node:fs' ;
2839import { registerHooks } from 'node:module' ;
2940import { networkInterfaces , tmpdir } from 'node:os' ;
3041import { join } from 'node:path' ;
@@ -42,6 +53,13 @@ const localHost = everywhere ? '127.0.0.1' : host;
4253const port = Number ( env [ 'ZBUGS_PORT' ] ?? 4848 ) ;
4354const apiPort = Number ( env [ 'ZBUGS_API_PORT' ] ?? 5173 ) ;
4455const workDir = env [ 'ZBUGS_DIR' ] ?? mkdtempSync ( join ( tmpdir ( ) , 'zbugs-swift-' ) ) ;
56+ const keep = env [ 'ZBUGS_KEEP' ] === '1' ;
57+ if ( keep && ! env [ 'ZBUGS_DIR' ] ) {
58+ process . stderr . write (
59+ 'ZBUGS_KEEP needs ZBUGS_DIR, for the replica and keys\n' ,
60+ ) ;
61+ process . exit ( 2 ) ;
62+ }
4563
4664const here = ( path : string ) => fileURLToPath ( new URL ( path , import . meta. url ) ) ;
4765const zbugs = here ( '../../zbugs/' ) ;
@@ -77,29 +95,43 @@ function psql(url: string, ...args: string[]) {
7795 } ) ;
7896}
7997
80- // The database: zbugs' migrations, then its seed script.
81- psql ( adminURL , '-c' , `DROP DATABASE IF EXISTS ${ dbName } WITH (FORCE)` ) ;
82- psql ( adminURL , '-c' , `CREATE DATABASE ${ dbName } ` ) ;
83- const migrations = join ( zbugs , 'db/migrations' ) ;
84- for ( const file of readdirSync ( migrations )
85- . filter ( f => f . endsWith ( '.sql' ) )
86- . sort ( ) ) {
87- // In a transaction each, as drizzle-kit applies them.
88- psql ( upstreamURL , '--single-transaction' , '-f' , join ( migrations , file ) ) ;
98+ // The database: zbugs' migrations, then its seed script. Kept, it also
99+ // holds zero-cache's client groups, which an app's local data belongs to.
100+ const kept =
101+ keep &&
102+ execFileSync (
103+ 'psql' ,
104+ [ adminURL , '-tAc' , `SELECT 1 FROM pg_database WHERE datname = '${ dbName } '` ] ,
105+ { encoding : 'utf8' } ,
106+ ) . trim ( ) === '1' ;
107+ if ( ! kept ) {
108+ psql ( adminURL , '-c' , `DROP DATABASE IF EXISTS ${ dbName } WITH (FORCE)` ) ;
109+ psql ( adminURL , '-c' , `CREATE DATABASE ${ dbName } ` ) ;
110+ const migrations = join ( zbugs , 'db/migrations' ) ;
111+ for ( const file of readdirSync ( migrations )
112+ . filter ( f => f . endsWith ( '.sql' ) )
113+ . sort ( ) ) {
114+ // In a transaction each, as drizzle-kit applies them.
115+ psql ( upstreamURL , '--single-transaction' , '-f' , join ( migrations , file ) ) ;
116+ }
117+ execFileSync ( process . execPath , [ join ( zbugs , 'scripts/seed.ts' ) ] , {
118+ cwd : zbugs ,
119+ stdio : [ 'ignore' , 'ignore' , 'inherit' ] ,
120+ env : {
121+ ...env ,
122+ [ 'ZERO_UPSTREAM_DB' ] : upstreamURL ,
123+ [ 'ZERO_SEED_DATA_DIR' ] : join (
124+ zbugs ,
125+ 'db/seed-data' ,
126+ env [ 'ZBUGS_SEED' ] ?? 'github' ,
127+ ) ,
128+ } ,
129+ } ) ;
130+ // A replica of an earlier database.
131+ for ( const file of readdirSync ( workDir ) ) {
132+ if ( file . startsWith ( 'replica.db' ) ) rmSync ( join ( workDir , file ) ) ;
133+ }
89134}
90- execFileSync ( process . execPath , [ join ( zbugs , 'scripts/seed.ts' ) ] , {
91- cwd : zbugs ,
92- stdio : [ 'ignore' , 'ignore' , 'inherit' ] ,
93- env : {
94- ...env ,
95- [ 'ZERO_UPSTREAM_DB' ] : upstreamURL ,
96- [ 'ZERO_SEED_DATA_DIR' ] : join (
97- zbugs ,
98- 'db/seed-data' ,
99- env [ 'ZBUGS_SEED' ] ?? 'github' ,
100- ) ,
101- } ,
102- } ) ;
103135const crewID = execFileSync (
104136 'psql' ,
105137 [
@@ -110,8 +142,18 @@ const crewID = execFileSync(
110142 { encoding : 'utf8' } ,
111143) . trim ( ) ;
112144
113- // The API server, as `pnpm dev` runs it in apps/zbugs, with keys of its own.
114- const { privateJwk, publicJwk} = await createJwkPair ( ) ;
145+ // The API server, as `pnpm dev` runs it in apps/zbugs, with keys of its own:
146+ // the last run's, kept, so that the tokens it printed still work.
147+ const keysFile = join ( workDir , 'keys.json' ) ;
148+ const { privateJwk, publicJwk} =
149+ keep && existsSync ( keysFile )
150+ ? ( JSON . parse ( readFileSync ( keysFile , 'utf8' ) ) as Awaited <
151+ ReturnType < typeof createJwkPair >
152+ > )
153+ : await createJwkPair ( ) ;
154+ if ( keep ) {
155+ writeFileSync ( keysFile , JSON . stringify ( { privateJwk, publicJwk} ) ) ;
156+ }
115157env [ 'ZERO_UPSTREAM_DB' ] = upstreamURL ;
116158env [ 'PRIVATE_JWK' ] = JSON . stringify ( privateJwk ) ;
117159env [ 'VITE_PUBLIC_JWK' ] = JSON . stringify ( publicJwk ) ;
0 commit comments