index.ts 2.7 KB

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