@threadcrumbs/threadnauts 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/assets/index-C46d6g6p.js +2464 -0
- package/dist/assets/index-PzTcyxAK.css +1 -0
- package/dist/assets/shared-DiTtF988.js +4978 -0
- package/dist/index.html +3 -3
- package/dist/pro-manifest.json +3 -3
- package/package.json +3 -2
- package/server/api.mjs +23 -4
- package/dist/assets/index-BSPNlXcc.js +0 -2514
- package/dist/assets/index-DoVfuOrP.css +0 -1
- package/dist/assets/shared-CgqOYNxw.js +0 -4919
package/dist/index.html
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
<meta name="color-scheme" content="dark" />
|
|
7
7
|
<title>Threadnauts</title>
|
|
8
8
|
<link rel="icon" href="/favicon.svg?app=threadnauts" type="image/svg+xml" />
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/shared-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-C46d6g6p.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/shared-DiTtF988.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/index-PzTcyxAK.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
14
14
|
<div id="app"><div class="boot"><div class="inner">
|
package/dist/pro-manifest.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": [
|
|
3
3
|
{
|
|
4
|
-
"path": "assets/pro-
|
|
5
|
-
"sha256": "
|
|
6
|
-
"bytes":
|
|
4
|
+
"path": "assets/pro-lBnPFibL.js",
|
|
5
|
+
"sha256": "e8e7df01da6e616fb37a60ddc2657b63681cfd34adf8fb37116ecad906845a6e",
|
|
6
|
+
"bytes": 233030
|
|
7
7
|
}
|
|
8
8
|
]
|
|
9
9
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@threadcrumbs/threadnauts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A 3D colony sim where every coding-agent thread on your machine is a little bot building something.",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"icons": "node tools/build-world-icons.mjs",
|
|
50
50
|
"site": "node tools/build-site.mjs",
|
|
51
51
|
"test": "node --test \"test/**/*.test.mjs\"",
|
|
52
|
-
"prepack": "pnpm run build"
|
|
52
|
+
"prepack": "pnpm run build",
|
|
53
|
+
"auto-publish": "node ../common/tools/release.mjs"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
56
|
"@threadcrumbs/common": "^0.1.0"
|
package/server/api.mjs
CHANGED
|
@@ -90,9 +90,26 @@ const emptyState = () => ({
|
|
|
90
90
|
const asObject = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? v : {})
|
|
91
91
|
const asArray = (v) => (Array.isArray(v) ? v : [])
|
|
92
92
|
|
|
93
|
+
const BUSY_CODES = new Set(['EPERM', 'EBUSY', 'EACCES'])
|
|
94
|
+
const BUSY_WAITS_MS = [50, 150, 400]
|
|
95
|
+
const BUSY_MESSAGE = 'Another program has colony.json open. Threadnauts will try again.'
|
|
96
|
+
const isBusy = (err) => BUSY_CODES.has(err?.code)
|
|
97
|
+
|
|
98
|
+
async function whileBusy(operation) {
|
|
99
|
+
for (const wait of BUSY_WAITS_MS) {
|
|
100
|
+
try {
|
|
101
|
+
return await operation()
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (!isBusy(err)) throw err
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, wait))
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return operation()
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
async function readState() {
|
|
94
111
|
try {
|
|
95
|
-
const raw = migrate(JSON.parse(await fsp.readFile(STATE_FILE, 'utf8')))
|
|
112
|
+
const raw = migrate(JSON.parse(await whileBusy(() => fsp.readFile(STATE_FILE, 'utf8'))))
|
|
96
113
|
return {
|
|
97
114
|
version: STATE_VERSION,
|
|
98
115
|
archived: asArray(raw.archived),
|
|
@@ -109,7 +126,8 @@ async function readState() {
|
|
|
109
126
|
settings: raw.settings && typeof raw.settings === 'object' ? raw.settings : null,
|
|
110
127
|
updatedAt: Number(raw.updatedAt) || 0,
|
|
111
128
|
}
|
|
112
|
-
} catch {
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (isBusy(err)) throw err
|
|
113
131
|
return emptyState()
|
|
114
132
|
}
|
|
115
133
|
}
|
|
@@ -154,7 +172,7 @@ async function writeState(next) {
|
|
|
154
172
|
// colony.json names the repos you work in and where they are: the owner's business, not
|
|
155
173
|
// every account on the machine's. A fresh file per write, so the mode always applies.
|
|
156
174
|
await fsp.writeFile(tmp, JSON.stringify(state, null, 2), { mode: 0o600 })
|
|
157
|
-
await fsp.rename(tmp, STATE_FILE)
|
|
175
|
+
await whileBusy(() => fsp.rename(tmp, STATE_FILE))
|
|
158
176
|
} catch (err) {
|
|
159
177
|
await fsp.rm(tmp, { force: true }).catch(() => {})
|
|
160
178
|
throw err
|
|
@@ -477,7 +495,7 @@ export async function middleware(req, res, next) {
|
|
|
477
495
|
if (url.pathname === '/api/state' && req.method === 'PUT') {
|
|
478
496
|
const body = await readJsonBody(req)
|
|
479
497
|
const base = Number(body.baseUpdatedAt) || 0
|
|
480
|
-
return serialise(async () => {
|
|
498
|
+
return await serialise(async () => {
|
|
481
499
|
const current = await readState()
|
|
482
500
|
if (base && current.updatedAt !== base) return send(res, 409, current)
|
|
483
501
|
return send(res, 200, await writeState(body))
|
|
@@ -535,6 +553,7 @@ export async function middleware(req, res, next) {
|
|
|
535
553
|
|
|
536
554
|
return send(res, 404, { error: 'Unknown endpoint' })
|
|
537
555
|
} catch (err) {
|
|
556
|
+
if (isBusy(err)) return send(res, 503, { error: BUSY_MESSAGE })
|
|
538
557
|
return send(res, 500, { error: String(err && err.message ? err.message : err) })
|
|
539
558
|
}
|
|
540
559
|
}
|