Skip to content

Commit f7a9f02

Browse files
committed
chore: set up work-cli
1 parent 4380d87 commit f7a9f02

4 files changed

Lines changed: 177 additions & 7 deletions

File tree

bun.lock

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
"private": true,
44
"type": "module",
55
"scripts": {
6-
"dev": "portless run astro dev",
7-
"dev:sync": "portless tilly-sync sh -c 'bunx jazz-run sync --port \"$PORT\" --host \"$HOST\"'",
6+
"dev": "astro dev",
7+
"dev:sync": "bunx jazz-run sync",
88
"build": "astro build",
99
"build:prod": "astro build",
1010
"build:node": "ASTRO_ADAPTER=node astro build",
11-
"preview": "ASTRO_ADAPTER=node dotenv -e .env -- portless tilly-preview astro preview",
11+
"preview": "ASTRO_ADAPTER=node dotenv -e .env -- astro preview",
1212
"check": "concurrently -n Astro,Prettier,ESLint,Knip \"astro check\" \"prettier --check .\" \"eslint . --ext .ts,.tsx,.js,.jsx,.astro\" \"knip --no-config-hints\"",
1313
"format": "prettier -w .",
1414
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.astro",
@@ -102,7 +102,6 @@
102102
"globals": "^17.6.0",
103103
"jsdom": "^29.1.1",
104104
"knip": "^6.11.0",
105-
"portless": "^0.12.0",
106105
"prettier": "^3.8.3",
107106
"prettier-plugin-astro": "^0.14.1",
108107
"prettier-plugin-tailwindcss": "^0.8.0",

scripts/work-setup.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env bun
2+
3+
import { execFileSync, spawn, type ChildProcess } from "child_process"
4+
import { existsSync, readFileSync, writeFileSync } from "fs"
5+
import { createConnection } from "net"
6+
import { join } from "path"
7+
8+
let workRoot = readRequiredEnv("WORK_ROOT")
9+
let sourceRoot = readRequiredEnv("WORK_SOURCE_ROOT")
10+
let workspace = readRequiredEnv("WORK_WORKSPACE")
11+
let syncServer = `wss://sync.${workspace}.tilly.localhost`
12+
let sourceEnvPath = join(sourceRoot, ".env")
13+
let workspaceEnvPath = join(workRoot, ".env")
14+
15+
await main()
16+
17+
async function main() {
18+
if (!existsSync(sourceEnvPath)) {
19+
throw new Error(`Missing source .env at ${sourceEnvPath}`)
20+
}
21+
22+
let envFile = readFileSync(sourceEnvPath, "utf-8")
23+
envFile = setEnvValue(envFile, "PUBLIC_JAZZ_SYNC_SERVER", syncServer)
24+
writeFileSync(workspaceEnvPath, envFile)
25+
26+
execFileSync("bun", ["install"], { cwd: workRoot, stdio: "inherit" })
27+
28+
let worker = await createWorkspaceJazzWorker()
29+
30+
envFile = readFileSync(workspaceEnvPath, "utf-8")
31+
envFile = setEnvValue(envFile, "PUBLIC_JAZZ_WORKER_ACCOUNT", worker.accountId)
32+
envFile = setEnvValue(envFile, "JAZZ_WORKER_SECRET", worker.accountSecret)
33+
writeFileSync(workspaceEnvPath, envFile)
34+
35+
console.log(`Workspace ready: ${workspace}`)
36+
}
37+
38+
function readRequiredEnv(key: string): string {
39+
let value = process.env[key]
40+
if (!value) throw new Error(`Missing ${key}`)
41+
return value
42+
}
43+
44+
function setEnvValue(content: string, key: string, value: string): string {
45+
let lines = content.split("\n")
46+
let nextLine = `${key}=${value}`
47+
let replaced = false
48+
49+
lines = lines.map(line => {
50+
if (!line.startsWith(`${key}=`)) return line
51+
52+
replaced = true
53+
return nextLine
54+
})
55+
56+
if (!replaced) {
57+
if (lines.at(-1) !== "") lines.push("")
58+
lines.push(nextLine)
59+
}
60+
61+
return lines.join("\n")
62+
}
63+
64+
type JazzWorkerCredentials = { accountId: string; accountSecret: string }
65+
66+
async function createWorkspaceJazzWorker(): Promise<JazzWorkerCredentials> {
67+
let port = 45000 + Math.floor(Math.random() * 10000)
68+
let peer = `ws://127.0.0.1:${port}`
69+
let jazzRun = join(workRoot, "node_modules", ".bin", "jazz-run")
70+
let syncProcess = spawn(
71+
jazzRun,
72+
["sync", "--port", String(port), "--host", "127.0.0.1"],
73+
{ cwd: workRoot, stdio: "ignore" },
74+
)
75+
76+
try {
77+
await waitForPort(port)
78+
79+
let output = execFileSync(
80+
jazzRun,
81+
[
82+
"account",
83+
"create",
84+
"--name",
85+
`Tilly Worker (${workspace})`,
86+
"--peer",
87+
peer,
88+
"--json",
89+
],
90+
{ cwd: workRoot, encoding: "utf-8" },
91+
)
92+
93+
return parseJazzWorkerCredentials(output)
94+
} finally {
95+
stopProcess(syncProcess)
96+
}
97+
}
98+
99+
async function waitForPort(port: number) {
100+
let deadline = Date.now() + 15_000
101+
102+
while (Date.now() < deadline) {
103+
let result = await tryConnect(port)
104+
if (result) return
105+
await sleep(200)
106+
}
107+
108+
throw new Error(`Timed out waiting for Jazz sync server on port ${port}`)
109+
}
110+
111+
function tryConnect(port: number): Promise<boolean> {
112+
return new Promise(resolve => {
113+
let socket = createConnection({ host: "127.0.0.1", port })
114+
let done = false
115+
116+
let finish = (result: boolean) => {
117+
if (done) return
118+
119+
done = true
120+
socket.destroy()
121+
resolve(result)
122+
}
123+
124+
socket.setTimeout(500)
125+
socket.on("connect", () => finish(true))
126+
socket.on("error", () => finish(false))
127+
socket.on("timeout", () => finish(false))
128+
})
129+
}
130+
131+
function parseJazzWorkerCredentials(output: string): JazzWorkerCredentials {
132+
let parsed: unknown = JSON.parse(output.trim())
133+
if (!isRecord(parsed)) throw new Error("Invalid Jazz account output")
134+
135+
let accountId = parsed.accountID
136+
let accountSecret = parsed.agentSecret
137+
138+
if (typeof accountId !== "string" || typeof accountSecret !== "string") {
139+
throw new Error("Jazz account output is missing credentials")
140+
}
141+
142+
return { accountId, accountSecret }
143+
}
144+
145+
function isRecord(value: unknown): value is Record<string, unknown> {
146+
return typeof value === "object" && value !== null
147+
}
148+
149+
function stopProcess(process: ChildProcess) {
150+
if (process.pid) process.kill()
151+
}
152+
153+
function sleep(milliseconds: number): Promise<void> {
154+
return new Promise(resolve => setTimeout(resolve, milliseconds))
155+
}

work.config.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export default {
2+
project: "tilly",
3+
worktrees: {
4+
dir: "../tilly.worktrees",
5+
setup: "bun scripts/work-setup.ts",
6+
},
7+
commands: {
8+
sync: {
9+
run: 'bunx jazz-run sync --port "$PORT" --host "$HOST"',
10+
autoStart: true,
11+
route: true,
12+
},
13+
web: {
14+
run: 'PUBLIC_JAZZ_SYNC_SERVER="wss://sync.${WORK_WORKSPACE}.tilly.localhost" astro dev --port "$PORT" --host "$HOST"',
15+
autoStart: true,
16+
route: true,
17+
},
18+
},
19+
}

0 commit comments

Comments
 (0)