@centralu/darwin-arm64 0.1.0-beta.14 → 0.1.0-beta.15
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/Centralu.app/Contents/Info.plist +2 -2
- package/Centralu.app/Contents/MacOS/centralu +0 -0
- package/Centralu.app/Contents/MacOS/centralu-keeper +0 -0
- package/Centralu.app/Contents/Resources/content/centralu-keeper +0 -0
- package/Centralu.app/Contents/Resources/content/content-manifest.json +8 -8
- package/Centralu.app/Contents/Resources/content/content-manifest.json.sig +1 -1
- package/Centralu.app/Contents/Resources/content/host/bundle-info.json +2 -2
- package/Centralu.app/Contents/Resources/content/host/main.mjs +364 -55
- package/Centralu.app/Contents/Resources/content/host/remote-install.mjs +105 -19
- package/Centralu.app/Contents/Resources/resources/host/bundle-info.json +2 -2
- package/Centralu.app/Contents/Resources/resources/host/main.mjs +364 -55
- package/Centralu.app/Contents/Resources/resources/host/remote-install.mjs +105 -19
- package/Centralu.app/Contents/_CodeSignature/CodeResources +20 -20
- package/package.json +1 -1
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// not survive the way back from Windows; tunnel.ts). The sentences for the person are the hub's.
|
|
10
10
|
import { spawnSync } from 'node:child_process'
|
|
11
11
|
import { createHash, randomBytes } from 'node:crypto'
|
|
12
|
-
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from 'node:fs'
|
|
12
|
+
import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
13
13
|
import { homedir } from 'node:os'
|
|
14
|
-
import { join } from 'node:path'
|
|
14
|
+
import { basename, join } from 'node:path'
|
|
15
15
|
import { pathToFileURL } from 'node:url'
|
|
16
16
|
|
|
17
17
|
const WORD = /^[0-9A-Za-z][0-9A-Za-z.+-]{0,63}$/
|
|
@@ -86,27 +86,79 @@ export function prune(l) {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
sweep(l.versions, new Set(keep.map((p) => p.version)))
|
|
89
|
+
// What `placeVersion` moved aside and could not remove then
|
|
90
|
+
try {
|
|
91
|
+
for (const name of readdirSync(l.root).filter((n) => n.startsWith('.replaced-'))) {
|
|
92
|
+
try {
|
|
93
|
+
rmSync(join(l.root, name), { recursive: true, force: true })
|
|
94
|
+
} catch {
|
|
95
|
+
left.push(name)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {}
|
|
89
99
|
sweep(l.node, new Set(keep.map((p) => `v${p.node}`)))
|
|
90
100
|
return { removed, left }
|
|
91
101
|
}
|
|
92
102
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
/** A lock folder with no pid in it, younger than this, is held: its installer has made it and not yet written the pid */
|
|
104
|
+
const LOCK_YOUNG_MS = 60_000
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* One installer at a time per data folder; a lock whose process is gone is taken over.
|
|
108
|
+
*
|
|
109
|
+
* Taking it is `mkdir`, which only one installer can do; the pid follows. A lock found with no pid is
|
|
110
|
+
* held while it is young (another installer between the two, which reading it as pid 0 once took
|
|
111
|
+
* over), and stale only after `LOCK_YOUNG_MS` (an installer that died between them). A stale lock is
|
|
112
|
+
* taken over by moving it aside first, so of two installers that both found it stale only one removes
|
|
113
|
+
* it, and one that moved a lock just replaced by a live one puts it back. Unlock removes the lock only
|
|
114
|
+
* while it is still this run's own (its `token`).
|
|
115
|
+
*/
|
|
116
|
+
export function lock(l) {
|
|
117
|
+
const token = `${process.pid}-${randomBytes(8).toString('hex')}`
|
|
118
|
+
for (let i = 0; i < 3; i++) {
|
|
96
119
|
try {
|
|
97
120
|
mkdirSync(l.lock)
|
|
98
121
|
writeFileSync(join(l.lock, 'pid'), String(process.pid))
|
|
99
|
-
|
|
122
|
+
writeFileSync(join(l.lock, 'token'), token)
|
|
123
|
+
return () => {
|
|
124
|
+
if (readFileSafe(join(l.lock, 'token')) === token) rmSync(l.lock, { recursive: true, force: true })
|
|
125
|
+
}
|
|
100
126
|
} catch (err) {
|
|
101
127
|
if (err.code !== 'EEXIST') throw err
|
|
102
|
-
const pid = Number(readFileSafe(join(l.lock, 'pid')))
|
|
103
|
-
if (pid > 0 && alive(pid)) throw new Refusal('busy', String(pid))
|
|
104
|
-
rmSync(l.lock, { recursive: true, force: true })
|
|
105
128
|
}
|
|
129
|
+
const held = readFileSafe(join(l.lock, 'pid'))
|
|
130
|
+
if (!lockIsStale(l.lock, held)) throw new Refusal('busy', held.trim())
|
|
131
|
+
const aside = `${l.lock}.${token}.stale`
|
|
132
|
+
try {
|
|
133
|
+
renameSync(l.lock, aside)
|
|
134
|
+
} catch {
|
|
135
|
+
continue // gone already, or another installer moved it first
|
|
136
|
+
}
|
|
137
|
+
const moved = readFileSafe(join(aside, 'pid'))
|
|
138
|
+
if (moved !== held) {
|
|
139
|
+
// Replaced by a live lock between reading it and moving it: not ours to remove
|
|
140
|
+
try {
|
|
141
|
+
renameSync(aside, l.lock)
|
|
142
|
+
} catch {
|
|
143
|
+
// Someone holds a newer one; this one stays aside, named, and harmless
|
|
144
|
+
}
|
|
145
|
+
throw new Refusal('busy', moved.trim())
|
|
146
|
+
}
|
|
147
|
+
rmSync(aside, { recursive: true, force: true })
|
|
106
148
|
}
|
|
107
149
|
throw new Refusal('busy')
|
|
108
150
|
}
|
|
109
151
|
|
|
152
|
+
function lockIsStale(dir, pidText) {
|
|
153
|
+
const pid = Number(pidText.trim())
|
|
154
|
+
if (pidText.trim() && pid > 0) return !alive(pid)
|
|
155
|
+
try {
|
|
156
|
+
return Date.now() - statSync(dir).mtimeMs > LOCK_YOUNG_MS
|
|
157
|
+
} catch {
|
|
158
|
+
return true // gone: the next turn takes the lock
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
110
162
|
function readFileSafe(file) {
|
|
111
163
|
try {
|
|
112
164
|
return readFileSync(file, 'utf8')
|
|
@@ -144,10 +196,17 @@ export async function fetchChecked(pkg) {
|
|
|
144
196
|
return bytes
|
|
145
197
|
}
|
|
146
198
|
|
|
147
|
-
/**
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Which package brings the host, where the host sits in it, and what of it a remote never runs (plan
|
|
201
|
+
* S6). The host-only package (`@centralu/host-<platform>`) is the host and nothing else; a version
|
|
202
|
+
* published before it existed comes as its platform package, whose window is thrown away
|
|
203
|
+
*/
|
|
204
|
+
function hostParts(platform, packages) {
|
|
205
|
+
const hostOnly = `@centralu/host-${platform}`
|
|
206
|
+
if (packages.some((pkg) => pkg.name === hostOnly)) return { pkg: hostOnly, host: join('host', 'main.mjs'), drop: [] }
|
|
207
|
+
const pkg = `@centralu/${platform}`
|
|
208
|
+
if (platform === 'win32-x64') return { pkg, host: join('Centralu', 'resources', 'host', 'main.mjs'), drop: [join('Centralu', 'centralu.exe')] }
|
|
209
|
+
return { pkg, host: join('host', 'main.mjs'), drop: ['Centralu.AppImage'] }
|
|
151
210
|
}
|
|
152
211
|
|
|
153
212
|
function sameInstall(dir, p) {
|
|
@@ -159,6 +218,32 @@ function sameInstall(dir, p) {
|
|
|
159
218
|
}
|
|
160
219
|
}
|
|
161
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Puts the unpacked `partial` folder in place as `dir`. A `dir` already there (the same version
|
|
223
|
+
* installed again with other bytes, possibly the one `current` runs) is moved aside first and removed
|
|
224
|
+
* only once the new one is in place, and put back if that fails: removing it first would leave
|
|
225
|
+
* `current` naming nothing after a failed rename, and on Windows a removal can stop halfway on the
|
|
226
|
+
* files of a host running from it (moving that folder is allowed, plan §10.5). What cannot be removed
|
|
227
|
+
* stays aside under a dot name, and the next prune tries again
|
|
228
|
+
*/
|
|
229
|
+
export function placeVersion(l, partial, dir) {
|
|
230
|
+
const aside = existsSync(dir) ? join(l.root, `.replaced-${basename(dir)}-${process.pid}-${randomBytes(4).toString('hex')}`) : null
|
|
231
|
+
if (aside) renameSync(dir, aside)
|
|
232
|
+
try {
|
|
233
|
+
renameSync(partial, dir)
|
|
234
|
+
} catch (err) {
|
|
235
|
+
if (aside) renameSync(aside, dir)
|
|
236
|
+
throw err
|
|
237
|
+
}
|
|
238
|
+
if (aside) {
|
|
239
|
+
try {
|
|
240
|
+
rmSync(aside, { recursive: true, force: true })
|
|
241
|
+
} catch {
|
|
242
|
+
// A Windows program still running from it; harmless where it is
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
162
247
|
/** Installs `p.version` beside what is there, switches `current` to it, and keeps `previous` */
|
|
163
248
|
export async function install(p, { dataDir = process.env.CC_DATA_DIR || join(homedir(), '.centralu'), platform = process.platform } = {}) {
|
|
164
249
|
if (!WORD.test(p.version) || !WORD.test(p.node)) throw new Refusal('params', 'version')
|
|
@@ -167,7 +252,7 @@ export async function install(p, { dataDir = process.env.CC_DATA_DIR || join(hom
|
|
|
167
252
|
const unlock = lock(l)
|
|
168
253
|
try {
|
|
169
254
|
const dir = join(l.versions, p.version)
|
|
170
|
-
const parts =
|
|
255
|
+
const parts = hostParts(p.platform, p.packages)
|
|
171
256
|
if (!sameInstall(dir, p)) {
|
|
172
257
|
// Everything lands in a folder of its own first; the version's folder appears by one rename
|
|
173
258
|
const partial = join(l.root, `.partial-${p.version}-${process.pid}`)
|
|
@@ -183,7 +268,7 @@ export async function install(p, { dataDir = process.env.CC_DATA_DIR || join(hom
|
|
|
183
268
|
if (r.status !== 0) throw new Refusal('unpack', `${pkg.name}: ${(r.stderr || r.error?.message || '').trim().split('\n').at(-1)}`)
|
|
184
269
|
rmSync(file, { force: true })
|
|
185
270
|
}
|
|
186
|
-
const pkgDir = join(partial, 'node_modules', '
|
|
271
|
+
const pkgDir = join(partial, 'node_modules', ...parts.pkg.split('/'))
|
|
187
272
|
if (!existsSync(join(pkgDir, parts.host))) throw new Refusal('no_host', p.platform)
|
|
188
273
|
for (const d of parts.drop) rmSync(join(pkgDir, d), { force: true })
|
|
189
274
|
writeFileSync(
|
|
@@ -191,8 +276,7 @@ export async function install(p, { dataDir = process.env.CC_DATA_DIR || join(hom
|
|
|
191
276
|
JSON.stringify({ version: p.version, node: p.node, platform: p.platform, packages: p.packages.map(({ name, integrity }) => ({ name, integrity })), installedAt: new Date().toISOString(), by: p.hub ?? null }, null, 2) + '\n',
|
|
192
277
|
)
|
|
193
278
|
mkdirSync(l.versions, { recursive: true })
|
|
194
|
-
|
|
195
|
-
renameSync(partial, dir)
|
|
279
|
+
placeVersion(l, partial, dir)
|
|
196
280
|
} finally {
|
|
197
281
|
rmSync(partial, { recursive: true, force: true })
|
|
198
282
|
}
|
|
@@ -249,8 +333,10 @@ export async function setPointers(p, { dataDir = process.env.CC_DATA_DIR || join
|
|
|
249
333
|
return { current: null, previous: null }
|
|
250
334
|
}
|
|
251
335
|
const dir = join(l.versions, current.version)
|
|
252
|
-
|
|
253
|
-
|
|
336
|
+
// Both must be there: a `previous` naming nothing would offer a rollback to a version that is gone
|
|
337
|
+
for (const v of [current, previous].filter(Boolean)) {
|
|
338
|
+
if (!existsSync(join(l.versions, v.version, 'install.json')) || !existsSync(join(l.node, `v${v.node}`))) throw new Refusal('missing', `${v.version} ${v.node}`)
|
|
339
|
+
}
|
|
254
340
|
await ensureManagedLauncher(l, dir, current.version, platform)
|
|
255
341
|
if (previous) writeAtomic(l.previous, `${previous.version} ${previous.node}\n`)
|
|
256
342
|
else rmSync(l.previous, { force: true })
|
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
<dict>
|
|
7
7
|
<key>Resources/content/centralu-keeper</key>
|
|
8
8
|
<data>
|
|
9
|
-
|
|
9
|
+
S6PmDZBcltPbc+YqfMxnMC7Fzqg=
|
|
10
10
|
</data>
|
|
11
11
|
<key>Resources/content/content-manifest.json</key>
|
|
12
12
|
<data>
|
|
13
|
-
|
|
13
|
+
FVQzlGY+W0+fwrw5kHnevgtXdlE=
|
|
14
14
|
</data>
|
|
15
15
|
<key>Resources/content/content-manifest.json.sig</key>
|
|
16
16
|
<data>
|
|
17
|
-
|
|
17
|
+
M1moCCTklHFZgQKwIoA5ssQ95iY=
|
|
18
18
|
</data>
|
|
19
19
|
<key>Resources/content/host/app-template/AGENTS.md</key>
|
|
20
20
|
<data>
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
</data>
|
|
59
59
|
<key>Resources/content/host/bundle-info.json</key>
|
|
60
60
|
<data>
|
|
61
|
-
|
|
61
|
+
axEp9jRvgUmjCUzQreUbTsVqNx8=
|
|
62
62
|
</data>
|
|
63
63
|
<key>Resources/content/host/codex-orchestrator-bridge.mjs</key>
|
|
64
64
|
<data>
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
</data>
|
|
67
67
|
<key>Resources/content/host/main.mjs</key>
|
|
68
68
|
<data>
|
|
69
|
-
|
|
69
|
+
9rA/FpVT1/ZI3E9/g5DTFJYIDQU=
|
|
70
70
|
</data>
|
|
71
71
|
<key>Resources/content/host/node_modules/better-sqlite3/build/Release/better_sqlite3.node</key>
|
|
72
72
|
<data>
|
|
@@ -338,7 +338,7 @@
|
|
|
338
338
|
</data>
|
|
339
339
|
<key>Resources/content/host/remote-install.mjs</key>
|
|
340
340
|
<data>
|
|
341
|
-
|
|
341
|
+
yl0J+JffsEv7CSEYqn+zj2IPN6g=
|
|
342
342
|
</data>
|
|
343
343
|
<key>Resources/content/host/remote-runtime.json</key>
|
|
344
344
|
<data>
|
|
@@ -394,7 +394,7 @@
|
|
|
394
394
|
</data>
|
|
395
395
|
<key>Resources/resources/host/bundle-info.json</key>
|
|
396
396
|
<data>
|
|
397
|
-
|
|
397
|
+
axEp9jRvgUmjCUzQreUbTsVqNx8=
|
|
398
398
|
</data>
|
|
399
399
|
<key>Resources/resources/host/codex-orchestrator-bridge.mjs</key>
|
|
400
400
|
<data>
|
|
@@ -402,7 +402,7 @@
|
|
|
402
402
|
</data>
|
|
403
403
|
<key>Resources/resources/host/main.mjs</key>
|
|
404
404
|
<data>
|
|
405
|
-
|
|
405
|
+
9rA/FpVT1/ZI3E9/g5DTFJYIDQU=
|
|
406
406
|
</data>
|
|
407
407
|
<key>Resources/resources/host/node_modules/better-sqlite3/build/Release/better_sqlite3.node</key>
|
|
408
408
|
<data>
|
|
@@ -674,7 +674,7 @@
|
|
|
674
674
|
</data>
|
|
675
675
|
<key>Resources/resources/host/remote-install.mjs</key>
|
|
676
676
|
<data>
|
|
677
|
-
|
|
677
|
+
yl0J+JffsEv7CSEYqn+zj2IPN6g=
|
|
678
678
|
</data>
|
|
679
679
|
<key>Resources/resources/host/remote-runtime.json</key>
|
|
680
680
|
<data>
|
|
@@ -711,30 +711,30 @@
|
|
|
711
711
|
<dict>
|
|
712
712
|
<key>cdhash</key>
|
|
713
713
|
<data>
|
|
714
|
-
|
|
714
|
+
NnxR9qphJM40DeAOreaIeIxBijY=
|
|
715
715
|
</data>
|
|
716
716
|
<key>requirement</key>
|
|
717
|
-
<string>cdhash H"
|
|
717
|
+
<string>cdhash H"367c51f6aa6124ce340de00eade688788c418a36"</string>
|
|
718
718
|
</dict>
|
|
719
719
|
<key>Resources/content/centralu-keeper</key>
|
|
720
720
|
<dict>
|
|
721
721
|
<key>hash2</key>
|
|
722
722
|
<data>
|
|
723
|
-
|
|
723
|
+
9lKqLGO/xKRmsO1KsmG9paSS9FIZoNppZb9nW+MaW6U=
|
|
724
724
|
</data>
|
|
725
725
|
</dict>
|
|
726
726
|
<key>Resources/content/content-manifest.json</key>
|
|
727
727
|
<dict>
|
|
728
728
|
<key>hash2</key>
|
|
729
729
|
<data>
|
|
730
|
-
|
|
730
|
+
DZIWHDz+gm5QeQxBEuEDoEh3lncC5pHJPi8k09MhsKk=
|
|
731
731
|
</data>
|
|
732
732
|
</dict>
|
|
733
733
|
<key>Resources/content/content-manifest.json.sig</key>
|
|
734
734
|
<dict>
|
|
735
735
|
<key>hash2</key>
|
|
736
736
|
<data>
|
|
737
|
-
|
|
737
|
+
AkVZ05FH4plYtmB1G7zwZZFPIQKqqeImiB4pSpbT3wU=
|
|
738
738
|
</data>
|
|
739
739
|
</dict>
|
|
740
740
|
<key>Resources/content/host/app-template/AGENTS.md</key>
|
|
@@ -811,7 +811,7 @@
|
|
|
811
811
|
<dict>
|
|
812
812
|
<key>hash2</key>
|
|
813
813
|
<data>
|
|
814
|
-
|
|
814
|
+
nsmeQuwa+zGfJyZ6UZcK6nLkUPCz9FLQeFUYatLMctA=
|
|
815
815
|
</data>
|
|
816
816
|
</dict>
|
|
817
817
|
<key>Resources/content/host/codex-orchestrator-bridge.mjs</key>
|
|
@@ -825,7 +825,7 @@
|
|
|
825
825
|
<dict>
|
|
826
826
|
<key>hash2</key>
|
|
827
827
|
<data>
|
|
828
|
-
|
|
828
|
+
MX2TWPFloAHS/JrMVB4E8+a+iK6FeilesBB8lRkvOwY=
|
|
829
829
|
</data>
|
|
830
830
|
</dict>
|
|
831
831
|
<key>Resources/content/host/node_modules/better-sqlite3/build/Release/better_sqlite3.node</key>
|
|
@@ -1301,7 +1301,7 @@
|
|
|
1301
1301
|
<dict>
|
|
1302
1302
|
<key>hash2</key>
|
|
1303
1303
|
<data>
|
|
1304
|
-
|
|
1304
|
+
New4g3YdpbDtGTsBcScEixBPE/i5amL0KBDnwJoahDQ=
|
|
1305
1305
|
</data>
|
|
1306
1306
|
</dict>
|
|
1307
1307
|
<key>Resources/content/host/remote-runtime.json</key>
|
|
@@ -1399,7 +1399,7 @@
|
|
|
1399
1399
|
<dict>
|
|
1400
1400
|
<key>hash2</key>
|
|
1401
1401
|
<data>
|
|
1402
|
-
|
|
1402
|
+
nsmeQuwa+zGfJyZ6UZcK6nLkUPCz9FLQeFUYatLMctA=
|
|
1403
1403
|
</data>
|
|
1404
1404
|
</dict>
|
|
1405
1405
|
<key>Resources/resources/host/codex-orchestrator-bridge.mjs</key>
|
|
@@ -1413,7 +1413,7 @@
|
|
|
1413
1413
|
<dict>
|
|
1414
1414
|
<key>hash2</key>
|
|
1415
1415
|
<data>
|
|
1416
|
-
|
|
1416
|
+
MX2TWPFloAHS/JrMVB4E8+a+iK6FeilesBB8lRkvOwY=
|
|
1417
1417
|
</data>
|
|
1418
1418
|
</dict>
|
|
1419
1419
|
<key>Resources/resources/host/node_modules/better-sqlite3/build/Release/better_sqlite3.node</key>
|
|
@@ -1889,7 +1889,7 @@
|
|
|
1889
1889
|
<dict>
|
|
1890
1890
|
<key>hash2</key>
|
|
1891
1891
|
<data>
|
|
1892
|
-
|
|
1892
|
+
New4g3YdpbDtGTsBcScEixBPE/i5amL0KBDnwJoahDQ=
|
|
1893
1893
|
</data>
|
|
1894
1894
|
</dict>
|
|
1895
1895
|
<key>Resources/resources/host/remote-runtime.json</key>
|