This commit is contained in:
2026-02-22 01:50:15 +00:00
parent 23b369c7e1
commit ab03b175e7
40 changed files with 829 additions and 956 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
export type KeyMapping = { file: string; dir?: string };
export type ResolveDirs = { wsDir: string; screensDir: string };
export type ResolveDirs = { wsDir: string; screensDir: string; projDir: string };
+65 -1
View File
@@ -1,6 +1,6 @@
import { mkdir, readdir, rename, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { getUserWorkspacesDir, getUserHomepageWorkspaceDir, getUserStateFile } from '@@/data-path';
import { getUserWorkspacesDir, getUserHomepageWorkspaceDir, getUserStateFile, getUserProjectsDir } from '@@/data-path';
import type { KeyMapping, ResolveDirs } from './types';
const RESERVED_DIRS = new Set(['screens']);
@@ -39,6 +39,35 @@ export function resolveKey(dirs: ResolveDirs, key: string): KeyMapping | null {
return { file: join(dir, 'host-terminals.json'), dir };
}
// Project keys — stored in {projDir}/{slug}/.officerdev/
const projMetaMatch = key.match(/^proj-meta-(.+)$/);
if (projMetaMatch) {
const slug = projMetaMatch[1]!;
const dir = join(dirs.projDir, slug, '.officerdev');
return { file: join(dir, 'meta.json'), dir };
}
const projLayoutMatch = key.match(/^proj-layout-(.+)$/);
if (projLayoutMatch) {
const slug = projLayoutMatch[1]!;
const dir = join(dirs.projDir, slug, '.officerdev');
return { file: join(dir, 'layout.json'), dir };
}
const projTerminalsMatch = key.match(/^proj-terminals-(.+)$/);
if (projTerminalsMatch) {
const slug = projTerminalsMatch[1]!;
const dir = join(dirs.projDir, slug, '.officerdev');
return { file: join(dir, 'terminals.json'), dir };
}
const projHostTerminalsMatch = key.match(/^proj-host-terminals-(.+)$/);
if (projHostTerminalsMatch) {
const slug = projHostTerminalsMatch[1]!;
const dir = join(dirs.projDir, slug, '.officerdev');
return { file: join(dir, 'host-terminals.json'), dir };
}
return null;
}
@@ -125,6 +154,23 @@ async function readWorkspaceDir(dirPath: string, id: string, result: Record<stri
if (hostTerminals !== null) result[`ws-host-terminals-${id}`] = hostTerminals;
}
async function readProjectDir(projectPath: string, slug: string, result: Record<string, unknown>): Promise<unknown | null> {
const officerdevDir = join(projectPath, '.officerdev');
const meta = await readJsonFile(join(officerdevDir, 'meta.json'));
if (!meta) return null;
const layout = await readJsonFile(join(officerdevDir, 'layout.json'));
if (layout !== null) result[`proj-layout-${slug}`] = layout;
const terminals = await readJsonFile(join(officerdevDir, 'terminals.json'));
if (terminals !== null) result[`proj-terminals-${slug}`] = terminals;
const hostTerminals = await readJsonFile(join(officerdevDir, 'host-terminals.json'));
if (hostTerminals !== null) result[`proj-host-terminals-${slug}`] = hostTerminals;
return { ...(meta as object), id: slug, cwd: `/Projects/${slug}` };
}
async function readScreensDir(screensDir: string, result: Record<string, unknown>) {
let entries: import('node:fs').Dirent[] = [];
try {
@@ -168,6 +214,23 @@ export async function readAllWorkspacesState(dirs: ResolveDirs): Promise<Record<
await readWorkspaceDir(join(dirs.wsDir, entry.name), entry.name, result);
}
// Read project data — scan directories containing .officerdev/meta.json
const projects: unknown[] = [];
let projEntries: import('node:fs').Dirent[] = [];
try {
projEntries = await readdir(dirs.projDir, { withFileTypes: true });
} catch {
result['projects'] = projects;
return result;
}
for (const entry of projEntries) {
if (!entry.isDirectory()) continue;
const meta = await readProjectDir(join(dirs.projDir, entry.name), entry.name, result);
if (meta) projects.push(meta);
}
result['projects'] = projects;
return result;
}
@@ -175,5 +238,6 @@ export function getDirs(email: string): ResolveDirs {
return {
wsDir: getUserWorkspacesDir(email),
screensDir: join(getUserWorkspacesDir(email), 'screens'),
projDir: getUserProjectsDir(email),
};
}
+25 -8
View File
@@ -1,14 +1,7 @@
import { mkdir, readdir, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { createRouter } from '@@/create-router';
import {
getDirs,
migrateFromState,
migrateHomepageToScreens,
readAllWorkspacesState,
resolveKey,
writeJsonFile,
} from './utils';
import { getDirs, migrateFromState, migrateHomepageToScreens, readAllWorkspacesState, resolveKey, writeJsonFile } from './utils';
export const workspacesRouter = createRouter();
@@ -37,6 +30,30 @@ workspacesRouter.patch('/', async (ctx) => {
await mkdir(dirs.wsDir, { recursive: true });
for (const [key, value] of Object.entries(body)) {
// Handle proj-meta-{slug}: create/update/delete project
const projMetaMatch = key.match(/^proj-meta-(.+)$/);
if (projMetaMatch) {
const slug = projMetaMatch[1]!;
const projectDir = join(dirs.projDir, slug);
if (value === null) {
await rm(projectDir, { recursive: true, force: true });
continue;
}
const officerdevDir = join(projectDir, '.officerdev');
const metaFile = join(officerdevDir, 'meta.json');
const isNew = !(await Bun.file(metaFile).exists());
await mkdir(officerdevDir, { recursive: true });
await writeJsonFile(metaFile, value);
if (isNew) {
const proc = Bun.spawn(['git', 'init', projectDir]);
await proc.exited;
}
continue;
}
const mapping = resolveKey(dirs, key);
if (!mapping) continue;