reference.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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. for (const [name, source] of draft.list()) {
  51. if (source.type === "local") {
  52. materialized.set(
  53. name,
  54. new Info({
  55. name,
  56. path: source.path,
  57. ...(source.description === undefined ? {} : { description: source.description }),
  58. ...(source.hidden === undefined ? {} : { hidden: source.hidden }),
  59. source,
  60. }),
  61. )
  62. continue
  63. }
  64. const repository = Repository.parse(source.repository)
  65. if (!repository || !Repository.isRemote(repository)) continue
  66. if (source.branch) {
  67. try {
  68. Repository.validateBranch(source.branch)
  69. } catch {
  70. continue
  71. }
  72. }
  73. materialized.set(
  74. name,
  75. new Info({
  76. name,
  77. path: AbsolutePath.make(Repository.cachePath(global.repos, repository, source.branch)),
  78. ...(source.description === undefined ? {} : { description: source.description }),
  79. ...(source.hidden === undefined ? {} : { hidden: source.hidden }),
  80. source,
  81. }),
  82. )
  83. yield* cache.ensure({ reference: repository, branch: source.branch, refresh: true }).pipe(
  84. Effect.catchCause((cause) =>
  85. Effect.logWarning("failed to materialize reference", {
  86. name,
  87. repository: source.repository,
  88. cause,
  89. }),
  90. ),
  91. Effect.forkIn(scope),
  92. )
  93. }
  94. yield* events.publish(Event.Updated, {})
  95. }),
  96. })
  97. return Service.of({
  98. transform: state.transform,
  99. reload: state.reload,
  100. list: Effect.fn("Reference.list")(function* () {
  101. return Array.from(materialized.values())
  102. }),
  103. })
  104. }),
  105. )
  106. export const locationLayer = layer
  107. export const node = makeLocationNode({
  108. service: Service,
  109. layer,
  110. deps: [Global.node, EventV2.node, RepositoryCache.node],
  111. })