index.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import { App } from "../app/app"
  2. import { BunProc } from "../bun"
  3. import { Config } from "../config/config"
  4. import { Log } from "../util/log"
  5. import path from "path"
  6. export namespace Format {
  7. const log = Log.create({ service: "format" })
  8. const state = App.state("format", async () => {
  9. const hooks: Record<string, Hook[]> = {}
  10. for (const item of FORMATTERS) {
  11. if (await item.enabled()) {
  12. for (const ext of item.extensions) {
  13. const list = hooks[ext] ?? []
  14. list.push({
  15. command: item.command,
  16. environment: item.environment,
  17. })
  18. hooks[ext] = list
  19. }
  20. }
  21. }
  22. const cfg = await Config.get()
  23. for (const [file, items] of Object.entries(
  24. cfg.experimental?.hook?.file_edited ?? {},
  25. )) {
  26. for (const item of items) {
  27. const list = hooks[file] ?? []
  28. list.push({
  29. command: item.command,
  30. environment: item.environment,
  31. })
  32. hooks[file] = list
  33. }
  34. }
  35. return {
  36. hooks,
  37. }
  38. })
  39. export async function run(file: string) {
  40. log.info("formatting", { file })
  41. const { hooks } = await state()
  42. const ext = path.extname(file)
  43. const match = hooks[ext]
  44. if (!match) return
  45. for (const item of match) {
  46. log.info("running", { command: item.command })
  47. const proc = Bun.spawn({
  48. cmd: item.command.map((x) => x.replace("$FILE", file)),
  49. cwd: App.info().path.cwd,
  50. env: item.environment,
  51. stdout: "ignore",
  52. stderr: "ignore",
  53. })
  54. const exit = await proc.exited
  55. if (exit !== 0)
  56. log.error("failed", {
  57. command: item.command,
  58. ...item.environment,
  59. })
  60. }
  61. }
  62. interface Hook {
  63. command: string[]
  64. environment?: Record<string, string>
  65. }
  66. interface Native {
  67. name: string
  68. command: string[]
  69. environment?: Record<string, string>
  70. extensions: string[]
  71. enabled(): Promise<boolean>
  72. }
  73. const FORMATTERS: Native[] = [
  74. {
  75. name: "prettier",
  76. command: [BunProc.which(), "run", "prettier", "--write", "$FILE"],
  77. environment: {
  78. BUN_BE_BUN: "1",
  79. },
  80. extensions: [
  81. ".js",
  82. ".jsx",
  83. ".mjs",
  84. ".cjs",
  85. ".ts",
  86. ".tsx",
  87. ".mts",
  88. ".cts",
  89. ".html",
  90. ".htm",
  91. ".css",
  92. ".scss",
  93. ".sass",
  94. ".less",
  95. ".vue",
  96. ".svelte",
  97. ".json",
  98. ".jsonc",
  99. ".yaml",
  100. ".yml",
  101. ".toml",
  102. ".xml",
  103. ".md",
  104. ".mdx",
  105. ".graphql",
  106. ".gql",
  107. ],
  108. async enabled() {
  109. try {
  110. const proc = Bun.spawn({
  111. cmd: [BunProc.which(), "run", "prettier", "--version"],
  112. cwd: App.info().path.cwd,
  113. env: {
  114. BUN_BE_BUN: "1",
  115. },
  116. stdout: "ignore",
  117. stderr: "ignore",
  118. })
  119. const exit = await proc.exited
  120. return exit === 0
  121. } catch {
  122. return false
  123. }
  124. },
  125. },
  126. ]
  127. }