reference.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. export * as Reference from "./reference"
  2. import { makeLocationNode } from "./effect/app-node"
  3. import { Context, Effect, Layer, Scope, Types } from "effect"
  4. import { Reference } from "@opencode-ai/schema/reference"
  5. import { Global } from "./global"
  6. import { EventV2 } from "./event"
  7. import { Repository } from "./repository"
  8. import { RepositoryCache } from "./repository-cache"
  9. import { AbsolutePath } from "./schema"
  10. import { State } from "./state"
  11. export const LocalSource = Reference.LocalSource
  12. export type LocalSource = Reference.LocalSource
  13. export const GitSource = Reference.GitSource
  14. export type GitSource = Reference.GitSource
  15. export const Source = Reference.Source
  16. export type Source = Reference.Source
  17. export const Event = Reference.Event
  18. export const Info = Reference.Info
  19. export type Info = Reference.Info
  20. type Data = {
  21. sources: Map<string, Types.DeepMutable<Source>>
  22. }
  23. type Draft = {
  24. add(name: string, source: Source): void
  25. remove(name: string): void
  26. list(): readonly [string, Source][]
  27. }
  28. export interface Interface extends State.Transformable<Draft> {
  29. readonly list: () => Effect.Effect<Info[]>
  30. }
  31. export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Reference") {}
  32. const layer = Layer.effect(
  33. Service,
  34. Effect.gen(function* () {
  35. const global = yield* Global.Service
  36. const events = yield* EventV2.Service
  37. const cache = yield* RepositoryCache.Service
  38. const scope = yield* Scope.Scope
  39. const materialized = new Map<string, Info>()
  40. const state = State.create<Data, Draft>({
  41. initial: () => ({ sources: new Map() }),
  42. draft: (draft) => ({
  43. add: (name, source) => draft.sources.set(name, source as Types.DeepMutable<Source>),
  44. remove: (name) => draft.sources.delete(name),
  45. list: () => Array.from(draft.sources.entries()) as [string, Source][],
  46. }),
  47. finalize: (draft) =>
  48. Effect.gen(function* () {
  49. materialized.clear()
  50. const seen = new Map<string, string | undefined>()
  51. for (const [name, source] of draft.list()) {
  52. if (source.type === "local") {
  53. materialized.set(
  54. name,
  55. new Info({
  56. name,
  57. path: source.path,
  58. ...(source.description === undefined ? {} : { description: source.description }),
  59. ...(source.hidden === undefined ? {} : { hidden: source.hidden }),
  60. source,
  61. }),
  62. )
  63. continue
  64. }
  65. const repository = Repository.parse(source.repository)
  66. if (!repository || !Repository.isRemote(repository)) continue
  67. if (source.branch) {
  68. try {
  69. Repository.validateBranch(source.branch)
  70. } catch {
  71. continue
  72. }
  73. }
  74. const target = Repository.cachePath(global.repos, repository)
  75. if (seen.has(target) && seen.get(target) !== source.branch) continue
  76. seen.set(target, source.branch)
  77. materialized.set(
  78. name,
  79. new Info({
  80. name,
  81. path: AbsolutePath.make(target),
  82. ...(source.description === undefined ? {} : { description: source.description }),
  83. ...(source.hidden === undefined ? {} : { hidden: source.hidden }),
  84. source,
  85. }),
  86. )
  87. yield* cache.ensure({ reference: repository, branch: source.branch, refresh: true }).pipe(
  88. Effect.catchCause((cause) =>
  89. Effect.logWarning("failed to materialize reference", {
  90. name,
  91. repository: source.repository,
  92. cause,
  93. }),
  94. ),
  95. Effect.forkIn(scope),
  96. )
  97. }
  98. yield* events.publish(Event.Updated, {})
  99. }),
  100. })
  101. return Service.of({
  102. transform: state.transform,
  103. reload: state.reload,
  104. list: Effect.fn("Reference.list")(function* () {
  105. return Array.from(materialized.values())
  106. }),
  107. })
  108. }),
  109. )
  110. export const locationLayer = layer
  111. export const node = makeLocationNode({
  112. service: Service,
  113. layer,
  114. deps: [Global.node, EventV2.node, RepositoryCache.node],
  115. })