fix(projects): open draft after adding project

This commit is contained in:
Bohdan Triapitsyn
2026-08-10 16:10:03 +03:00
parent bd4e7668fb
commit f9595cb80b
7 changed files with 94 additions and 10 deletions
@@ -88,7 +88,7 @@ This module provides OpenCode server integration utilities for the web server ru
- `GET /api/config/opencode-resolution`
- `POST /api/opencode/upgrade` (enforces the active runtime's upgrade capability, serializes supported OpenCode upgrades, then restarts managed OpenCode so the new binary is active)
- `GET /api/opencode/upgrade-status` (returns version availability plus the authoritative `upgrade.supported`, `upgrade.manager`, and `upgrade.reason` capability)
- `POST /api/opencode/directory`
- `POST /api/opencode/directory` (validates and activates an existing project directory; `{ create: true }` explicitly creates the requested project directory before activation, including outside the previously active workspace)
- `GET /api/provider/:providerId/source`
- `PUT /api/provider` (create/update custom OpenAI-compatible provider config in OpenCode user/project/custom layers via `scope`; secrets stay in auth via the OpenCode auth API)
- `DELETE /api/provider/:providerId/auth`
@@ -0,0 +1,45 @@
import { describe, expect, it, vi } from 'vitest';
import express from 'express';
import request from 'supertest';
import { registerOpenCodeRoutes } from './routes.js';
const createApp = (overrides = {}) => {
const app = express();
app.use(express.json());
const dependencies = {
fsPromises: { mkdir: vi.fn(async () => undefined) },
validateDirectoryPath: vi.fn(async (directory) => ({ ok: true, directory })),
readSettingsFromDisk: vi.fn(async () => ({ projects: [] })),
sanitizeProjects: (projects) => projects,
persistSettings: vi.fn(async (settings) => settings),
...overrides,
};
registerOpenCodeRoutes(app, dependencies);
return { app, dependencies };
};
describe('OpenCode project directory route', () => {
it('creates and activates a requested project outside the active workspace', async () => {
const { app, dependencies } = createApp();
const response = await request(app)
.post('/api/opencode/directory')
.send({ path: '/projects/testing-one', create: true })
.expect(200);
expect(dependencies.fsPromises.mkdir).toHaveBeenCalledWith('/projects/testing-one', { recursive: true });
expect(dependencies.validateDirectoryPath).toHaveBeenCalledWith('/projects/testing-one');
expect(response.body).toMatchObject({ success: true, path: '/projects/testing-one' });
});
it('does not create a directory for the existing activation flow', async () => {
const { app, dependencies } = createApp();
await request(app)
.post('/api/opencode/directory')
.send({ path: '/projects/existing' })
.expect(200);
expect(dependencies.fsPromises.mkdir).not.toHaveBeenCalled();
});
});
@@ -24,6 +24,7 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
refreshOpenCodeAfterConfigChange,
buildOpenCodeUrl,
getOpenCodeAuthHeaders,
fsPromises = fs.promises,
} = dependencies;
let authLibrary = null;
@@ -581,6 +582,10 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
return res.status(400).json({ error: 'Path is required' });
}
if (req.body?.create === true) {
await fsPromises.mkdir(path.resolve(requestedPath), { recursive: true });
}
const validated = await validateDirectoryPath(requestedPath);
if (!validated.ok) {
return res.status(400).json({ error: validated.error });