| 37 | }; |
| 38 | |
| 39 | export class HostWorkspaceResolver { |
| 40 | constructor( |
| 41 | private readonly catalog: Pick<ProjectCatalog, 'list' | 'touch'>, |
| 42 | private readonly membership: HostProjectMembershipGate, |
| 43 | private readonly onProjectChanged: () => void, |
| 44 | ) {} |
| 45 | |
| 46 | resolve(target: WorkspaceTarget): Promise<ResolvedWorkspace> { |
| 47 | return this.run(target, async (workspace) => workspace); |
| 48 | } |
| 49 | |
| 50 | run<Result>( |
| 51 | target: WorkspaceTarget, |
| 52 | operation: (workspace: ResolvedWorkspace) => Promise<Result>, |
| 53 | ): Promise<Result> { |
| 54 | return this.membership.run(async () => operation(await this.#resolve(target))); |
| 55 | } |
| 56 | |
| 57 | runWithUsageRecorded<Result>( |
| 58 | target: WorkspaceTarget, |
| 59 | operation: (workspace: ResolvedWorkspace) => Promise<Result>, |
| 60 | ): Promise<Result> { |
| 61 | return this.membership.run(async () => { |
| 62 | const workspace = await this.#resolve(target); |
| 63 | if (workspace.projectId !== null) { |
| 64 | try { |
| 65 | await this.catalog.touch(workspace.projectId, workspace.cwd); |
| 66 | } catch (error) { |
| 67 | if (error instanceof ProjectNotFoundError) { |
| 68 | throw new WorkspaceResolutionError('not_found', error.message); |
| 69 | } |
| 70 | if ( |
| 71 | error instanceof ProjectArchivedError || |
| 72 | error instanceof ProjectUnavailableError || |
| 73 | error instanceof ProjectPathMismatchError |
| 74 | ) { |
| 75 | throw new WorkspaceResolutionError('operation_conflict', error.message); |
| 76 | } |
| 77 | throw error; |
| 78 | } |
| 79 | this.onProjectChanged(); |
| 80 | } |
| 81 | return operation(workspace); |
| 82 | }); |
| 83 | } |
| 84 | |
| 85 | async #resolve(target: WorkspaceTarget): Promise<ResolvedWorkspace> { |
| 86 | if (target.kind === 'host_path') { |
| 87 | const cwd = await canonicalHostDirectory(target.path); |
| 88 | return { target: { kind: 'host_path', path: cwd }, cwd, projectId: null }; |
| 89 | } |
| 90 | |
| 91 | const project = findProjectByIdentity(await this.catalog.list(), target.projectId); |
| 92 | if (!project) { |
| 93 | throw new WorkspaceResolutionError( |
| 94 | 'not_found', |
| 95 | `Project does not exist: ${target.projectId}`, |
| 96 | ); |
nothing calls this directly
no outgoing calls
no test coverage detected