centralu 0.1.0-beta.21 → 0.1.0-beta.23
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/bin/centralu.mjs +29 -6
- package/bin/platform.mjs +12 -0
- package/bin/semver.mjs +25 -0
- package/package.json +5 -5
package/bin/centralu.mjs
CHANGED
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
* being first exercised on someone's laptop.
|
|
24
24
|
*/
|
|
25
25
|
import { execFileSync, spawn } from 'node:child_process'
|
|
26
|
-
import { copyDiffers, isNewer } from './semver.mjs'
|
|
26
|
+
import { copyDiffers, isNewer, keepsInstalledCopy } from './semver.mjs'
|
|
27
27
|
import {
|
|
28
28
|
asideCopies,
|
|
29
|
+
bundleVersionCommand,
|
|
29
30
|
busyMessage,
|
|
30
31
|
earlyExitMessage,
|
|
31
32
|
executableIn,
|
|
@@ -65,7 +66,12 @@ const pkg = require('../package.json')
|
|
|
65
66
|
|
|
66
67
|
const APP_NAME = 'Centralu'
|
|
67
68
|
const BUNDLE = `${APP_NAME}.app`
|
|
68
|
-
|
|
69
|
+
/*
|
|
70
|
+
* `CENTRALU_INSTALLED_COPY` points `install` and the copy checks at another bundle path. It exists
|
|
71
|
+
* so the launcher's own tests can run `centralu install` against a temporary folder and never
|
|
72
|
+
* against the real /Applications (tooling/launcher-install.test.ts).
|
|
73
|
+
*/
|
|
74
|
+
const INSTALLED = process.env.CENTRALU_INSTALLED_COPY || `/Applications/${BUNDLE}`
|
|
69
75
|
|
|
70
76
|
const PLATFORM = process.platform
|
|
71
77
|
const HOME = homedir()
|
|
@@ -275,8 +281,22 @@ function queryRegistry(key) {
|
|
|
275
281
|
* **Never done quietly through postinstall.** Writing silently into someone's `/Applications`
|
|
276
282
|
* erodes trust, and pnpm blocks postinstall by default anyway. This is only run when the
|
|
277
283
|
* person explicitly asks for it.
|
|
284
|
+
*
|
|
285
|
+
* **Never over a newer local build** (#645), unless `--force`. The in-app update runs this after
|
|
286
|
+
* `npm i -g` with the same rule (`keepsInstalledCopy`): a maintainer's local build
|
|
287
|
+
* (`0.2.0-beta.21.local.1`) waiting for "Apply now" would otherwise be replaced by npm's beta.21.
|
|
288
|
+
* Any released version is replaced, even a higher one: npm is the source of truth for releases,
|
|
289
|
+
* and a release pulled from `latest` must not survive here. The check comes before finding the app
|
|
290
|
+
* package, since keeping the copy needs nothing from it. `--force` replaces a local build on purpose.
|
|
278
291
|
*/
|
|
279
|
-
function install() {
|
|
292
|
+
function install(force) {
|
|
293
|
+
const copy = installedCopyVersion()
|
|
294
|
+
if (!force && keepsInstalledCopy(pkg.version, copy)) {
|
|
295
|
+
const where = PLATFORM === 'win32' ? WIN.dir : INSTALLED
|
|
296
|
+
console.log(`${where} holds the local build ${copy}, newer than this package (${pkg.version}), and was left as it is.`)
|
|
297
|
+
console.log('To replace it anyway: centralu install --force')
|
|
298
|
+
return
|
|
299
|
+
}
|
|
280
300
|
const app = requireApp()
|
|
281
301
|
if (PLATFORM === 'win32') return installWindows(app)
|
|
282
302
|
if (PLATFORM !== 'darwin') return installDesktopEntry(app)
|
|
@@ -498,6 +518,7 @@ async function update() {
|
|
|
498
518
|
|
|
499
519
|
/**
|
|
500
520
|
* The version of the copy placed in /Applications. null if there is nothing to compare.
|
|
521
|
+
* (`bundleVersionCommand`: the in-app update reads it with the same command.)
|
|
501
522
|
*
|
|
502
523
|
* `defaults` is used because there is no guarantee Info.plist stays XML — Tauri just happens
|
|
503
524
|
* to write it as XML today, and if it ever switches to a binary plist, anything reading it
|
|
@@ -517,7 +538,8 @@ function installedCopyVersion() {
|
|
|
517
538
|
}
|
|
518
539
|
if (PLATFORM !== 'darwin' || !existsSync(INSTALLED)) return null
|
|
519
540
|
try {
|
|
520
|
-
const
|
|
541
|
+
const read = bundleVersionCommand(INSTALLED)
|
|
542
|
+
const out = execFileSync(read.command, read.args, {
|
|
521
543
|
encoding: 'utf8',
|
|
522
544
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
523
545
|
})
|
|
@@ -561,7 +583,8 @@ const HELP = `${APP_NAME} ${pkg.version}
|
|
|
561
583
|
|
|
562
584
|
centralu Launches the app
|
|
563
585
|
centralu install Registers it in the app list (macOS: /Applications, Linux: a menu entry,
|
|
564
|
-
Windows: %LOCALAPPDATA%\\Programs\\Centralu and a Start menu shortcut)
|
|
586
|
+
Windows: %LOCALAPPDATA%\\Programs\\Centralu and a Start menu shortcut).
|
|
587
|
+
A newer local build there is kept; --force replaces it anyway
|
|
565
588
|
centralu uninstall Removes that registration (conversation history is kept)
|
|
566
589
|
centralu update Upgrades if a new version is available
|
|
567
590
|
centralu serve Runs the host alone on this machine, for the app on another computer
|
|
@@ -573,7 +596,7 @@ Requires: Node 22+, and the claude or codex CLI (the app's first screen reports
|
|
|
573
596
|
const [cmd, ...rest] = process.argv.slice(2)
|
|
574
597
|
switch (cmd) {
|
|
575
598
|
case 'install':
|
|
576
|
-
install()
|
|
599
|
+
install(rest.includes('--force'))
|
|
577
600
|
break
|
|
578
601
|
case 'uninstall':
|
|
579
602
|
uninstall()
|
package/bin/platform.mjs
CHANGED
|
@@ -357,3 +357,15 @@ export function busyMessage(path) {
|
|
|
357
357
|
export function isBusyError(e) {
|
|
358
358
|
return e?.code === 'EBUSY' || e?.code === 'EPERM' || e?.code === 'EACCES' || e?.code === 'ENOTEMPTY'
|
|
359
359
|
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* The command that prints the version a macOS bundle carries (`CFBundleShortVersionString`).
|
|
363
|
+
*
|
|
364
|
+
* `plutil` reads XML and binary plists alike, so nothing breaks if Tauri ever writes a binary one.
|
|
365
|
+
* The in-app update runs the same command (`installedCopyVersionSource` in
|
|
366
|
+
* `packages/agent-host/src/os.ts`, pinned to this by `updates.test.ts`), so the host and the
|
|
367
|
+
* launcher can never disagree about which version a copy is (#645).
|
|
368
|
+
*/
|
|
369
|
+
export function bundleVersionCommand(bundle) {
|
|
370
|
+
return { command: '/usr/bin/plutil', args: ['-extract', 'CFBundleShortVersionString', 'raw', '-o', '-', posix.join(bundle, 'Contents', 'Info.plist')] }
|
|
371
|
+
}
|
package/bin/semver.mjs
CHANGED
|
@@ -82,3 +82,28 @@ function split(raw) {
|
|
|
82
82
|
const pre = dash === -1 ? null : v.slice(dash + 1)
|
|
83
83
|
return [core, pre]
|
|
84
84
|
}
|
|
85
|
+
|
|
86
|
+
const LOCAL_BUILD = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*\.local\.[1-9]\d*$/
|
|
87
|
+
|
|
88
|
+
/** A maintainer's local build: `<base>.local.N`, strict SemVer, with a prerelease base and a positive N (`scripts/local-manifest.mjs`). */
|
|
89
|
+
export function isLocalBuildVersion(v) {
|
|
90
|
+
return typeof v === 'string' && v.length <= 64 && LOCAL_BUILD.test(v)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Whether the copy `centralu install` made is kept rather than replaced by `target` (#645).
|
|
95
|
+
*
|
|
96
|
+
* npm is the source of truth for releases: any released version in the copy, even a higher one, is
|
|
97
|
+
* replaced by the target, so a release pulled from `latest` (a dist-tag rollback) does not survive in
|
|
98
|
+
* /Applications. The one exception is a maintainer's local build (`<base>.local.N`, `pnpm
|
|
99
|
+
* local-update`, #610/#613) newer than the target: it waits there for "Apply now", and putting the
|
|
100
|
+
* release it was cut from over it would undo it. The copy's version must be well-formed SemVer in
|
|
101
|
+
* that exact shape; anything unreadable or malformed (`garbage`, `$(VERSION)`, an empty file) is
|
|
102
|
+
* replaced, so a corrupt plist or version file can never block every later update.
|
|
103
|
+
*
|
|
104
|
+
* `centralu install` applies this unless `--force` is given; the in-app update applies the mirror in
|
|
105
|
+
* `packages/protocol/src/semver.ts` (pinned answer to answer by `tooling/launcher-semver.test.ts`).
|
|
106
|
+
*/
|
|
107
|
+
export function keepsInstalledCopy(target, copyVersion) {
|
|
108
|
+
return isLocalBuildVersion(copyVersion) && isNewer(copyVersion, target)
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "centralu",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.23",
|
|
4
4
|
"description": "Run, watch and steer several Claude Code and Codex CLI sessions from one window",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ijun17",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"win32"
|
|
38
38
|
],
|
|
39
39
|
"optionalDependencies": {
|
|
40
|
-
"@centralu/darwin-arm64": "0.1.0-beta.
|
|
41
|
-
"@centralu/linux-arm64": "0.1.0-beta.
|
|
42
|
-
"@centralu/linux-x64": "0.1.0-beta.
|
|
43
|
-
"@centralu/win32-x64": "0.1.0-beta.
|
|
40
|
+
"@centralu/darwin-arm64": "0.1.0-beta.23",
|
|
41
|
+
"@centralu/linux-arm64": "0.1.0-beta.23",
|
|
42
|
+
"@centralu/linux-x64": "0.1.0-beta.23",
|
|
43
|
+
"@centralu/win32-x64": "0.1.0-beta.23"
|
|
44
44
|
}
|
|
45
45
|
}
|