index.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. import { App } from "../app/app"
  2. import { Bus } from "../bus"
  3. import { File } from "../file"
  4. import { Log } from "../util/log"
  5. import path from "path"
  6. import * as Formatter from "./formatter"
  7. import { Config } from "../config/config"
  8. import { mergeDeep } from "remeda"
  9. export namespace Format {
  10. const log = Log.create({ service: "format" })
  11. const state = App.state("format", async () => {
  12. const enabled: Record<string, boolean> = {}
  13. const cfg = await Config.get()
  14. const formatters = { ...Formatter } as Record<string, Formatter.Info>
  15. for (const [name, item] of Object.entries(cfg.formatter ?? {})) {
  16. if (item.disabled) {
  17. delete formatters[name]
  18. continue
  19. }
  20. const result: Formatter.Info = mergeDeep(formatters[name] ?? {}, {
  21. command: [],
  22. extensions: [],
  23. ...item,
  24. })
  25. result.enabled = async () => true
  26. result.name = name
  27. formatters[name] = result
  28. }
  29. return {
  30. enabled,
  31. formatters,
  32. }
  33. })
  34. async function isEnabled(item: Formatter.Info) {
  35. const s = await state()
  36. let status = s.enabled[item.name]
  37. if (status === undefined) {
  38. status = await item.enabled()
  39. s.enabled[item.name] = status
  40. }
  41. return status
  42. }
  43. async function getFormatter(ext: string) {
  44. const formatters = await state().then((x) => x.formatters)
  45. const result = []
  46. for (const item of Object.values(formatters)) {
  47. log.info("checking", { name: item.name, ext })
  48. if (!item.extensions.includes(ext)) continue
  49. if (!(await isEnabled(item))) continue
  50. result.push(item)
  51. }
  52. return result
  53. }
  54. export function init() {
  55. log.info("init")
  56. Bus.subscribe(File.Event.Edited, async (payload) => {
  57. const file = payload.properties.file
  58. log.info("formatting", { file })
  59. const ext = path.extname(file)
  60. for (const item of await getFormatter(ext)) {
  61. log.info("running", { command: item.command })
  62. try {
  63. const proc = Bun.spawn({
  64. cmd: item.command.map((x) => x.replace("$FILE", file)),
  65. cwd: App.info().path.cwd,
  66. env: { ...process.env, ...item.environment },
  67. stdout: "ignore",
  68. stderr: "ignore",
  69. })
  70. const exit = await proc.exited
  71. if (exit !== 0)
  72. log.error("failed", {
  73. command: item.command,
  74. ...item.environment,
  75. })
  76. } catch (error) {
  77. log.error("failed", {
  78. error,
  79. command: item.command,
  80. ...item.environment,
  81. })
  82. // re-raising
  83. throw error
  84. }
  85. }
  86. })
  87. }
  88. }