app.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import "zod-openapi/extend"
  2. import { Log } from "../util/log"
  3. import { Context } from "../util/context"
  4. import { Filesystem } from "../util/filesystem"
  5. import { Project } from "../util/project"
  6. import { Global } from "../global"
  7. import path from "path"
  8. import os from "os"
  9. import { z } from "zod"
  10. export namespace App {
  11. const log = Log.create({ service: "app" })
  12. export const Info = z
  13. .object({
  14. project: z.string(),
  15. user: z.string(),
  16. hostname: z.string(),
  17. git: z.boolean(),
  18. path: z.object({
  19. config: z.string(),
  20. data: z.string(),
  21. root: z.string(),
  22. cwd: z.string(),
  23. state: z.string(),
  24. }),
  25. time: z.object({
  26. initialized: z.number().optional(),
  27. }),
  28. })
  29. .openapi({
  30. ref: "App.Info",
  31. })
  32. export type Info = z.infer<typeof Info>
  33. const ctx = Context.create<Awaited<ReturnType<typeof create>>>("app")
  34. const APP_JSON = "app.json"
  35. async function create(input: { cwd: string }) {
  36. log.info("creating", {
  37. cwd: input.cwd,
  38. })
  39. const git = await Filesystem.findUp(".git", input.cwd).then(([x]) =>
  40. x ? path.dirname(x) : undefined,
  41. )
  42. log.info("git", { git })
  43. const data = path.join(
  44. Global.Path.data,
  45. "project",
  46. git ? directory(git) : "global",
  47. )
  48. const stateFile = Bun.file(path.join(data, APP_JSON))
  49. const state = (await stateFile.json().catch(() => ({}))) as {
  50. initialized: number
  51. }
  52. await stateFile.write(JSON.stringify(state))
  53. const services = new Map<
  54. any,
  55. {
  56. state: any
  57. shutdown?: (input: any) => Promise<void>
  58. }
  59. >()
  60. const root = git ?? input.cwd
  61. const project = await Project.getName(root)
  62. const info: Info = {
  63. project: project,
  64. user: os.userInfo().username,
  65. hostname: os.hostname(),
  66. time: {
  67. initialized: state.initialized,
  68. },
  69. git: git !== undefined,
  70. path: {
  71. config: Global.Path.config,
  72. state: Global.Path.state,
  73. data,
  74. root,
  75. cwd: input.cwd,
  76. },
  77. }
  78. const result = {
  79. services,
  80. info,
  81. }
  82. return result
  83. }
  84. export function state<State>(
  85. key: any,
  86. init: (app: Info) => State,
  87. shutdown?: (state: Awaited<State>) => Promise<void>,
  88. ) {
  89. return () => {
  90. const app = ctx.use()
  91. const services = app.services
  92. if (!services.has(key)) {
  93. log.info("registering service", { name: key })
  94. services.set(key, {
  95. state: init(app.info),
  96. shutdown,
  97. })
  98. }
  99. return services.get(key)?.state as State
  100. }
  101. }
  102. export function info() {
  103. return ctx.use().info
  104. }
  105. export async function provide<T>(
  106. input: { cwd: string },
  107. cb: (app: Info) => Promise<T>,
  108. ) {
  109. const app = await create(input)
  110. return ctx.provide(app, async () => {
  111. const result = await cb(app.info)
  112. for (const [key, entry] of app.services.entries()) {
  113. if (!entry.shutdown) continue
  114. log.info("shutdown", { name: key })
  115. await entry.shutdown?.(await entry.state)
  116. }
  117. return result
  118. })
  119. }
  120. export async function initialize() {
  121. const { info } = ctx.use()
  122. info.time.initialized = Date.now()
  123. await Bun.write(
  124. path.join(info.path.data, APP_JSON),
  125. JSON.stringify({
  126. initialized: Date.now(),
  127. }),
  128. )
  129. }
  130. function directory(input: string): string {
  131. return input
  132. .split(path.sep)
  133. .filter(Boolean)
  134. .join("-")
  135. .replace(/[^A-Za-z0-9_]/g, "-")
  136. }
  137. }