feat: simplify code project creation

This commit is contained in:
inman
2026-09-07 10:06:37 +08:00
parent f8eee430f4
commit af13aca003
15 changed files with 351 additions and 486 deletions

View File

@@ -211,21 +211,7 @@ export class CodingProjectService {
);
}
}
const config = await this.readCurrentConfig(projectPath);
if (!config) {
throw new CodingProjectServiceError(
409,
'CODING_PROJECT_CONFIG_INVALID',
'Coding project configuration is unavailable',
);
}
if (!config.projectId) {
throw new CodingProjectServiceError(
409,
'CODING_PROJECT_IDENTITY_REQUIRED',
'A durable coding project identity is required',
);
}
const config = (await this.getConfig(project.id)).config;
return { project, path: projectPath, projectId: config.projectId };
}
@@ -335,19 +321,10 @@ export class CodingProjectService {
async getConfig(projectId?: string): Promise<CodingProjectConfigSnapshot> {
const project = projectId ? await this.getProject(projectId) : await this.requireActiveProject();
const config = await this.readCurrentConfig(project.path);
if (!config) {
throw new CodingProjectServiceError(
409,
'CODING_PROJECT_CONFIG_INVALID',
'Coding project configuration is unavailable',
);
}
return {
project,
config,
knowledgeFiles: await this.listKnowledgeFiles(project.path),
};
const config = await this.requireCurrentConfig(project);
return config.projectId
? await this.projectConfigSnapshot(project, config)
: await this.ensureProjectIdentity(project);
}
async saveConfig(projectId: string, value: unknown): Promise<CodingProjectConfigSnapshot> {
@@ -379,8 +356,8 @@ export class CodingProjectService {
): Promise<CodingProjectConfigSnapshot> {
const project = await this.getProject(localProjectId);
return await this.serializeIdentityTransition(project.path, async () => {
const current = await this.getConfig(project.id);
if (current.config.projectId !== undefined) {
const current = await this.requireCurrentConfig(project);
if (current.projectId !== undefined) {
throw new CodingProjectServiceError(
409,
'CODING_PROJECT_IDENTITY_IMMUTABLE',
@@ -397,7 +374,7 @@ export class CodingProjectService {
nextProjectId,
);
const next = {
...current.config,
...current,
projectId: nextProjectId,
updatedAt: this.options.now?.() ?? new Date().toISOString(),
};
@@ -407,11 +384,7 @@ export class CodingProjectService {
storageFailure(error);
}
await this.options.onResourcesChanged?.(project);
return {
project: current.project,
config: next,
knowledgeFiles: await this.listKnowledgeFiles(current.project.path),
};
return await this.projectConfigSnapshot(project, next);
});
}
@@ -428,8 +401,8 @@ export class CodingProjectService {
}
const project = await this.getProject(localProjectId);
return await this.serializeIdentityTransition(project.path, async () => {
const current = await this.getConfig(project.id);
if (current.config.projectId === undefined) {
const current = await this.requireCurrentConfig(project);
if (current.projectId === undefined) {
throw new CodingProjectServiceError(
409,
'CODING_PROJECT_IDENTITY_REQUIRED',
@@ -442,11 +415,11 @@ export class CodingProjectService {
);
await this.options.onProjectIdentityChanging?.(
project,
current.config.projectId,
current.projectId,
nextProjectId,
);
const next = {
...current.config,
...current,
projectId: nextProjectId,
updatedAt: this.options.now?.() ?? new Date().toISOString(),
};
@@ -456,11 +429,7 @@ export class CodingProjectService {
storageFailure(error);
}
await this.options.onResourcesChanged?.(project);
return {
project: current.project,
config: next,
knowledgeFiles: await this.listKnowledgeFiles(current.project.path),
};
return await this.projectConfigSnapshot(project, next);
});
}
@@ -540,6 +509,61 @@ export class CodingProjectService {
});
}
private async ensureProjectIdentity(
project: CodingProject,
): Promise<CodingProjectConfigSnapshot> {
return await this.serializeIdentityTransition(project.path, async () => {
const current = await this.requireCurrentConfig(project);
if (current.projectId) {
return await this.projectConfigSnapshot(project, current);
}
const nextProjectId = resolveProjectIdentity(
{ kind: 'create' },
this.options.createProjectId ?? randomUUID,
);
await this.options.onProjectIdentityChanging?.(
project,
undefined,
nextProjectId,
);
const next = {
...current,
projectId: nextProjectId,
updatedAt: this.options.now?.() ?? new Date().toISOString(),
};
try {
await (this.options.writeConfig ?? writeCodingProjectConfigV2)(project.path, next);
} catch (error) {
storageFailure(error);
}
await this.options.onResourcesChanged?.(project);
return await this.projectConfigSnapshot(project, next);
});
}
private async requireCurrentConfig(project: CodingProject): Promise<CodingProjectConfigV2> {
const config = await this.readCurrentConfig(project.path);
if (!config) {
throw new CodingProjectServiceError(
409,
'CODING_PROJECT_CONFIG_INVALID',
'Coding project configuration is unavailable',
);
}
return config;
}
private async projectConfigSnapshot(
project: CodingProject,
config: CodingProjectConfigV2,
): Promise<CodingProjectConfigSnapshot> {
return {
project,
config,
knowledgeFiles: await this.listKnowledgeFiles(project.path),
};
}
private async listKnowledgeFiles(projectPath: string): Promise<string[]> {
try {
const entries = await readdir(path.join(projectPath, 'knowledge'), { withFileTypes: true });