@venturewild/workspace 0.6.9 → 0.6.11
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/package.json
CHANGED
package/server/src/index.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
import { PairingStore } from './pairing.mjs';
|
|
34
34
|
import { verifyGoogleVouch, emailMatches } from './google-vouch.mjs';
|
|
35
35
|
import { listDir, readFile, fullTree, workspaceSummary, safeResolve } from './fs.mjs';
|
|
36
|
-
import { browseDir, browseRoots } from './lobby-browse.mjs';
|
|
36
|
+
import { browseDir, browseRoots, listDrives } from './lobby-browse.mjs';
|
|
37
37
|
import { InboxWatcher } from './inbox.mjs';
|
|
38
38
|
import { ActivityBus } from './activity.mjs';
|
|
39
39
|
import { createWorkspacePresence } from './workspace-presence.mjs';
|
|
@@ -2194,7 +2194,11 @@ export async function createServer(overrides = {}) {
|
|
|
2194
2194
|
const reqPath = c.req.query('path');
|
|
2195
2195
|
try {
|
|
2196
2196
|
const listing = browseDir(reqPath && reqPath.trim() ? reqPath.trim() : home);
|
|
2197
|
-
return c.json({
|
|
2197
|
+
return c.json({
|
|
2198
|
+
...listing,
|
|
2199
|
+
roots: browseRoots({ home, workspacesHome }),
|
|
2200
|
+
drives: listDrives(), // "This PC" — jump to any drive (D:, E:, …)
|
|
2201
|
+
});
|
|
2198
2202
|
} catch (e) {
|
|
2199
2203
|
return c.json({ error: e.code || 'browse_failed', message: String(e.message || e) }, 400);
|
|
2200
2204
|
}
|
|
@@ -3047,19 +3051,35 @@ export async function createServer(overrides = {}) {
|
|
|
3047
3051
|
});
|
|
3048
3052
|
|
|
3049
3053
|
// --- frontend bundle (built by `npm run build:web`) ---
|
|
3054
|
+
// Cache strategy so an AUTO-UPDATED server's new UI is picked up WITHOUT a hard
|
|
3055
|
+
// refresh (the whole point of "seamless like OneDrive"): the SPA shell
|
|
3056
|
+
// (index.html) must always be revalidated — `no-cache` — so the browser never
|
|
3057
|
+
// keeps loading a stale bundle reference after the server updates. Vite's
|
|
3058
|
+
// /assets/* files are CONTENT-HASHED (a new build = a new filename), so they're
|
|
3059
|
+
// safe to cache forever (`immutable`). Without this, index.html had NO cache
|
|
3060
|
+
// headers and browsers heuristically cached it, stranding users on the old UI.
|
|
3050
3061
|
if (existsSync(config.webDir)) {
|
|
3051
3062
|
app.use(
|
|
3052
3063
|
'/*',
|
|
3053
3064
|
serveStatic({
|
|
3054
3065
|
root: path.relative(process.cwd(), config.webDir),
|
|
3066
|
+
onFound: (filePath, c) => {
|
|
3067
|
+
// Separator-agnostic (Windows serves backslash paths to onFound).
|
|
3068
|
+
if (/[\\/]assets[\\/]/.test(filePath)) {
|
|
3069
|
+
c.header('Cache-Control', 'public, max-age=31536000, immutable');
|
|
3070
|
+
} else {
|
|
3071
|
+
// index.html + any other top-level shell file → always revalidate.
|
|
3072
|
+
c.header('Cache-Control', 'no-cache');
|
|
3073
|
+
}
|
|
3074
|
+
},
|
|
3055
3075
|
}),
|
|
3056
3076
|
);
|
|
3057
|
-
// SPA fallback
|
|
3077
|
+
// SPA fallback (client-side routes) — same no-cache shell rule.
|
|
3058
3078
|
app.notFound((c) => {
|
|
3059
3079
|
const indexHtmlPath = path.join(config.webDir, 'index.html');
|
|
3060
3080
|
if (existsSync(indexHtmlPath)) {
|
|
3061
3081
|
return new Response(readFileSync(indexHtmlPath), {
|
|
3062
|
-
headers: { 'content-type': 'text/html' },
|
|
3082
|
+
headers: { 'content-type': 'text/html', 'cache-control': 'no-cache' },
|
|
3063
3083
|
});
|
|
3064
3084
|
}
|
|
3065
3085
|
return c.text('wild-workspace: frontend not built; run `npm run build`', 200);
|
|
@@ -22,6 +22,24 @@ function isDirSafe(p) {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// The filesystem roots ("This PC"): on Windows each drive (C:\, D:\, …) is a
|
|
26
|
+
// SEPARATE root with no common parent — without this, the picker is trapped on
|
|
27
|
+
// the home drive (you could never browse to D:). We probe C–Z (A/B are legacy
|
|
28
|
+
// floppies; probing them can stall on some setups) and keep the ones that exist.
|
|
29
|
+
// On POSIX there's a single root, `/`.
|
|
30
|
+
export function listDrives() {
|
|
31
|
+
if (process.platform !== 'win32') {
|
|
32
|
+
return isDirSafe('/') ? [{ name: '/', dir: '/' }] : [];
|
|
33
|
+
}
|
|
34
|
+
const drives = [];
|
|
35
|
+
for (let code = 'C'.charCodeAt(0); code <= 'Z'.charCodeAt(0); code += 1) {
|
|
36
|
+
const letter = String.fromCharCode(code);
|
|
37
|
+
const root = `${letter}:\\`;
|
|
38
|
+
if (isDirSafe(root)) drives.push({ name: `${letter}:`, dir: root });
|
|
39
|
+
}
|
|
40
|
+
return drives;
|
|
41
|
+
}
|
|
42
|
+
|
|
25
43
|
// Quick-access roots offered beside the listing — only the ones that actually
|
|
26
44
|
// exist, de-duped by resolved path (Home/Desktop/Documents can coincide).
|
|
27
45
|
export function browseRoots({ home, workspacesHome } = {}) {
|
|
@@ -77,7 +95,10 @@ export function browseDir(target) {
|
|
|
77
95
|
}
|
|
78
96
|
const entries = dirents
|
|
79
97
|
.filter((d) => {
|
|
80
|
-
if (d.name.startsWith('.')) return false; // hidden / dotfolders
|
|
98
|
+
if (d.name.startsWith('.')) return false; // hidden / dotfolders (POSIX + tooling)
|
|
99
|
+
// Windows system clutter that shows at every drive root ($RECYCLE.BIN,
|
|
100
|
+
// $WinREAgent, System Volume Information) — not pickable workspaces.
|
|
101
|
+
if (d.name.startsWith('$') || d.name === 'System Volume Information') return false;
|
|
81
102
|
if (d.isDirectory()) return true;
|
|
82
103
|
// Follow symlinks: a link to a directory is still a pickable folder.
|
|
83
104
|
if (d.isSymbolicLink()) return isDirSafe(path.join(abs, d.name));
|