@openchamber/web 1.2.6 → 1.2.8
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 +10 -1
- package/bin/cli.js +274 -4
- package/dist/assets/{ToolOutputDialog-C4z4D1To.js → ToolOutputDialog-BYb4g1c7.js} +2 -2
- package/dist/assets/{index-Ch3--lhG.js → index--hIzFLog.js} +2 -2
- package/dist/assets/index-BSN-fGG5.css +1 -0
- package/dist/assets/main-DI52tBPC.js +118 -0
- package/dist/assets/{vendor-.pnpm-B7ko08Tb.js → vendor-.pnpm-Buk6Khtx.js} +305 -305
- package/dist/index.html +3 -3
- package/package.json +1 -1
- package/server/index.js +106 -0
- package/server/lib/package-manager.js +255 -0
- package/dist/assets/index-DR5HBLaO.css +0 -1
- package/dist/assets/main-DwC7ZQj_.js +0 -118
package/dist/index.html
CHANGED
|
@@ -157,10 +157,10 @@
|
|
|
157
157
|
pointer-events: none;
|
|
158
158
|
}
|
|
159
159
|
</style>
|
|
160
|
-
<script type="module" crossorigin src="/assets/index
|
|
161
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-.pnpm-
|
|
160
|
+
<script type="module" crossorigin src="/assets/index--hIzFLog.js"></script>
|
|
161
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-.pnpm-Buk6Khtx.js">
|
|
162
162
|
<link rel="stylesheet" crossorigin href="/assets/vendor--B3aGWKBE.css">
|
|
163
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
163
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BSN-fGG5.css">
|
|
164
164
|
</head>
|
|
165
165
|
<body class="h-full bg-background text-foreground">
|
|
166
166
|
<div id="root" class="h-full">
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -1605,6 +1605,112 @@ async function main(options = {}) {
|
|
|
1605
1605
|
|
|
1606
1606
|
app.use('/api', (req, res, next) => uiAuthController.requireAuth(req, res, next));
|
|
1607
1607
|
|
|
1608
|
+
app.get('/api/openchamber/update-check', async (_req, res) => {
|
|
1609
|
+
try {
|
|
1610
|
+
const { checkForUpdates } = await import('./lib/package-manager.js');
|
|
1611
|
+
const updateInfo = await checkForUpdates();
|
|
1612
|
+
res.json(updateInfo);
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
console.error('Failed to check for updates:', error);
|
|
1615
|
+
res.status(500).json({
|
|
1616
|
+
available: false,
|
|
1617
|
+
error: error instanceof Error ? error.message : 'Failed to check for updates',
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
});
|
|
1621
|
+
|
|
1622
|
+
app.post('/api/openchamber/update-install', async (_req, res) => {
|
|
1623
|
+
try {
|
|
1624
|
+
const { spawn: spawnChild } = await import('child_process');
|
|
1625
|
+
const {
|
|
1626
|
+
checkForUpdates,
|
|
1627
|
+
getUpdateCommand,
|
|
1628
|
+
detectPackageManager,
|
|
1629
|
+
} = await import('./lib/package-manager.js');
|
|
1630
|
+
|
|
1631
|
+
// Verify update is available
|
|
1632
|
+
const updateInfo = await checkForUpdates();
|
|
1633
|
+
if (!updateInfo.available) {
|
|
1634
|
+
return res.status(400).json({ error: 'No update available' });
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
const pm = detectPackageManager();
|
|
1638
|
+
const updateCmd = getUpdateCommand(pm);
|
|
1639
|
+
|
|
1640
|
+
// Get current server port for restart
|
|
1641
|
+
const currentPort = server.address()?.port || 3000;
|
|
1642
|
+
|
|
1643
|
+
// Try to read stored instance options for restart
|
|
1644
|
+
const tmpDir = os.tmpdir();
|
|
1645
|
+
const instanceFilePath = path.join(tmpDir, `openchamber-${currentPort}.json`);
|
|
1646
|
+
let storedOptions = { port: currentPort, daemon: true };
|
|
1647
|
+
try {
|
|
1648
|
+
const content = fs.readFileSync(instanceFilePath, 'utf8');
|
|
1649
|
+
storedOptions = JSON.parse(content);
|
|
1650
|
+
} catch {
|
|
1651
|
+
// Use defaults
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
// Build restart command with stored options
|
|
1655
|
+
let restartCmd = `openchamber serve --port ${storedOptions.port} --daemon`;
|
|
1656
|
+
if (storedOptions.uiPassword) {
|
|
1657
|
+
// Escape password for shell
|
|
1658
|
+
const escapedPw = storedOptions.uiPassword.replace(/'/g, "'\\''");
|
|
1659
|
+
restartCmd += ` --ui-password '${escapedPw}'`;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// Respond immediately - update will happen after response
|
|
1663
|
+
res.json({
|
|
1664
|
+
success: true,
|
|
1665
|
+
message: 'Update starting, server will restart shortly',
|
|
1666
|
+
version: updateInfo.version,
|
|
1667
|
+
packageManager: pm,
|
|
1668
|
+
});
|
|
1669
|
+
|
|
1670
|
+
// Give time for response to be sent
|
|
1671
|
+
setTimeout(() => {
|
|
1672
|
+
console.log(`\nInstalling update using ${pm}...`);
|
|
1673
|
+
console.log(`Running: ${updateCmd}`);
|
|
1674
|
+
|
|
1675
|
+
// Create a script that will:
|
|
1676
|
+
// 1. Wait for current process to exit
|
|
1677
|
+
// 2. Run the update
|
|
1678
|
+
// 3. Restart the server with original options
|
|
1679
|
+
const script = `
|
|
1680
|
+
sleep 2
|
|
1681
|
+
${updateCmd}
|
|
1682
|
+
if [ $? -eq 0 ]; then
|
|
1683
|
+
echo "Update successful, restarting OpenChamber..."
|
|
1684
|
+
${restartCmd}
|
|
1685
|
+
else
|
|
1686
|
+
echo "Update failed"
|
|
1687
|
+
exit 1
|
|
1688
|
+
fi
|
|
1689
|
+
`;
|
|
1690
|
+
|
|
1691
|
+
// Spawn detached shell to run update after we exit
|
|
1692
|
+
const child = spawnChild('sh', ['-c', script], {
|
|
1693
|
+
detached: true,
|
|
1694
|
+
stdio: 'ignore',
|
|
1695
|
+
env: process.env,
|
|
1696
|
+
});
|
|
1697
|
+
child.unref();
|
|
1698
|
+
|
|
1699
|
+
console.log('Update process spawned, shutting down server...');
|
|
1700
|
+
|
|
1701
|
+
// Give child process time to start, then exit
|
|
1702
|
+
setTimeout(() => {
|
|
1703
|
+
process.exit(0);
|
|
1704
|
+
}, 500);
|
|
1705
|
+
}, 500);
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
console.error('Failed to install update:', error);
|
|
1708
|
+
res.status(500).json({
|
|
1709
|
+
error: error instanceof Error ? error.message : 'Failed to install update',
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
});
|
|
1713
|
+
|
|
1608
1714
|
app.get('/api/openchamber/models-metadata', async (req, res) => {
|
|
1609
1715
|
const now = Date.now();
|
|
1610
1716
|
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
|
|
9
|
+
const PACKAGE_NAME = '@openchamber/web';
|
|
10
|
+
const NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}`;
|
|
11
|
+
const CHANGELOG_URL = 'https://raw.githubusercontent.com/btriapitsyn/openchamber/main/CHANGELOG.md';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect which package manager was used to install this package.
|
|
15
|
+
* Strategy:
|
|
16
|
+
* 1. Check npm_config_user_agent (set during npm/pnpm/yarn/bun install)
|
|
17
|
+
* 2. Check npm_execpath for PM binary path
|
|
18
|
+
* 3. Analyze package location path for PM-specific patterns
|
|
19
|
+
* 4. Fall back to npm
|
|
20
|
+
*/
|
|
21
|
+
export function detectPackageManager() {
|
|
22
|
+
// Strategy 1: Check user agent (most reliable during install)
|
|
23
|
+
const userAgent = process.env.npm_config_user_agent || '';
|
|
24
|
+
if (userAgent.startsWith('pnpm')) return 'pnpm';
|
|
25
|
+
if (userAgent.startsWith('yarn')) return 'yarn';
|
|
26
|
+
if (userAgent.startsWith('bun')) return 'bun';
|
|
27
|
+
if (userAgent.startsWith('npm')) return 'npm';
|
|
28
|
+
|
|
29
|
+
// Strategy 2: Check execpath
|
|
30
|
+
const execPath = process.env.npm_execpath || '';
|
|
31
|
+
if (execPath.includes('pnpm')) return 'pnpm';
|
|
32
|
+
if (execPath.includes('yarn')) return 'yarn';
|
|
33
|
+
if (execPath.includes('bun')) return 'bun';
|
|
34
|
+
|
|
35
|
+
// Strategy 3: Analyze package location for PM-specific patterns
|
|
36
|
+
try {
|
|
37
|
+
const pkgPath = path.resolve(__dirname, '..', '..');
|
|
38
|
+
if (pkgPath.includes('.pnpm')) return 'pnpm';
|
|
39
|
+
if (pkgPath.includes('/.yarn/') || pkgPath.includes('\\.yarn\\')) return 'yarn';
|
|
40
|
+
if (pkgPath.includes('/.bun/') || pkgPath.includes('\\.bun\\')) return 'bun';
|
|
41
|
+
} catch {
|
|
42
|
+
// Ignore path resolution errors
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Strategy 4: Check which PM binaries are available and preferred
|
|
46
|
+
const pmChecks = [
|
|
47
|
+
{ name: 'pnpm', check: () => isCommandAvailable('pnpm') },
|
|
48
|
+
{ name: 'yarn', check: () => isCommandAvailable('yarn') },
|
|
49
|
+
{ name: 'bun', check: () => isCommandAvailable('bun') },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
for (const { name, check } of pmChecks) {
|
|
53
|
+
if (check()) {
|
|
54
|
+
// Verify this PM actually has the package installed globally
|
|
55
|
+
if (isPackageInstalledWith(name)) {
|
|
56
|
+
return name;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return 'npm';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isCommandAvailable(command) {
|
|
65
|
+
try {
|
|
66
|
+
const result = spawnSync(command, ['--version'], {
|
|
67
|
+
encoding: 'utf8',
|
|
68
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
69
|
+
timeout: 5000,
|
|
70
|
+
});
|
|
71
|
+
return result.status === 0;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isPackageInstalledWith(pm) {
|
|
78
|
+
try {
|
|
79
|
+
let args;
|
|
80
|
+
switch (pm) {
|
|
81
|
+
case 'pnpm':
|
|
82
|
+
args = ['list', '-g', '--depth=0', PACKAGE_NAME];
|
|
83
|
+
break;
|
|
84
|
+
case 'yarn':
|
|
85
|
+
args = ['global', 'list', '--depth=0'];
|
|
86
|
+
break;
|
|
87
|
+
case 'bun':
|
|
88
|
+
args = ['pm', 'ls', '-g'];
|
|
89
|
+
break;
|
|
90
|
+
default:
|
|
91
|
+
args = ['list', '-g', '--depth=0', PACKAGE_NAME];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const result = spawnSync(pm, args, {
|
|
95
|
+
encoding: 'utf8',
|
|
96
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
97
|
+
timeout: 10000,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (result.status !== 0) return false;
|
|
101
|
+
return result.stdout.includes(PACKAGE_NAME) || result.stdout.includes('openchamber');
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get the update command for the detected package manager
|
|
109
|
+
*/
|
|
110
|
+
export function getUpdateCommand(pm = detectPackageManager()) {
|
|
111
|
+
switch (pm) {
|
|
112
|
+
case 'pnpm':
|
|
113
|
+
return `pnpm add -g ${PACKAGE_NAME}@latest`;
|
|
114
|
+
case 'yarn':
|
|
115
|
+
return `yarn global add ${PACKAGE_NAME}@latest`;
|
|
116
|
+
case 'bun':
|
|
117
|
+
return `bun add -g ${PACKAGE_NAME}@latest`;
|
|
118
|
+
default:
|
|
119
|
+
return `npm install -g ${PACKAGE_NAME}@latest`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get current installed version from package.json
|
|
125
|
+
*/
|
|
126
|
+
export function getCurrentVersion() {
|
|
127
|
+
try {
|
|
128
|
+
const pkgPath = path.resolve(__dirname, '..', '..', 'package.json');
|
|
129
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
130
|
+
return pkg.version || 'unknown';
|
|
131
|
+
} catch {
|
|
132
|
+
return 'unknown';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Fetch latest version from npm registry
|
|
138
|
+
*/
|
|
139
|
+
export async function getLatestVersion() {
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
142
|
+
headers: { Accept: 'application/json' },
|
|
143
|
+
signal: AbortSignal.timeout(10000),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
throw new Error(`Registry responded with ${response.status}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
return data['dist-tags']?.latest || null;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.warn('Failed to fetch latest version from npm:', error.message);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Parse semver version to numeric for comparison
|
|
160
|
+
*/
|
|
161
|
+
function parseVersion(version) {
|
|
162
|
+
const parts = version.replace(/^v/, '').split('.').map(Number);
|
|
163
|
+
return (parts[0] || 0) * 10000 + (parts[1] || 0) * 100 + (parts[2] || 0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Fetch changelog notes between versions
|
|
168
|
+
*/
|
|
169
|
+
export async function fetchChangelogNotes(fromVersion, toVersion) {
|
|
170
|
+
try {
|
|
171
|
+
const response = await fetch(CHANGELOG_URL, {
|
|
172
|
+
signal: AbortSignal.timeout(10000),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (!response.ok) return undefined;
|
|
176
|
+
|
|
177
|
+
const changelog = await response.text();
|
|
178
|
+
const sections = changelog.split(/^## /m).slice(1);
|
|
179
|
+
|
|
180
|
+
const fromNum = parseVersion(fromVersion);
|
|
181
|
+
const toNum = parseVersion(toVersion);
|
|
182
|
+
|
|
183
|
+
const relevantSections = sections.filter((section) => {
|
|
184
|
+
const match = section.match(/^\[(\d+\.\d+\.\d+)\]/);
|
|
185
|
+
if (!match) return false;
|
|
186
|
+
const ver = parseVersion(match[1]);
|
|
187
|
+
return ver > fromNum && ver <= toNum;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (relevantSections.length === 0) return undefined;
|
|
191
|
+
|
|
192
|
+
return relevantSections
|
|
193
|
+
.map((s) => '## ' + s.trim())
|
|
194
|
+
.join('\n\n');
|
|
195
|
+
} catch {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check for updates and return update info
|
|
202
|
+
*/
|
|
203
|
+
export async function checkForUpdates() {
|
|
204
|
+
const currentVersion = getCurrentVersion();
|
|
205
|
+
const latestVersion = await getLatestVersion();
|
|
206
|
+
|
|
207
|
+
if (!latestVersion || currentVersion === 'unknown') {
|
|
208
|
+
return {
|
|
209
|
+
available: false,
|
|
210
|
+
currentVersion,
|
|
211
|
+
error: 'Unable to determine versions',
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const currentNum = parseVersion(currentVersion);
|
|
216
|
+
const latestNum = parseVersion(latestVersion);
|
|
217
|
+
const available = latestNum > currentNum;
|
|
218
|
+
|
|
219
|
+
const pm = detectPackageManager();
|
|
220
|
+
|
|
221
|
+
let changelog;
|
|
222
|
+
if (available) {
|
|
223
|
+
changelog = await fetchChangelogNotes(currentVersion, latestVersion);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
available,
|
|
228
|
+
version: latestVersion,
|
|
229
|
+
currentVersion,
|
|
230
|
+
body: changelog,
|
|
231
|
+
packageManager: pm,
|
|
232
|
+
// Show our CLI command, not raw package manager command
|
|
233
|
+
updateCommand: 'openchamber update',
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Execute the update (used by CLI)
|
|
239
|
+
*/
|
|
240
|
+
export function executeUpdate(pm = detectPackageManager()) {
|
|
241
|
+
const command = getUpdateCommand(pm);
|
|
242
|
+
console.log(`Updating ${PACKAGE_NAME} using ${pm}...`);
|
|
243
|
+
console.log(`Running: ${command}`);
|
|
244
|
+
|
|
245
|
+
const [cmd, ...args] = command.split(' ');
|
|
246
|
+
const result = spawnSync(cmd, args, {
|
|
247
|
+
stdio: 'inherit',
|
|
248
|
+
shell: true,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
success: result.status === 0,
|
|
253
|
+
exitCode: result.status,
|
|
254
|
+
};
|
|
255
|
+
}
|