4e21ca937a
Deploy Documentation / deploy (push) Has been cancelled
Canary / Prepare Version (push) Has been cancelled
Canary / build-cli (push) Has been cancelled
Canary / Upload Install Script (push) Has been cancelled
Canary / bundle-desktop (push) Has been cancelled
Canary / bundle-desktop-intel (push) Has been cancelled
Canary / bundle-desktop-linux (push) Has been cancelled
Canary / bundle-desktop-windows (push) Has been cancelled
Canary / bundle-desktop-windows-cuda (push) Has been cancelled
Canary / Release (push) Has been cancelled
Unused Dependencies / machete (push) Has been cancelled
CI / changes (push) Has been cancelled
CI / Check Rust Code Format (push) Has been cancelled
CI / Build and Test Rust Project (push) Has been cancelled
CI / Build Rust Project on Windows (push) Has been cancelled
CI / Check MSRV (push) Has been cancelled
CI / Lint Rust Code (push) Has been cancelled
CI / Check Generated Schemas are Up-to-Date (push) Has been cancelled
CI / Test and Lint Electron Desktop App (push) Has been cancelled
CI / H5 Plaza Tests and Build (push) Has been cancelled
Live Provider Tests / check-fork (push) Has been cancelled
Live Provider Tests / changes (push) Has been cancelled
Live Provider Tests / Build Binary (push) Has been cancelled
Live Provider Tests / Smoke Tests (push) Has been cancelled
Live Provider Tests / Smoke Tests (Code Execution) (push) Has been cancelled
Live Provider Tests / Compaction Tests (push) Has been cancelled
Live Provider Tests / goose server HTTP integration tests (push) Has been cancelled
Publish Ask AI Bot Docker Image / docker (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Fork goose with custom MCP widgets, platform extensions (aider, git, web, search), MindSpace H5 backend/frontend, Plaza/Ops UIs, and deploy scripts for tkmind.cn. Co-authored-by: Cursor <cursoragent@cursor.com>
117 lines
3.9 KiB
JavaScript
117 lines
3.9 KiB
JavaScript
import fs from 'node:fs';
|
|
import fsPromises from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { ensureHtmlThumbnail } from './mindspace-thumbnails.mjs';
|
|
|
|
export function workspaceThumbnailRelativePath(htmlRelativePath) {
|
|
const normalized = String(htmlRelativePath ?? '').replace(/^\/+/, '');
|
|
const dir = path.posix.dirname(normalized);
|
|
const base = path.basename(normalized, path.extname(normalized));
|
|
const thumb = `${base}.thumbnail.svg`;
|
|
return dir === '.' ? thumb : path.posix.join(dir, thumb);
|
|
}
|
|
|
|
export function workspaceThumbnailAbsolutePath(publishDir, htmlRelativePath) {
|
|
return path.join(publishDir, workspaceThumbnailRelativePath(htmlRelativePath));
|
|
}
|
|
|
|
export async function ensureWorkspaceHtmlThumbnail(publishDir, htmlRelativePath, html, meta = {}) {
|
|
const htmlPath = path.join(publishDir, htmlRelativePath);
|
|
const content = html ?? (await fsPromises.readFile(htmlPath, 'utf8'));
|
|
const thumbRel = workspaceThumbnailRelativePath(htmlRelativePath);
|
|
return ensureHtmlThumbnail(publishDir, thumbRel, content, {
|
|
...meta,
|
|
contentBaseDir: path.dirname(htmlPath),
|
|
});
|
|
}
|
|
|
|
export async function readWorkspaceThumbnailIfExists(publishDir, htmlRelativePath) {
|
|
try {
|
|
return await fsPromises.readFile(workspaceThumbnailAbsolutePath(publishDir, htmlRelativePath), 'utf8');
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function scanPublishTree(publishRoot) {
|
|
if (!fs.existsSync(publishRoot)) return;
|
|
const entries = await fsPromises.readdir(publishRoot, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
if (!entry.isDirectory() || entry.name === 'wiki' || entry.name.startsWith('.')) continue;
|
|
const userDir = path.join(publishRoot, entry.name);
|
|
await scanUserHtmlFiles(userDir);
|
|
}
|
|
}
|
|
|
|
async function scanUserHtmlFiles(userDir) {
|
|
const walk = async (dir) => {
|
|
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const full = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
if (entry.name === '.agents' || entry.name === 'node_modules') continue;
|
|
await walk(full);
|
|
continue;
|
|
}
|
|
if (!entry.name.endsWith('.html') || entry.name.endsWith('.thumbnail.svg')) continue;
|
|
const rel = path.relative(userDir, full);
|
|
await ensureWorkspaceHtmlThumbnail(userDir, rel).catch(() => {});
|
|
}
|
|
};
|
|
await walk(userDir);
|
|
}
|
|
|
|
export function startWorkspaceThumbnailWatcher(publishRoot) {
|
|
if (!fs.existsSync(publishRoot)) {
|
|
fs.mkdirSync(publishRoot, { recursive: true });
|
|
}
|
|
|
|
void scanPublishTree(publishRoot);
|
|
|
|
const pending = new Map();
|
|
const schedule = (userDir, relativePath) => {
|
|
const key = path.join(userDir, relativePath);
|
|
const existing = pending.get(key);
|
|
if (existing) clearTimeout(existing);
|
|
pending.set(
|
|
key,
|
|
setTimeout(() => {
|
|
pending.delete(key);
|
|
void ensureWorkspaceHtmlThumbnail(userDir, relativePath).catch(() => {});
|
|
}, 400),
|
|
);
|
|
};
|
|
|
|
const attachUserWatcher = (userDir) => {
|
|
if (!fs.existsSync(userDir)) return;
|
|
void scanUserHtmlFiles(userDir);
|
|
try {
|
|
fs.watch(userDir, { recursive: true }, (_event, filename) => {
|
|
if (!filename || !String(filename).endsWith('.html')) return;
|
|
if (String(filename).endsWith('.thumbnail.svg')) return;
|
|
schedule(userDir, filename);
|
|
});
|
|
} catch {
|
|
// Some platforms disallow recursive watch; startup scan still runs.
|
|
}
|
|
};
|
|
|
|
for (const entry of fs.readdirSync(publishRoot, { withFileTypes: true })) {
|
|
if (entry.isDirectory() && entry.name !== 'wiki' && !entry.name.startsWith('.')) {
|
|
attachUserWatcher(path.join(publishRoot, entry.name));
|
|
}
|
|
}
|
|
|
|
try {
|
|
fs.watch(publishRoot, (_event, filename) => {
|
|
if (!filename) return;
|
|
const userDir = path.join(publishRoot, filename);
|
|
if (fs.existsSync(userDir) && fs.statSync(userDir).isDirectory()) {
|
|
attachUserWatcher(userDir);
|
|
}
|
|
});
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}
|