@superblocksteam/vite-plugin-file-sync 2.0.6-next.99 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/dist/ai-service/app-interface/linter.d.ts +2 -7
  2. package/dist/ai-service/app-interface/linter.d.ts.map +1 -1
  3. package/dist/ai-service/app-interface/linter.js +41 -52
  4. package/dist/ai-service/app-interface/linter.js.map +1 -1
  5. package/dist/ai-service/app-interface/shell.d.ts +0 -2
  6. package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
  7. package/dist/ai-service/app-interface/shell.js +4 -13
  8. package/dist/ai-service/app-interface/shell.js.map +1 -1
  9. package/dist/ai-service/const.d.ts +0 -2
  10. package/dist/ai-service/const.d.ts.map +1 -1
  11. package/dist/ai-service/const.js +0 -2
  12. package/dist/ai-service/const.js.map +1 -1
  13. package/dist/ai-service/eval/template-renderer.d.ts.map +1 -1
  14. package/dist/ai-service/eval/template-renderer.js +1 -1
  15. package/dist/ai-service/eval/template-renderer.js.map +1 -1
  16. package/dist/ai-service/index.d.ts.map +1 -1
  17. package/dist/ai-service/index.js +12 -4
  18. package/dist/ai-service/index.js.map +1 -1
  19. package/dist/ai-service/integrations/from-prompt-context.d.ts +1 -1
  20. package/dist/ai-service/integrations/from-prompt-context.d.ts.map +1 -1
  21. package/dist/ai-service/prompts/generated/library-components/SbButtonPropsDocs.d.ts +1 -1
  22. package/dist/ai-service/prompts/generated/library-components/SbButtonPropsDocs.d.ts.map +1 -1
  23. package/dist/ai-service/prompts/generated/library-components/SbButtonPropsDocs.js +2 -2
  24. package/dist/ai-service/prompts/generated/library-components/SbButtonPropsDocs.js.map +1 -1
  25. package/dist/ai-service/prompts/generated/library-components/SbCheckboxPropsDocs.d.ts +1 -1
  26. package/dist/ai-service/prompts/generated/library-components/SbCheckboxPropsDocs.d.ts.map +1 -1
  27. package/dist/ai-service/prompts/generated/library-components/SbCheckboxPropsDocs.js +2 -2
  28. package/dist/ai-service/prompts/generated/library-components/SbCheckboxPropsDocs.js.map +1 -1
  29. package/dist/ai-service/prompts/generated/library-components/SbColumnPropsDocs.d.ts +1 -1
  30. package/dist/ai-service/prompts/generated/library-components/SbColumnPropsDocs.d.ts.map +1 -1
  31. package/dist/ai-service/prompts/generated/library-components/SbColumnPropsDocs.js +2 -2
  32. package/dist/ai-service/prompts/generated/library-components/SbColumnPropsDocs.js.map +1 -1
  33. package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.d.ts +1 -1
  34. package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.d.ts.map +1 -1
  35. package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.js +2 -2
  36. package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.js.map +1 -1
  37. package/dist/ai-service/prompts/generated/library-components/SbDatePickerPropsDocs.d.ts +1 -1
  38. package/dist/ai-service/prompts/generated/library-components/SbDatePickerPropsDocs.d.ts.map +1 -1
  39. package/dist/ai-service/prompts/generated/library-components/SbDatePickerPropsDocs.js +2 -2
  40. package/dist/ai-service/prompts/generated/library-components/SbDatePickerPropsDocs.js.map +1 -1
  41. package/dist/ai-service/prompts/generated/library-components/SbDropdownPropsDocs.d.ts +1 -1
  42. package/dist/ai-service/prompts/generated/library-components/SbDropdownPropsDocs.d.ts.map +1 -1
  43. package/dist/ai-service/prompts/generated/library-components/SbDropdownPropsDocs.js +2 -2
  44. package/dist/ai-service/prompts/generated/library-components/SbDropdownPropsDocs.js.map +1 -1
  45. package/dist/ai-service/prompts/generated/library-components/SbIconPropsDocs.d.ts +1 -1
  46. package/dist/ai-service/prompts/generated/library-components/SbIconPropsDocs.d.ts.map +1 -1
  47. package/dist/ai-service/prompts/generated/library-components/SbIconPropsDocs.js +2 -2
  48. package/dist/ai-service/prompts/generated/library-components/SbIconPropsDocs.js.map +1 -1
  49. package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.d.ts +1 -1
  50. package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.d.ts.map +1 -1
  51. package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.js +2 -2
  52. package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.js.map +1 -1
  53. package/dist/ai-service/prompts/generated/library-components/SbInputPropsDocs.d.ts +1 -1
  54. package/dist/ai-service/prompts/generated/library-components/SbInputPropsDocs.d.ts.map +1 -1
  55. package/dist/ai-service/prompts/generated/library-components/SbInputPropsDocs.js +2 -2
  56. package/dist/ai-service/prompts/generated/library-components/SbInputPropsDocs.js.map +1 -1
  57. package/dist/ai-service/prompts/generated/library-components/SbModalPropsDocs.d.ts +1 -1
  58. package/dist/ai-service/prompts/generated/library-components/SbModalPropsDocs.d.ts.map +1 -1
  59. package/dist/ai-service/prompts/generated/library-components/SbModalPropsDocs.js +2 -2
  60. package/dist/ai-service/prompts/generated/library-components/SbModalPropsDocs.js.map +1 -1
  61. package/dist/ai-service/prompts/generated/library-components/SbPagePropsDocs.d.ts +1 -1
  62. package/dist/ai-service/prompts/generated/library-components/SbPagePropsDocs.d.ts.map +1 -1
  63. package/dist/ai-service/prompts/generated/library-components/SbPagePropsDocs.js +2 -2
  64. package/dist/ai-service/prompts/generated/library-components/SbPagePropsDocs.js.map +1 -1
  65. package/dist/ai-service/prompts/generated/library-components/SbSectionPropsDocs.d.ts +1 -1
  66. package/dist/ai-service/prompts/generated/library-components/SbSectionPropsDocs.d.ts.map +1 -1
  67. package/dist/ai-service/prompts/generated/library-components/SbSectionPropsDocs.js +2 -2
  68. package/dist/ai-service/prompts/generated/library-components/SbSectionPropsDocs.js.map +1 -1
  69. package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.d.ts +1 -1
  70. package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.d.ts.map +1 -1
  71. package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.js +2 -2
  72. package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.js.map +1 -1
  73. package/dist/ai-service/prompts/generated/library-components/SbSwitchPropsDocs.d.ts +1 -1
  74. package/dist/ai-service/prompts/generated/library-components/SbSwitchPropsDocs.d.ts.map +1 -1
  75. package/dist/ai-service/prompts/generated/library-components/SbSwitchPropsDocs.js +2 -2
  76. package/dist/ai-service/prompts/generated/library-components/SbSwitchPropsDocs.js.map +1 -1
  77. package/dist/ai-service/prompts/generated/library-components/SbTablePropsDocs.d.ts +1 -1
  78. package/dist/ai-service/prompts/generated/library-components/SbTablePropsDocs.d.ts.map +1 -1
  79. package/dist/ai-service/prompts/generated/library-components/SbTablePropsDocs.js +2 -2
  80. package/dist/ai-service/prompts/generated/library-components/SbTablePropsDocs.js.map +1 -1
  81. package/dist/ai-service/prompts/generated/library-components/SbTextPropsDocs.d.ts +1 -1
  82. package/dist/ai-service/prompts/generated/library-components/SbTextPropsDocs.d.ts.map +1 -1
  83. package/dist/ai-service/prompts/generated/library-components/SbTextPropsDocs.js +2 -2
  84. package/dist/ai-service/prompts/generated/library-components/SbTextPropsDocs.js.map +1 -1
  85. package/dist/ai-service/prompts/generated/library-typedefs/Dim.js +1 -1
  86. package/dist/ai-service/prompts/generated/library-typedefs/SbEventFlow.js +1 -1
  87. package/dist/ai-service/prompts/generated/library-typedefs/index.d.ts +0 -1
  88. package/dist/ai-service/prompts/generated/library-typedefs/index.d.ts.map +1 -1
  89. package/dist/ai-service/prompts/generated/library-typedefs/index.js +0 -1
  90. package/dist/ai-service/prompts/generated/library-typedefs/index.js.map +1 -1
  91. package/dist/ai-service/prompts/generated/subprompts/full-examples.js +1 -1
  92. package/dist/ai-service/prompts/generated/subprompts/superblocks-api.d.ts +1 -1
  93. package/dist/ai-service/prompts/generated/subprompts/superblocks-api.d.ts.map +1 -1
  94. package/dist/ai-service/prompts/generated/subprompts/superblocks-api.js +2 -2
  95. package/dist/ai-service/prompts/generated/subprompts/superblocks-api.js.map +1 -1
  96. package/dist/ai-service/prompts/generated/subprompts/superblocks-components-rules.js +1 -1
  97. package/dist/ai-service/prompts/generated/subprompts/superblocks-custom-components.js +1 -1
  98. package/dist/ai-service/prompts/generated/subprompts/superblocks-data-filtering.js +1 -1
  99. package/dist/ai-service/prompts/generated/subprompts/superblocks-event-flow.js +1 -1
  100. package/dist/ai-service/prompts/generated/subprompts/superblocks-forms.js +1 -1
  101. package/dist/ai-service/prompts/generated/subprompts/superblocks-layouts.js +1 -1
  102. package/dist/ai-service/prompts/generated/subprompts/superblocks-page.js +1 -1
  103. package/dist/ai-service/prompts/generated/subprompts/superblocks-rbac.js +1 -1
  104. package/dist/ai-service/prompts/generated/subprompts/superblocks-routes.js +1 -1
  105. package/dist/ai-service/prompts/generated/subprompts/superblocks-state.js +1 -1
  106. package/dist/ai-service/prompts/generated/subprompts/superblocks-theming.js +1 -1
  107. package/dist/ai-service/prompts/generated/subprompts/system.js +1 -1
  108. package/dist/ai-service/prompts/system.d.ts.map +1 -1
  109. package/dist/ai-service/prompts/system.js +0 -4
  110. package/dist/ai-service/prompts/system.js.map +1 -1
  111. package/dist/ai-service/state-machine/clark-fsm.d.ts +0 -2
  112. package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
  113. package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
  114. package/dist/ai-service/state-machine/handlers/agent-planning.js +2 -13
  115. package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
  116. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts +1 -1
  117. package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
  118. package/dist/ai-service/state-machine/handlers/llm-generating.js +24 -75
  119. package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
  120. package/dist/ai-service/state-machine/handlers/runtime-reviewing.d.ts.map +1 -1
  121. package/dist/ai-service/state-machine/handlers/runtime-reviewing.js +6 -18
  122. package/dist/ai-service/state-machine/handlers/runtime-reviewing.js.map +1 -1
  123. package/dist/ai-service/transform/add-metadata-to-api-yaml/transformer.d.ts.map +1 -1
  124. package/dist/ai-service/transform/add-metadata-to-api-yaml/transformer.js +2 -3
  125. package/dist/ai-service/transform/add-metadata-to-api-yaml/transformer.js.map +1 -1
  126. package/dist/ai-service/transform/shared.d.ts.map +1 -1
  127. package/dist/ai-service/transform/shared.js +8 -9
  128. package/dist/ai-service/transform/shared.js.map +1 -1
  129. package/dist/ai-service/types.d.ts +10 -23
  130. package/dist/ai-service/types.d.ts.map +1 -1
  131. package/dist/ai-service/types.js.map +1 -1
  132. package/dist/codegen.js +1 -1
  133. package/dist/codegen.js.map +1 -1
  134. package/dist/components-manager.d.ts +1 -4
  135. package/dist/components-manager.d.ts.map +1 -1
  136. package/dist/components-manager.js +4 -34
  137. package/dist/components-manager.js.map +1 -1
  138. package/dist/file-sync-vite-plugin.d.ts.map +1 -1
  139. package/dist/file-sync-vite-plugin.js +15 -43
  140. package/dist/file-sync-vite-plugin.js.map +1 -1
  141. package/dist/file-system-helpers.d.ts +0 -4
  142. package/dist/file-system-helpers.d.ts.map +1 -1
  143. package/dist/file-system-helpers.js +0 -10
  144. package/dist/file-system-helpers.js.map +1 -1
  145. package/dist/file-system-manager.d.ts +39 -52
  146. package/dist/file-system-manager.d.ts.map +1 -1
  147. package/dist/file-system-manager.js +532 -659
  148. package/dist/file-system-manager.js.map +1 -1
  149. package/dist/index.d.ts +0 -1
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +0 -1
  152. package/dist/index.js.map +1 -1
  153. package/dist/inject-index-vite-plugin.d.ts +2 -0
  154. package/dist/inject-index-vite-plugin.d.ts.map +1 -1
  155. package/dist/inject-index-vite-plugin.js +2 -2
  156. package/dist/inject-index-vite-plugin.js.map +1 -1
  157. package/dist/injected-index.d.ts +2 -2
  158. package/dist/injected-index.d.ts.map +1 -1
  159. package/dist/injected-index.js.map +1 -1
  160. package/dist/lock-service/index.d.ts.map +1 -1
  161. package/dist/lock-service/index.js +2 -13
  162. package/dist/lock-service/index.js.map +1 -1
  163. package/dist/parsing/computed/to-code-computed.d.ts.map +1 -1
  164. package/dist/parsing/computed/to-code-computed.js +3 -7
  165. package/dist/parsing/computed/to-code-computed.js.map +1 -1
  166. package/dist/parsing/entity/to-value-entity.js.map +1 -1
  167. package/dist/parsing/events/to-code-events.d.ts +1 -1
  168. package/dist/parsing/events/to-code-events.d.ts.map +1 -1
  169. package/dist/parsing/events/to-code-events.js +4 -15
  170. package/dist/parsing/events/to-code-events.js.map +1 -1
  171. package/dist/parsing/events/to-value-events.d.ts.map +1 -1
  172. package/dist/parsing/events/to-value-events.js +0 -47
  173. package/dist/parsing/events/to-value-events.js.map +1 -1
  174. package/dist/parsing/properties.js.map +1 -1
  175. package/dist/parsing/template/index.js +1 -1
  176. package/dist/parsing/template/index.js.map +1 -1
  177. package/dist/parsing/template/to-code-template.d.ts +1 -2
  178. package/dist/parsing/template/to-code-template.d.ts.map +1 -1
  179. package/dist/parsing/template/to-code-template.js +2 -2
  180. package/dist/parsing/template/to-code-template.js.map +1 -1
  181. package/dist/plugin-options.d.ts +2 -0
  182. package/dist/plugin-options.d.ts.map +1 -1
  183. package/dist/plugin-options.js.map +1 -1
  184. package/dist/refactor/blocks.d.ts.map +1 -1
  185. package/dist/refactor/blocks.js +69 -2
  186. package/dist/refactor/blocks.js.map +1 -1
  187. package/dist/refactor/javascript.d.ts +4 -0
  188. package/dist/refactor/javascript.d.ts.map +1 -1
  189. package/dist/refactor/javascript.js +8 -0
  190. package/dist/refactor/javascript.js.map +1 -1
  191. package/dist/rename-manager.d.ts +5 -0
  192. package/dist/rename-manager.d.ts.map +1 -1
  193. package/dist/rename-manager.js +27 -1
  194. package/dist/rename-manager.js.map +1 -1
  195. package/dist/routing.d.ts +2 -2
  196. package/dist/routing.d.ts.map +1 -1
  197. package/dist/routing.js +1 -21
  198. package/dist/routing.js.map +1 -1
  199. package/dist/sb-scope-manager.d.ts +1 -1
  200. package/dist/sb-scope-manager.d.ts.map +1 -1
  201. package/dist/sb-scope-manager.js +0 -10
  202. package/dist/sb-scope-manager.js.map +1 -1
  203. package/dist/socket-manager.d.ts +2 -2
  204. package/dist/socket-manager.d.ts.map +1 -1
  205. package/dist/socket-manager.js +5 -7
  206. package/dist/socket-manager.js.map +1 -1
  207. package/dist/source-tracker.d.ts +20 -20
  208. package/dist/source-tracker.d.ts.map +1 -1
  209. package/dist/source-tracker.js +17 -33
  210. package/dist/source-tracker.js.map +1 -1
  211. package/dist/sync-service/index.d.ts.map +1 -1
  212. package/dist/sync-service/index.js +1 -2
  213. package/dist/sync-service/index.js.map +1 -1
  214. package/dist/sync-service/list-dir.js +1 -1
  215. package/dist/sync-service/list-dir.js.map +1 -1
  216. package/dist/util/logger.d.ts +17 -13
  217. package/dist/util/logger.d.ts.map +1 -1
  218. package/dist/util/logger.js +44 -34
  219. package/dist/util/logger.js.map +1 -1
  220. package/dist/util/tracing.d.ts +4 -0
  221. package/dist/util/tracing.d.ts.map +1 -0
  222. package/dist/util/tracing.js +56 -0
  223. package/dist/util/tracing.js.map +1 -0
  224. package/dist/util.d.ts +0 -1
  225. package/dist/util.d.ts.map +1 -1
  226. package/dist/util.js +0 -8
  227. package/dist/util.js.map +1 -1
  228. package/package.json +6 -15
  229. package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.d.ts +0 -2
  230. package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.d.ts.map +0 -1
  231. package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.js +0 -6
  232. package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.js.map +0 -1
  233. package/dist/ai-service/state-machine/helpers/rate-limiting.d.ts +0 -6
  234. package/dist/ai-service/state-machine/helpers/rate-limiting.d.ts.map +0 -1
  235. package/dist/ai-service/state-machine/helpers/rate-limiting.js +0 -26
  236. package/dist/ai-service/state-machine/helpers/rate-limiting.js.map +0 -1
  237. package/dist/ai-service/test-utils/app-generation-mocks/orders-app.d.ts +0 -3
  238. package/dist/ai-service/test-utils/app-generation-mocks/orders-app.d.ts.map +0 -1
  239. package/dist/ai-service/test-utils/app-generation-mocks/orders-app.js +0 -851
  240. package/dist/ai-service/test-utils/app-generation-mocks/orders-app.js.map +0 -1
  241. package/dist/ai-service/test-utils/app-generation-mocks/smoketest.d.ts +0 -3
  242. package/dist/ai-service/test-utils/app-generation-mocks/smoketest.d.ts.map +0 -1
  243. package/dist/ai-service/test-utils/app-generation-mocks/smoketest.js +0 -111
  244. package/dist/ai-service/test-utils/app-generation-mocks/smoketest.js.map +0 -1
  245. package/dist/ai-service/test-utils/mock-utils.d.ts +0 -22
  246. package/dist/ai-service/test-utils/mock-utils.d.ts.map +0 -1
  247. package/dist/ai-service/test-utils/mock-utils.js +0 -46
  248. package/dist/ai-service/test-utils/mock-utils.js.map +0 -1
  249. package/dist/binding-extraction/index.d.ts +0 -2
  250. package/dist/binding-extraction/index.d.ts.map +0 -1
  251. package/dist/binding-extraction/index.js +0 -2
  252. package/dist/binding-extraction/index.js.map +0 -1
  253. package/dist/operations/operation-processor.d.ts +0 -24
  254. package/dist/operations/operation-processor.d.ts.map +0 -1
  255. package/dist/operations/operation-processor.js +0 -80
  256. package/dist/operations/operation-processor.js.map +0 -1
  257. package/dist/operations/types.d.ts +0 -8
  258. package/dist/operations/types.d.ts.map +0 -1
  259. package/dist/operations/types.js +0 -2
  260. package/dist/operations/types.js.map +0 -1
  261. package/dist/parsing/index.d.ts +0 -3
  262. package/dist/parsing/index.d.ts.map +0 -1
  263. package/dist/parsing/index.js +0 -3
  264. package/dist/parsing/index.js.map +0 -1
  265. package/dist/refactor/entities.d.ts +0 -6
  266. package/dist/refactor/entities.d.ts.map +0 -1
  267. package/dist/refactor/entities.js +0 -62
  268. package/dist/refactor/entities.js.map +0 -1
@@ -9,12 +9,10 @@ import { glob } from "glob";
9
9
  import { isEqual } from "lodash-es";
10
10
  import yaml from "yaml";
11
11
  import { generateJSXAttribute } from "./codegen.js";
12
- import { ComponentsManager } from "./components-manager.js";
13
12
  import { addLegacyCustomComponentVariables, modifyLegacyCustomComponentElements, modifyLegacyCustomComponentImports, } from "./custom-components.js";
14
13
  import { applyErrorHandling, } from "./errors/error-handler.js";
15
- import { getApiFilePath, getPageFolder, isPageFilePath, PAGES_DIRECTORY, ROUTES_FILE, SCOPE_FILE, } from "./file-system-helpers.js";
14
+ import { getPageFolder, PAGES_DIRECTORY, ROUTES_FILE, SCOPE_FILE, } from "./file-system-helpers.js";
16
15
  import { generate } from "./generate.js";
17
- import { OperationProcessor } from "./operations/operation-processor.js";
18
16
  import { doesElementHaveBinding } from "./parsing/bindings.js";
19
17
  import { getSbElementId } from "./parsing/ids.js";
20
18
  import { makeJSXAttribute } from "./parsing/jsx.js";
@@ -25,7 +23,6 @@ import { RenameManager } from "./rename-manager.js";
25
23
  import { SourceTracker } from "./source-tracker.js";
26
24
  import { traverse } from "./traverse.js";
27
25
  import { getErrorMeta, getLogger } from "./util/logger.js";
28
- import { getPageName } from "./util.js";
29
26
  const SUPPORTED_FILETYPES = [
30
27
  {
31
28
  type: "tsx",
@@ -47,41 +44,26 @@ const SUPPORTED_FILETYPES = [
47
44
  type: "js-api-step",
48
45
  extension: ".js",
49
46
  },
50
- {
51
- type: "json",
52
- extension: ".json",
53
- },
54
47
  ];
55
48
  const APP_THEME_FILE_NAME = "appTheme.ts";
56
- export class FileSystemManager extends TracedEventEmitter {
49
+ export class FileSyncManager extends TracedEventEmitter {
57
50
  rootDir;
58
51
  tsFiles = {};
59
52
  apiFiles = {};
60
53
  sourceTracker;
61
54
  fsOperationQueue;
62
- operationProcessor;
63
55
  generationNumberSequence;
64
56
  routes = {};
65
- routeChangesQueue = [];
66
57
  watcher;
67
58
  registeredComponentPaths = {};
68
59
  renameManager = new RenameManager();
69
60
  _tracer;
70
- transactionNonce = Date.now();
71
- pendingTransactions = new Set();
72
- processedTransactions = [];
73
61
  constructor(fsOperationQueue, generationNumberSequence, tracer) {
74
62
  super(tracer, { captureRejections: true });
75
63
  this.rootDir = "/";
76
64
  this.fsOperationQueue = fsOperationQueue;
77
65
  this.generationNumberSequence = generationNumberSequence;
78
66
  this._tracer = tracer;
79
- // intentionally a new queue here, we don't want to share the queue with the fsOperationQueue
80
- this.operationProcessor = new OperationProcessor({
81
- batchWindowMs: 50,
82
- maxBatchSize: 100,
83
- });
84
- this.operationProcessor.disable();
85
67
  applyErrorHandling(this, {
86
68
  watch: { operation: "editing from code" },
87
69
  handleCreatePage: { operation: "creating a page" },
@@ -127,7 +109,27 @@ export class FileSystemManager extends TracedEventEmitter {
127
109
  }
128
110
  return path.join(this.rootDir, "App.tsx");
129
111
  }
130
- // MARK: core setup/init
112
+ updateApi = (content, path) => {
113
+ if (!this.rootDir) {
114
+ throw new Error("Root directory not set");
115
+ }
116
+ const { api: apiContents, stepPathMap } = content;
117
+ const pageName = getPageName(path);
118
+ let scopeId = this.sourceTracker?.getScopeDefinitionForPage(pageName)?.id;
119
+ if (!scopeId) {
120
+ console.warn("Scope ID not found for API", apiContents.metadata.name);
121
+ scopeId = "";
122
+ }
123
+ const updatedApi = {
124
+ apiPb: yaml.parse(JSON.stringify(apiContents)),
125
+ pageName,
126
+ stepPathMap,
127
+ scopeId,
128
+ };
129
+ const isNewApi = !this.apiFiles[path];
130
+ this.apiFiles[path] = updatedApi;
131
+ return { updatedApi, pageName, isNewApi };
132
+ };
131
133
  async watch(watcher, rootPath) {
132
134
  const logger = getLogger();
133
135
  this.rootDir = rootPath;
@@ -213,10 +215,178 @@ export class FileSystemManager extends TracedEventEmitter {
213
215
  return;
214
216
  const { type, path } = file;
215
217
  if (type === "api") {
216
- this.updateInternalApiData(content, path);
218
+ this.updateApi(content, path);
219
+ }
220
+ });
221
+ const handleFileChange = async (event, filePath) => {
222
+ logger.info(`File changed: ${filePath}, event: ${event}`);
223
+ switch (event) {
224
+ case "add": {
225
+ const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
226
+ if (!fileType || !filePath.startsWith(rootPath)) {
227
+ return;
228
+ }
229
+ const data = await readFile(filePath);
230
+ if (typeof data !== "string")
231
+ return;
232
+ // if we match pages/*/index.tsx, we want to emit an addPage event
233
+ if (/pages\/.*\/index\.tsx/.test(filePath)) {
234
+ const file = await readFile(filePath);
235
+ if (!file) {
236
+ logger.error(`Failed to read file: ${filePath}`);
237
+ return;
238
+ }
239
+ if (!(filePath in this.tsFiles)) {
240
+ this.tsFiles[filePath] = file;
241
+ this.handleNonVisualChangeByDeletingIds(filePath, file);
242
+ this.emit("addPage", filePath);
243
+ }
244
+ }
245
+ switch (fileType.type) {
246
+ case "api":
247
+ case "python-api-step":
248
+ case "js-api-step": {
249
+ await this.processApiUpdates(filePath, fileType);
250
+ break;
251
+ }
252
+ }
253
+ break;
254
+ }
255
+ case "change": {
256
+ // Special routes file handling
257
+ if (filePath === routePath) {
258
+ const data = await readFile(filePath);
259
+ try {
260
+ this.routes = JSON.parse(data);
261
+ this.emit("routesChanged", this.routes);
262
+ }
263
+ catch (e) {
264
+ logger.error("Error parsing routes file", getErrorMeta(e));
265
+ }
266
+ return;
267
+ }
268
+ const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
269
+ if (!fileType || !filePath.startsWith(rootPath)) {
270
+ return;
271
+ }
272
+ const data = await readFile(filePath);
273
+ if (typeof data !== "string")
274
+ return;
275
+ switch (fileType.type) {
276
+ case "tsx":
277
+ case "scope":
278
+ {
279
+ if (!(filePath in this.tsFiles && this.tsFiles[filePath] === data)) {
280
+ logger.info(`File changed: ${filePath} updating AST tracker`);
281
+ this.tsFiles[filePath] = data;
282
+ // only update the source tracker if the file is different
283
+ this.handleNonVisualChangeByDeletingIds(filePath, data);
284
+ this.emit("fileChanged", filePath, data, true);
285
+ }
286
+ else {
287
+ logger.info(`File unchanged from last tracked state: ${filePath}`);
288
+ this.emit("fileChanged", filePath, data, false);
289
+ }
290
+ }
291
+ break;
292
+ case "api":
293
+ case "python-api-step":
294
+ case "js-api-step":
295
+ {
296
+ await this.processApiUpdates(filePath, fileType);
297
+ }
298
+ break;
299
+ }
300
+ break;
301
+ }
302
+ case "unlink": {
303
+ if (filePath in this.apiFiles) {
304
+ const api = this.apiFiles[filePath];
305
+ delete this.apiFiles[filePath];
306
+ if (!api || !this.sourceTracker)
307
+ break;
308
+ const scopeId = await this.sourceTracker.deleteApi({
309
+ pageName: api.pageName,
310
+ apiName: api.apiPb.metadata.name,
311
+ });
312
+ const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
313
+ await this.writeChanges(changes);
314
+ this.emit("apiManualDelete", {
315
+ api: {
316
+ id: getClientApiId(api.apiPb.metadata.name, api.pageName),
317
+ apiName: api.apiPb.metadata.name,
318
+ // TODO(saksham): get pagename more defensively
319
+ pageName: getPageName(filePath),
320
+ scopeId,
321
+ },
322
+ });
323
+ }
324
+ if (filePath in this.tsFiles) {
325
+ delete this.tsFiles[filePath];
326
+ this.sourceTracker?.removeFile(filePath);
327
+ this.emit("deletePage", filePath);
328
+ }
329
+ break;
330
+ }
331
+ }
332
+ };
333
+ watcher.on("all", async (event, filePath) => {
334
+ const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
335
+ switch (fileType?.type) {
336
+ case "tsx":
337
+ case "scope":
338
+ // these can be awaited to ensure sequential execution
339
+ this.fsOperationQueue.enqueue(async () => {
340
+ return await handleFileChange(event, filePath);
341
+ });
342
+ break;
343
+ default:
344
+ // some files including APIs cannot be awaited currently
345
+ this.fsOperationQueue.enqueue(async () => {
346
+ void handleFileChange(event, filePath);
347
+ });
217
348
  }
218
349
  });
219
- watcher.on("all", this.handleFileChange);
350
+ }
351
+ getApiFiles() {
352
+ return this.formatApisToClientApis(this.apiFiles);
353
+ }
354
+ getTsFilePaths() {
355
+ return Object.keys(this.tsFiles);
356
+ }
357
+ getSourceTracker() {
358
+ return this.sourceTracker;
359
+ }
360
+ async writeFile(path, content, kind) {
361
+ if (kind === "ts") {
362
+ // happens eagerly regardless of error - possible desync
363
+ this.tsFiles[path] = content;
364
+ // TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
365
+ await this.fsOperationQueue.enqueue(async () => {
366
+ await fs.writeFile(path, content);
367
+ });
368
+ }
369
+ else if (kind === "api") {
370
+ const currentApiFile = this.apiFiles[path];
371
+ const apiPb = yaml.parse(content);
372
+ // Client APIs have id, but server APIs don't
373
+ if (apiPb.metadata.id) {
374
+ delete apiPb.metadata.id;
375
+ }
376
+ const stepPathMap = currentApiFile?.stepPathMap ?? {};
377
+ this.updateApi({ api: apiPb, stepPathMap }, path);
378
+ // TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
379
+ await this.fsOperationQueue.enqueue(async () => {
380
+ await writeApiFiles({ apiPb }, "api", path.split("/").slice(0, -1).join("/"), false, [], [], { extractLargeSourceFiles: true, minLinesForExtraction: 1 }, new Set(Object.keys(this.apiFiles)), stepPathMap);
381
+ // stepPathMap will be generated after the write when the file doesn't exist
382
+ if (this.apiFiles[path]) {
383
+ this.apiFiles[path].stepPathMap = stepPathMap;
384
+ }
385
+ });
386
+ }
387
+ }
388
+ readFile(path) {
389
+ return this.tsFiles[path];
220
390
  }
221
391
  initializeSourceTracker() {
222
392
  this.sourceTracker = new SourceTracker(this._tracer);
@@ -230,56 +400,49 @@ export class FileSystemManager extends TracedEventEmitter {
230
400
  }
231
401
  return this.sourceTracker.handleNonVisualChangeByDeletingIds(path, content);
232
402
  }
233
- async getFileWithIds(filePath) {
403
+ async getFileWithIds(path) {
234
404
  const logger = getLogger();
235
405
  if (!this.sourceTracker) {
236
406
  throw new Error("Source tracker not initialized");
237
407
  }
238
408
  const files = this.sourceTracker.getCurrentFiles();
239
- if (!files[filePath]) {
240
- throw new Error("File not found in source tracker " + filePath);
409
+ if (!files[path]) {
410
+ throw new Error("File not found in source tracker " + path);
241
411
  }
242
- const ast = files[filePath].ast;
412
+ const ast = files[path].ast;
243
413
  if (!ast) {
244
- throw new Error("No AST found for file in source tracker " + filePath);
414
+ throw new Error("No AST found for file in source tracker " + path);
245
415
  }
246
416
  const clonedAst = await transformFromAstAsync(ast, undefined, {
247
417
  ast: true,
248
418
  });
249
419
  if (!clonedAst?.ast) {
250
- throw new Error("Failed to clone AST for file " + filePath);
420
+ throw new Error("Failed to clone AST for file " + path);
251
421
  }
252
- const componentsManager = ComponentsManager.getInstance();
253
422
  const processedTransactions = this.getProcessedTransactionsWithNonce();
254
423
  const customImports = new Set();
255
- traverse(clonedAst.ast, {
256
- Program: {
257
- exit(nodePath) {
258
- if (isCustomBuildEnabled()) {
259
- addLegacyCustomComponentVariables(nodePath, customImports);
260
- }
261
- // we want to inject the component name into the file so we can use it for hot reloading purposes
262
- const name = componentsManager.getComponentName(filePath);
263
- if (name) {
264
- nodePath.pushContainer("body", t.variableDeclaration("const", [
265
- t.variableDeclarator(t.identifier("componentName"), t.stringLiteral(name)),
266
- ]));
267
- }
268
- },
269
- },
270
- ImportDeclaration(path) {
271
- if (isCustomBuildEnabled()) {
424
+ let customBuildTraversal = {};
425
+ if (isCustomBuildEnabled()) {
426
+ customBuildTraversal = {
427
+ ImportDeclaration(path) {
272
428
  // when we see a custom component, we want to track the import and modify it to point
273
429
  // to the built source
274
430
  modifyLegacyCustomComponentImports(path, customImports);
275
- }
276
- },
277
- JSXElement(path) {
278
- if (isCustomBuildEnabled()) {
431
+ },
432
+ JSXElement(path) {
279
433
  // based on the imports we see, we want to modify the JSXElement that uses the custom component
280
434
  modifyLegacyCustomComponentElements(path, customImports);
281
- }
282
- },
435
+ },
436
+ Program: {
437
+ // we want to write the wrapped custom elements as local variables to the file
438
+ exit(path) {
439
+ addLegacyCustomComponentVariables(path, customImports);
440
+ },
441
+ },
442
+ };
443
+ }
444
+ traverse(clonedAst.ast, {
445
+ ...customBuildTraversal,
283
446
  ReturnStatement(path) {
284
447
  const argument = path.get("argument");
285
448
  if (!argument.isJSXElement()) {
@@ -325,246 +488,6 @@ export class FileSystemManager extends TracedEventEmitter {
325
488
  return null;
326
489
  }
327
490
  }
328
- enableOperationsQueue() {
329
- this.operationProcessor.enable();
330
- }
331
- disableOperationsQueue() {
332
- this.operationProcessor.disable();
333
- }
334
- async flushOperations() {
335
- await this.operationProcessor.flush();
336
- }
337
- // MARK: file change handling
338
- handleFileChange = async (event, filePath) => {
339
- const logger = getLogger();
340
- logger.info(`File changed: ${filePath}, event: ${event}`);
341
- const rootPath = this.rootDir;
342
- if (!rootPath) {
343
- throw new Error("Root directory not set");
344
- }
345
- // Skip directory events
346
- if (event === "addDir" || event === "unlinkDir") {
347
- return;
348
- }
349
- const routePath = path.join(rootPath, ROUTES_FILE);
350
- const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
351
- // Only handle files we care about and that are in our root path
352
- if (!fileType || !filePath.startsWith(rootPath)) {
353
- return;
354
- }
355
- // Queue the operation based on the event type
356
- switch (event) {
357
- case "add": {
358
- const data = await readFile(filePath);
359
- if (typeof data !== "string")
360
- return;
361
- const isPage = isPageFilePath(filePath);
362
- if (isPage) {
363
- void this.operationProcessor.addOperation({
364
- metadata: {
365
- filePath,
366
- },
367
- execute: async () => {
368
- const file = await readFile(filePath);
369
- if (!file) {
370
- logger.error(`Failed to read file: ${filePath}`);
371
- return;
372
- }
373
- if (!(filePath in this.tsFiles)) {
374
- this.tsFiles[filePath] = file;
375
- this.handleNonVisualChangeByDeletingIds(filePath, file);
376
- this.emit("addPage", filePath);
377
- }
378
- },
379
- });
380
- }
381
- else {
382
- void this.operationProcessor.addOperation({
383
- metadata: {
384
- filePath,
385
- },
386
- execute: async () => {
387
- switch (fileType.type) {
388
- case "api":
389
- case "python-api-step":
390
- case "js-api-step": {
391
- await this.processApiFileUpdates(filePath, fileType);
392
- break;
393
- }
394
- }
395
- },
396
- });
397
- }
398
- break;
399
- }
400
- case "change": {
401
- if (filePath === routePath) {
402
- void this.operationProcessor.addOperation({
403
- metadata: {
404
- filePath,
405
- },
406
- priority: true,
407
- execute: async () => {
408
- try {
409
- const data = JSON.parse((await readFile(filePath)) ?? "{}");
410
- if (!isEqual(this.routes, data))
411
- this.routes = data;
412
- // this.addRoute assigns this.routes itself, causing this.routes === data
413
- // but we still want to emit the event to HMR root.tsx
414
- this.emit("routesChanged", this.routes);
415
- }
416
- catch (e) {
417
- logger.error("Error parsing routes file", getErrorMeta(e));
418
- }
419
- },
420
- });
421
- return;
422
- }
423
- else {
424
- void this.operationProcessor.addOperation({
425
- metadata: {
426
- filePath,
427
- },
428
- execute: async () => {
429
- const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
430
- if (!fileType || !filePath.startsWith(rootPath)) {
431
- return;
432
- }
433
- const data = await readFile(filePath);
434
- if (typeof data !== "string")
435
- return;
436
- switch (fileType.type) {
437
- case "tsx":
438
- case "scope":
439
- {
440
- if (!(filePath in this.tsFiles &&
441
- this.tsFiles[filePath] === data)) {
442
- logger.info(`File changed: ${filePath} updating AST tracker`);
443
- this.tsFiles[filePath] = data;
444
- // only update the source tracker if the file is different
445
- this.handleNonVisualChangeByDeletingIds(filePath, data);
446
- this.emit("fileChanged", filePath, data, true);
447
- }
448
- else {
449
- logger.info(`File unchanged from last tracked state: ${filePath}`);
450
- this.emit("fileChanged", filePath, data, false);
451
- }
452
- }
453
- break;
454
- case "api":
455
- case "python-api-step":
456
- case "js-api-step":
457
- {
458
- await this.processApiFileUpdates(filePath, fileType);
459
- }
460
- break;
461
- }
462
- },
463
- });
464
- }
465
- break;
466
- }
467
- case "unlink": {
468
- if (filePath in this.tsFiles) {
469
- void this.operationProcessor.addOperation({
470
- metadata: {
471
- filePath,
472
- },
473
- execute: async () => {
474
- await this.deleteTsFile(filePath);
475
- },
476
- });
477
- }
478
- else if (filePath in this.apiFiles) {
479
- void this.operationProcessor.addOperation({
480
- metadata: {
481
- filePath,
482
- },
483
- execute: async () => {
484
- await this.removeApiData(filePath);
485
- },
486
- });
487
- }
488
- break;
489
- }
490
- }
491
- };
492
- async deleteTsFile(filePath) {
493
- delete this.tsFiles[filePath];
494
- this.sourceTracker?.removeFile(filePath);
495
- if (isPageFilePath(filePath)) {
496
- this.emit("deletePage", filePath);
497
- }
498
- }
499
- getTsFilePaths() {
500
- return Object.keys(this.tsFiles);
501
- }
502
- getSourceTracker() {
503
- return this.sourceTracker;
504
- }
505
- // MARK: fs read/write
506
- async writeFile(path, content, kind) {
507
- switch (kind) {
508
- case "ts": {
509
- // happens eagerly regardless of error - possible desync
510
- this.tsFiles[path] = content;
511
- // TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
512
- await this.fsOperationQueue.enqueue(async () => {
513
- await fs.writeFile(path, content);
514
- });
515
- break;
516
- }
517
- case "api": {
518
- const currentApiFile = this.apiFiles[path];
519
- const apiPb = yaml.parse(content);
520
- // Client APIs have id, but server APIs don't
521
- if (apiPb.metadata.id) {
522
- delete apiPb.metadata.id;
523
- }
524
- const stepPathMap = currentApiFile?.stepPathMap ?? {};
525
- this.updateInternalApiData({ api: apiPb, stepPathMap }, path);
526
- // TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
527
- await this.fsOperationQueue.enqueue(async () => {
528
- await writeApiFiles({ apiPb }, "api", path.split("/").slice(0, -1).join("/"), false, [], [], { extractLargeSourceFiles: true, minLinesForExtraction: 1 }, new Set(Object.keys(this.apiFiles)), stepPathMap);
529
- // stepPathMap will be generated after the write when the file doesn't exist
530
- if (this.apiFiles[path]) {
531
- this.apiFiles[path].stepPathMap = stepPathMap;
532
- }
533
- });
534
- break;
535
- }
536
- default: {
537
- // TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
538
- await this.fsOperationQueue.enqueue(async () => {
539
- await fs.writeFile(path, content);
540
- });
541
- }
542
- }
543
- }
544
- readFile(path) {
545
- return this.tsFiles[path];
546
- }
547
- async addRoute(route, filePath) {
548
- if (!this.rootDir) {
549
- throw new Error("Root directory not set");
550
- }
551
- this.routes[route] = { file: this.getRelativeRoutePath(filePath) };
552
- await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
553
- }
554
- async removeRoute(filePath) {
555
- if (!this.rootDir) {
556
- throw new Error("Root directory not set");
557
- }
558
- const relativeFilePath = this.getRelativeRoutePath(filePath);
559
- this.routes = Object.fromEntries(Object.entries(this.routes).filter(([_, value]) => value.file !== relativeFilePath));
560
- await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
561
- }
562
- async writeChanges(changes, callback) {
563
- return Promise.all(changes.map(async ({ fileName, source, kind }) => {
564
- await this.writeFile(fileName, source, kind);
565
- return callback?.(fileName, source);
566
- }));
567
- }
568
491
  getLocalBindingEntities(path) {
569
492
  const logger = getLogger();
570
493
  const files = this.sourceTracker?.getCurrentFiles();
@@ -592,36 +515,8 @@ export class FileSystemManager extends TracedEventEmitter {
592
515
  });
593
516
  return Array.from(localBindingEntities);
594
517
  }
595
- getPageRoots(filePath) {
596
- const scopeFilePath = path.join(path.dirname(filePath), "index.tsx");
597
- const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
598
- if (!currentFile) {
599
- return null;
600
- }
601
- return getPageRoots(filePath, currentFile);
602
- }
603
- getScope(filePath) {
604
- // get the scope.ts file in the same directory as path
605
- const scopeFilePath = path.join(path.dirname(filePath), SCOPE_FILE);
606
- const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
607
- if (!currentFile) {
608
- console.log("File not found", scopeFilePath);
609
- return null;
610
- }
611
- const scope = getScope(scopeFilePath, currentFile);
612
- return scope;
613
- }
614
- getRoutes() {
615
- const routes = [];
616
- for (const [path, { file }] of Object.entries(this.routes)) {
617
- routes.push({
618
- path,
619
- component: file,
620
- });
621
- }
622
- return routes;
623
- }
624
- // MARK: transaction handling
518
+ pendingTransactions = new Set();
519
+ processedTransactions = [];
625
520
  flushTransactions = () => {
626
521
  // TODO do something more sophisticated than this.
627
522
  this.processedTransactions = this.processedTransactions.slice(-20);
@@ -634,92 +529,42 @@ export class FileSystemManager extends TracedEventEmitter {
634
529
  }
635
530
  this.pendingTransactions.add(transactionId);
636
531
  };
532
+ transactionNonce = Date.now();
637
533
  // Until we have a sophisticated way to track ALL operations, including non-UI initiated ops, we will use a nonce
638
534
  // TODO https://github.com/superblocksteam/superblocks/pull/11788
639
535
  getProcessedTransactionsWithNonce() {
640
536
  const nonce = `t-${this.transactionNonce++}`;
641
537
  return this.processedTransactions.concat(nonce);
642
538
  }
643
- // MARK: editor operations
644
539
  handleCreatePage = async (payload) => {
645
- this.trackTransaction(payload.transaction?.id);
646
- const { name, route, navigateToRoute, routeTestParams } = payload;
540
+ const { name } = payload;
647
541
  if (!this.rootDir) {
648
542
  throw new Error("Root directory not set");
649
543
  }
650
- if (!route) {
651
- throw new Error("Route is required when creating a page");
652
- }
653
544
  const pagePath = getPageFolder(this.rootDir, name);
654
545
  const pageIndexPath = path.join(pagePath, "index.tsx");
655
- const scopePath = path.join(pagePath, "scope.ts");
656
- const pageContent = /*js*/ `import {
657
- SbPage,
658
- Dim,
659
- SbSection,
660
- SbColumn,
661
- registerPage,
662
- } from "@superblocksteam/library";
663
- import { ${name}, ${name}Scope } from "./scope";
664
-
546
+ const pageContent = /*js*/ `import { SbPage, SbContainer, registerPage } from "@superblocksteam/library";
665
547
  function Page() {
666
- const {} = ${name};
667
- return (
668
- <SbPage name="${name}" height={Dim.fill()} width={Dim.fill()}>
669
- <SbSection height={Dim.fill()}>
670
- <SbColumn width={Dim.fill()}></SbColumn>
671
- </SbSection>
672
- </SbPage>
673
- );
548
+ return <SbPage name="${name}">
549
+ <SbContainer width={Dim.fill()} />
550
+ </SbPage>;
674
551
  }
675
552
 
676
- export default registerPage(Page, ${name}Scope);
677
- `;
678
- const scopeContent = /*js*/ `import { createSbScope } from "@superblocksteam/library";
679
-
680
- export const ${name}Scope = createSbScope<{}>(({ entities }) => ({}), {
681
- name: "${name}",
682
- });
683
-
684
- export const ${name} = ${name}Scope.entities;
553
+ export default registerPage(Page, { name: "${name}" });
685
554
  `;
686
- if (navigateToRoute) {
687
- this.routeChangesQueue.push({
688
- route: route,
689
- routeTestParams: routeTestParams,
690
- });
691
- }
692
- this.watcher?.unwatch(pagePath);
693
555
  await fs.mkdir(pagePath, { recursive: true });
694
556
  await this.writeFile(pageIndexPath, pageContent, "ts");
695
- await this.writeFile(scopePath, scopeContent, "ts");
696
557
  await this.handleNonVisualChangeByDeletingIds(pageIndexPath, pageContent);
697
- await this.handleNonVisualChangeByDeletingIds(scopePath, scopeContent);
698
- this.watcher?.add(pagePath);
699
- await this.addRoute(route, pageIndexPath);
700
558
  this.emit("addPage", pageIndexPath);
701
559
  };
702
- handleDeletePage = async (payload) => {
703
- const { name } = payload;
704
- if (!this.rootDir) {
705
- throw new Error("Root directory not set");
706
- }
707
- const pagePath = getPageFolder(this.rootDir, name);
708
- await fs.rm(pagePath, { recursive: true, force: true });
709
- await this.removeRoute(name);
710
- this.emit("deletePage", pagePath);
711
- };
712
- get routeChange() {
713
- return this.routeChangesQueue.shift();
714
- }
715
560
  handleReparent = async (payload, writeFile = true) => {
716
561
  const { from, to, changedProps, transaction } = payload;
717
562
  this.trackTransaction(transaction?.id);
718
- this.sourceTracker?.setProperties({
563
+ await this.sourceTracker?.setProperties({
719
564
  source: from.source,
720
565
  changes: changedProps ?? {},
721
566
  });
722
- this.sourceTracker?.moveElement({
567
+ await this.sourceTracker?.moveElement({
723
568
  from,
724
569
  to,
725
570
  });
@@ -733,7 +578,7 @@ export const ${name} = ${name}Scope.entities;
733
578
  };
734
579
  handleCreateComponent = async (payload, writeFile = true) => {
735
580
  this.trackTransaction(payload.transaction?.id);
736
- const sourceId = this.sourceTracker?.addElement(payload);
581
+ const sourceId = await this.sourceTracker?.addElement(payload);
737
582
  if (writeFile) {
738
583
  const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
739
584
  await this.writeChanges(changes ?? [], (fileName) => {
@@ -747,7 +592,7 @@ export const ${name} = ${name}Scope.entities;
747
592
  const { elements, transaction } = payload;
748
593
  this.trackTransaction(transaction?.id);
749
594
  for (const element of elements) {
750
- this.sourceTracker?.deleteElement({
595
+ await this.sourceTracker?.deleteElement({
751
596
  source: element.source,
752
597
  scopeName: element.scopeName,
753
598
  });
@@ -763,7 +608,7 @@ export const ${name} = ${name}Scope.entities;
763
608
  handleSetProperty = async (payload, writeFile = true) => {
764
609
  const { element: { source }, property, value, transaction, } = payload;
765
610
  this.trackTransaction(transaction?.id);
766
- this.sourceTracker?.setProperty({
611
+ await this.sourceTracker?.setProperty({
767
612
  source,
768
613
  property,
769
614
  info: value,
@@ -778,7 +623,7 @@ export const ${name} = ${name}Scope.entities;
778
623
  handleSetProperties = async (payload, writeFile = true) => {
779
624
  const { element: { source }, properties, transaction, } = payload;
780
625
  this.trackTransaction(transaction?.id);
781
- this.sourceTracker?.setProperties({
626
+ await this.sourceTracker?.setProperties({
782
627
  source,
783
628
  changes: properties,
784
629
  });
@@ -834,152 +679,11 @@ export const ${name} = ${name}Scope.entities;
834
679
  this.flushTransactions();
835
680
  return returnValues;
836
681
  };
837
- // MARK: entity operations
838
- handleAddEntity = async (payload) => {
839
- this.sourceTracker?.addEntity({
840
- scopeId: payload.scopeId,
841
- entity: {
842
- type: payload.type,
843
- name: payload.name,
844
- attributes: payload.attributes,
845
- },
846
- });
847
- const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
848
- await this.writeChanges(changes, (fileName) => {
849
- this.emit("addEntity", fileName, payload);
850
- });
851
- };
852
- handleUpdateEntity = async (payload) => {
853
- this.sourceTracker?.updateEntity({
854
- entityId: payload.entityId,
855
- updates: payload.updates,
856
- });
857
- const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
858
- await this.writeChanges(changes, (fileName) => {
859
- this.emit("updateEntity", fileName, payload);
860
- });
861
- };
862
- handleDeleteEntity = async (payload) => {
863
- const deletedEntityName = this.sourceTracker?.deleteEntity({
864
- entityId: payload.entityId,
865
- });
866
- const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
867
- await this.writeChanges(changes, (fileName) => {
868
- if (deletedEntityName) {
869
- this.emit("deleteEntity", fileName, deletedEntityName);
870
- }
871
- });
872
- };
873
- handleUpdateTheme = async (payload) => {
874
- const { theme } = payload;
875
- if (!this.rootDir) {
876
- throw new Error("Root directory not set");
877
- }
878
- const filePath = path.join(this.rootDir, APP_THEME_FILE_NAME);
879
- this.sourceTracker?.updateTheme({
880
- themeFilePath: filePath,
881
- theme,
882
- });
883
- const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
884
- await this.writeChanges(changes);
885
- };
886
- // MARK: rename operations
887
- handleRenameElement = async (payload) => {
888
- if (payload.kind === "component") {
889
- return this.handleRenameComponent(payload);
890
- }
891
- else if (payload.kind === "entity") {
892
- return this.handleRenameEntity(payload);
893
- }
894
- else if (payload.kind === "page") {
895
- return this.handleRenamePage(payload);
896
- }
897
- };
898
- handleRenameComponent = async (payload) => {
899
- const { elementId, newName, oldName, scopeName } = payload;
900
- await this.sourceTracker?.renameComponent({
901
- widgetSourceId: elementId,
902
- oldName,
903
- newName,
904
- scopeName,
905
- });
906
- await this.renameIdentifierInApis({
907
- elementId,
908
- oldName,
909
- newName,
910
- });
911
- const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
912
- await this.writeChanges(changes, (fileName) => {
913
- this.emit("renameComponent", fileName);
914
- });
915
- };
916
- handleRenameEntity = async (payload) => {
917
- const { elementId, newName, oldName, scopeName } = payload;
918
- this.sourceTracker?.renameEntity({
919
- entityId: elementId,
920
- oldName,
921
- newName,
922
- scopeName,
923
- });
924
- await this.renameIdentifierInApis(payload);
925
- const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
926
- await this.writeChanges(changes, (fileName) => {
927
- this.emit("renameEntity", fileName);
928
- });
929
- };
930
- handleRenamePage = async (payload) => {
931
- const { newName, oldName } = payload;
932
- if (!this.rootDir) {
933
- throw new Error("Root directory not set");
934
- }
935
- const newPageFolder = getPageFolder(this.rootDir, newName);
936
- const newIndexFilePath = path.join(newPageFolder, "index.tsx");
937
- const oldIndexFilePath = path.join(this.rootDir, "pages", oldName, "index.tsx");
938
- const oldPageFolder = getPageFolder(this.rootDir, oldName);
939
- this.watcher?.unwatch(newPageFolder);
940
- this.watcher?.unwatch(oldPageFolder);
941
- const existingRoute = Object.keys(this.routes).find((route) => this.routes[route]?.file ===
942
- this.getRelativeRoutePath(oldIndexFilePath));
943
- if (!existingRoute) {
944
- throw new Error(`Route for ${oldName} not found`);
945
- }
946
- // Write the name attribute to the page file
947
- await this.sourceTracker?.renamePage({
948
- newName,
949
- filePath: oldIndexFilePath,
950
- });
951
- const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
952
- await this.writeChanges(changes);
953
- // A rename of a folder is an unlink followed by an add, so the file watcher will take over initializing this
954
- // "new" page
955
- await fs.rename(oldPageFolder, newPageFolder);
956
- // Now we just clean up the routes
957
- await this.removeRoute(oldIndexFilePath);
958
- await this.addRoute(existingRoute, newIndexFilePath);
959
- const newIndexFile = await readFile(newIndexFilePath);
960
- if (!newIndexFile) {
961
- throw new Error(`New index file ${newIndexFilePath} not found`);
962
- }
963
- this.tsFiles[newIndexFilePath] = newIndexFile;
964
- await this.handleNonVisualChangeByDeletingIds(newIndexFilePath, newIndexFile);
965
- this.emit("renamePage", newIndexFilePath);
966
- // Re-add the watcher
967
- this.watcher?.add(newPageFolder);
968
- this.watcher?.add(oldPageFolder);
969
- };
970
- getRelativeRoutePath(filePath) {
971
- if (!this.rootDir) {
972
- throw new Error("Root directory not set");
973
- }
974
- // no leading slash
975
- return path.relative(path.join(this.rootDir, PAGES_DIRECTORY), filePath);
976
- }
977
- // MARK: API operations
978
- handleUpdateApi = async (payload) => {
979
- const { api } = payload;
980
- if (!this.sourceTracker) {
981
- throw new Error("Source tracker not initialized");
982
- }
682
+ handleUpdateApi = async (payload) => {
683
+ const { api } = payload;
684
+ if (!this.sourceTracker) {
685
+ throw new Error("Source tracker not initialized");
686
+ }
983
687
  if (!this.rootDir) {
984
688
  throw new Error("Root directory not set");
985
689
  }
@@ -990,24 +694,24 @@ export const ${name} = ${name}Scope.entities;
990
694
  if (!apiName) {
991
695
  throw new Error("API name is not set");
992
696
  }
993
- const apiFilePath = getApiFilePath(this.rootDir, api.pageName, apiName);
994
- const apiDir = path.dirname(apiFilePath);
995
- const isNewApi = !this.getApiFiles()[apiFilePath];
697
+ const apiDir = path.join(this.rootDir, "pages", api.pageName, "apis", apiName);
698
+ const apiPath = path.join(apiDir, "api.yaml");
699
+ const isNewApi = !this.getApiFiles()[apiPath];
996
700
  try {
997
701
  const stats = await fs.stat(apiDir);
998
702
  if (!stats.isDirectory()) {
999
- await this.fsOperationQueue.enqueue(async () => await fs.mkdir(apiDir, { recursive: true }));
703
+ await fs.mkdir(apiDir, { recursive: true });
1000
704
  }
1001
705
  }
1002
706
  catch {
1003
- await this.fsOperationQueue.enqueue(async () => await fs.mkdir(apiDir, { recursive: true }));
707
+ await fs.mkdir(apiDir, { recursive: true });
1004
708
  }
1005
- await this.writeFile(apiFilePath, yaml.stringify(api.apiPb), "api");
709
+ await this.writeFile(apiPath, yaml.stringify(api.apiPb), "api");
1006
710
  const generationNumber = this.generationNumberSequence.next();
1007
711
  const apiDef = this.createClientApi(api);
1008
712
  let scopeId = "";
1009
713
  if (isNewApi) {
1010
- scopeId = await this.addApiToScope(api);
714
+ scopeId = await this.createScopedApi(api);
1011
715
  }
1012
716
  else {
1013
717
  const scopeDef = this.sourceTracker.getScopeDefinitionForPage(api.pageName);
@@ -1016,6 +720,20 @@ export const ${name} = ${name}Scope.entities;
1016
720
  this.emit("apiUpdate", { api: apiDef, scopeId });
1017
721
  return { api: apiDef, scopeId, generationNumber };
1018
722
  };
723
+ createScopedApi = async (api) => {
724
+ if (!this.sourceTracker) {
725
+ throw new Error("Source tracker not initialized");
726
+ }
727
+ // We want to add the API entity to our scope, but we do not want to emit entity events, because
728
+ // the API update event handles this particular side effect.
729
+ const scopeId = await this.sourceTracker.addApi({
730
+ pageName: api.pageName,
731
+ apiName: api.apiPb.metadata.name,
732
+ });
733
+ const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
734
+ await this.writeChanges(changes);
735
+ return scopeId;
736
+ };
1019
737
  handleDeleteApi = async (payload) => {
1020
738
  const logger = getLogger();
1021
739
  const { apis } = payload;
@@ -1023,34 +741,40 @@ export const ${name} = ${name}Scope.entities;
1023
741
  throw new Error("Root directory not set");
1024
742
  }
1025
743
  const rootDir = this.rootDir;
1026
- const deletedApis = [];
1027
- for (const { apiName, pageName } of apis) {
1028
- const apiFilePath = getApiFilePath(rootDir, pageName, apiName);
1029
- const api = this.apiFiles[apiFilePath];
1030
- if (!api || !this.sourceTracker) {
1031
- continue;
1032
- }
1033
- const apiDir = path.dirname(apiFilePath);
1034
- try {
1035
- const stats = await fs.stat(apiDir);
1036
- if (stats.isDirectory()) {
1037
- await fs.rmdir(apiDir, { recursive: true });
744
+ const executeDeleteApis = Promise.all(apis.map(({ apiName, pageName }) => {
745
+ return new Promise(
746
+ // eslint-disable-next-line no-async-promise-executor
747
+ async (resolve) => {
748
+ const apiFilePath = path.join(rootDir, "pages", pageName, "apis", apiName, "api.yaml");
749
+ const api = this.apiFiles[apiFilePath];
750
+ if (!api || !this.sourceTracker) {
751
+ return resolve(undefined);
1038
752
  }
1039
- delete this.apiFiles[apiFilePath];
1040
- const scopeId = this.sourceTracker.deleteApi({
1041
- pageName,
1042
- apiName,
1043
- });
1044
- deletedApis.push({
1045
- apiName,
1046
- pageName,
1047
- scopeId,
1048
- });
1049
- }
1050
- catch (e) {
1051
- logger.warn(`Could not delete api ${apiFilePath} ${JSON.stringify(e)}`);
1052
- }
1053
- }
753
+ const apiDir = path.join(rootDir, "pages", pageName, "apis", apiName);
754
+ try {
755
+ const stats = await fs.stat(apiDir);
756
+ if (stats.isDirectory()) {
757
+ await fs.rmdir(apiDir, { recursive: true });
758
+ }
759
+ delete this.apiFiles[apiFilePath];
760
+ const scopeId = await this.sourceTracker.deleteApi({
761
+ pageName,
762
+ apiName,
763
+ });
764
+ resolve({
765
+ apiName,
766
+ pageName,
767
+ scopeId,
768
+ });
769
+ }
770
+ catch (e) {
771
+ logger.warn(`Could not delete api ${apiFilePath} ${JSON.stringify(e)}`);
772
+ resolve(undefined);
773
+ }
774
+ resolve(undefined);
775
+ });
776
+ }));
777
+ const deletedApis = (await executeDeleteApis).filter((api) => api !== undefined);
1054
778
  const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
1055
779
  await this.writeChanges(changes);
1056
780
  this.emit("apiDelete", {
@@ -1131,40 +855,229 @@ export const ${name} = ${name}Scope.entities;
1131
855
  // TODO: Should I delete here?
1132
856
  }));
1133
857
  };
1134
- async removeApiData(filePath) {
1135
- const api = this.apiFiles[filePath];
1136
- if (!api) {
1137
- return;
858
+ handleAddEntity = async (payload) => {
859
+ await this.sourceTracker?.addEntity({
860
+ scopeId: payload.scopeId,
861
+ entity: {
862
+ type: payload.type,
863
+ name: payload.name,
864
+ attributes: payload.attributes,
865
+ },
866
+ });
867
+ const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
868
+ await this.writeChanges(changes, (fileName) => {
869
+ this.emit("addEntity", fileName, payload);
870
+ });
871
+ };
872
+ handleUpdateEntity = async (payload) => {
873
+ await this.sourceTracker?.updateEntity({
874
+ entityId: payload.entityId,
875
+ updates: payload.updates,
876
+ });
877
+ const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
878
+ await this.writeChanges(changes, (fileName) => {
879
+ this.emit("updateEntity", fileName, payload);
880
+ });
881
+ };
882
+ handleDeleteEntity = async (payload) => {
883
+ const deletedEntityName = await this.sourceTracker?.deleteEntity({
884
+ entityId: payload.entityId,
885
+ });
886
+ const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
887
+ await this.writeChanges(changes, (fileName) => {
888
+ if (deletedEntityName) {
889
+ this.emit("deleteEntity", fileName, deletedEntityName);
890
+ }
891
+ });
892
+ };
893
+ handleUpdateTheme = async (payload) => {
894
+ const { theme } = payload;
895
+ if (!this.rootDir) {
896
+ throw new Error("Root directory not set");
1138
897
  }
1139
- delete this.apiFiles[filePath];
1140
- this.sourceTracker?.deleteApi({
1141
- pageName: api.pageName,
1142
- apiName: api.apiPb.metadata.name,
898
+ const filePath = path.join(this.rootDir, APP_THEME_FILE_NAME);
899
+ await this.sourceTracker?.updateTheme({
900
+ themeFilePath: filePath,
901
+ theme,
1143
902
  });
1144
903
  const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
1145
904
  await this.writeChanges(changes);
1146
- const scopeId = this.sourceTracker?.getScopeDefinitionForPage(api.pageName)?.id;
1147
- this.emit("apiManualDelete", {
1148
- api: {
1149
- id: getClientApiId(api.apiPb.metadata.name, api.pageName),
1150
- apiName: api.apiPb.metadata.name,
1151
- // TODO(saksham): get pagename more defensively
1152
- pageName: getPageName(filePath),
1153
- scopeId,
1154
- },
905
+ };
906
+ handleRenameElement = async (payload) => {
907
+ if (payload.kind === "component") {
908
+ return this.handleRenameComponent(payload);
909
+ }
910
+ else if (payload.kind === "entity") {
911
+ return this.handleRenameEntity(payload);
912
+ }
913
+ else if (payload.kind === "page") {
914
+ return this.handleRenamePage(payload);
915
+ }
916
+ };
917
+ handleRenameComponent = async (payload) => {
918
+ const { elementId, newName, oldName, scopeName } = payload;
919
+ await this.sourceTracker?.renameComponent({
920
+ widgetSourceId: elementId,
921
+ oldName,
922
+ newName,
923
+ scopeName,
1155
924
  });
925
+ await this.renameIdentifierInApis({
926
+ elementId,
927
+ oldName,
928
+ newName,
929
+ });
930
+ const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
931
+ await this.writeChanges(changes, (fileName) => {
932
+ this.emit("renameComponent", fileName);
933
+ });
934
+ };
935
+ handleRenameEntity = async (payload) => {
936
+ const { elementId, newName, oldName, scopeName } = payload;
937
+ await this.sourceTracker?.renameEntity({
938
+ entityId: elementId,
939
+ oldName,
940
+ newName,
941
+ scopeName,
942
+ });
943
+ await this.renameIdentifierInApis(payload);
944
+ const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
945
+ await this.writeChanges(changes, (fileName) => {
946
+ this.emit("renameEntity", fileName);
947
+ });
948
+ };
949
+ handleRenamePage = async (payload) => {
950
+ const { newName, oldName } = payload;
951
+ if (!this.rootDir) {
952
+ throw new Error("Root directory not set");
953
+ }
954
+ const newPageFolder = getPageFolder(this.rootDir, newName);
955
+ const newIndexFilePath = path.join(newPageFolder, "index.tsx");
956
+ const oldIndexFilePath = path.join(this.rootDir, "pages", oldName, "index.tsx");
957
+ const oldPageFolder = getPageFolder(this.rootDir, oldName);
958
+ this.watcher?.unwatch(newPageFolder);
959
+ this.watcher?.unwatch(oldPageFolder);
960
+ const existingRoute = Object.keys(this.routes).find((route) => this.routes[route]?.file ===
961
+ this.getRelativeRoutePath(oldIndexFilePath));
962
+ if (!existingRoute) {
963
+ throw new Error(`Route for ${oldName} not found`);
964
+ }
965
+ // Write the name attribute to the page file
966
+ await this.sourceTracker?.renamePage({
967
+ newName,
968
+ filePath: oldIndexFilePath,
969
+ });
970
+ const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
971
+ await this.writeChanges(changes);
972
+ // A rename of a folder is an unlink followed by an add, so the file watcher will take over initializing this
973
+ // "new" page
974
+ await fs.rename(oldPageFolder, newPageFolder);
975
+ // Now we just clean up the routes
976
+ await this.removeRoute(oldIndexFilePath);
977
+ await this.addRoute(existingRoute, newIndexFilePath);
978
+ const newIndexFile = await readFile(newIndexFilePath);
979
+ if (!newIndexFile) {
980
+ throw new Error(`New index file ${newIndexFilePath} not found`);
981
+ }
982
+ this.tsFiles[newIndexFilePath] = newIndexFile;
983
+ await this.handleNonVisualChangeByDeletingIds(newIndexFilePath, newIndexFile);
984
+ this.emit("renamePage", newIndexFilePath);
985
+ // Re-add the watcher
986
+ this.watcher?.add(newPageFolder);
987
+ this.watcher?.add(oldPageFolder);
988
+ };
989
+ getPageRoots(filePath) {
990
+ const scopeFilePath = path.join(path.dirname(filePath), "index.tsx");
991
+ const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
992
+ if (!currentFile) {
993
+ return null;
994
+ }
995
+ return getPageRoots(filePath, currentFile);
1156
996
  }
1157
- getApiFiles() {
1158
- return Object.keys(this.apiFiles).reduce((acc, key) => {
1159
- if (!this.apiFiles[key]) {
1160
- return acc;
997
+ getScope(filePath) {
998
+ // get the scope.ts file in the same directory as path
999
+ const scopeFilePath = path.join(path.dirname(filePath), SCOPE_FILE);
1000
+ const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
1001
+ if (!currentFile) {
1002
+ console.log("File not found", scopeFilePath);
1003
+ return null;
1004
+ }
1005
+ const scope = getScope(scopeFilePath, currentFile);
1006
+ return scope;
1007
+ }
1008
+ getRoutes() {
1009
+ const routes = [];
1010
+ for (const [path, { file }] of Object.entries(this.routes)) {
1011
+ routes.push({
1012
+ path,
1013
+ component: file,
1014
+ });
1015
+ }
1016
+ return routes;
1017
+ }
1018
+ async addRoute(route, filePath) {
1019
+ if (!this.rootDir) {
1020
+ throw new Error("Root directory not set");
1021
+ }
1022
+ this.routes[route] = { file: this.getRelativeRoutePath(filePath) };
1023
+ await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
1024
+ }
1025
+ async removeRoute(filePath) {
1026
+ if (!this.rootDir) {
1027
+ throw new Error("Root directory not set");
1028
+ }
1029
+ const relativeFilePath = this.getRelativeRoutePath(filePath);
1030
+ this.routes = Object.fromEntries(Object.entries(this.routes).filter(([_, value]) => value.file !== relativeFilePath));
1031
+ await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
1032
+ }
1033
+ async writeChanges(changes, callback) {
1034
+ return Promise.all(changes.map(async ({ fileName, source, kind }) => {
1035
+ await this.writeFile(fileName, source, kind);
1036
+ return callback?.(fileName, source);
1037
+ }));
1038
+ }
1039
+ getNodeForWidgetSourceId(id) {
1040
+ return this.sourceTracker?.getElementToLocation(id);
1041
+ }
1042
+ getAstForWidgetSourceId(id) {
1043
+ const filePath = this.sourceTracker?.getElementToFilePath(id);
1044
+ if (!filePath) {
1045
+ return null;
1046
+ }
1047
+ return this.sourceTracker?.getCurrentFiles()[filePath]?.ast;
1048
+ }
1049
+ renameIdentifierInApis = async ({ elementId, oldName, newName, parentBinding, }) => {
1050
+ const apisInScope = structuredClone(this.getApisInScope(elementId));
1051
+ await this.renameManager.renameEntityInApis({
1052
+ oldName,
1053
+ newName,
1054
+ parentBinding,
1055
+ apis: apisInScope,
1056
+ });
1057
+ // only save the APIs that have changed
1058
+ await Promise.all(apisInScope.map(({ api, filePath }) => {
1059
+ if (isEqual(api?.apiPb, this.apiFiles[filePath]?.apiPb)) {
1060
+ return Promise.resolve();
1161
1061
  }
1162
- acc[key] = {
1163
- api: this.createClientApi(this.apiFiles[key]),
1164
- scopeId: this.apiFiles[key].scopeId,
1165
- };
1166
- return acc;
1167
- }, {});
1062
+ return this.writeFile(filePath, yaml.stringify(api?.apiPb), "api");
1063
+ }));
1064
+ };
1065
+ getApisInScope = (elementId) => {
1066
+ const filePath = this.sourceTracker?.getElementToFilePath(elementId);
1067
+ if (!filePath) {
1068
+ return [];
1069
+ }
1070
+ const pagePath = path.dirname(filePath);
1071
+ return Object.entries(this.apiFiles)
1072
+ .filter(([filePath]) => filePath.includes(pagePath))
1073
+ .map(([filePath, api]) => ({ api, filePath }));
1074
+ };
1075
+ getRelativeRoutePath(filePath) {
1076
+ if (!this.rootDir) {
1077
+ throw new Error("Root directory not set");
1078
+ }
1079
+ // no leading slash
1080
+ return path.relative(path.join(this.rootDir, PAGES_DIRECTORY), filePath);
1168
1081
  }
1169
1082
  // Utilities for converting server API format to Client API format
1170
1083
  // We internally save the API as the server does, but we return should always return it
@@ -1181,21 +1094,19 @@ export const ${name} = ${name}Scope.entities;
1181
1094
  },
1182
1095
  };
1183
1096
  }
1184
- addApiToScope = async (api) => {
1185
- if (!this.sourceTracker) {
1186
- throw new Error("Source tracker not initialized");
1187
- }
1188
- // We want to add the API entity to our scope, but we do not want to emit entity events, because
1189
- // the API update event handles this particular side effect.
1190
- const scopeId = await this.sourceTracker.addApi({
1191
- pageName: api.pageName,
1192
- apiName: api.apiPb.metadata.name,
1193
- });
1194
- const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
1195
- await this.writeChanges(changes);
1196
- return scopeId;
1197
- };
1198
- async processApiFileUpdates(filePath, fileType) {
1097
+ formatApisToClientApis(serverApis) {
1098
+ return Object.keys(serverApis).reduce((acc, key) => {
1099
+ if (!serverApis[key]) {
1100
+ return acc;
1101
+ }
1102
+ acc[key] = {
1103
+ api: this.createClientApi(serverApis[key]),
1104
+ scopeId: serverApis[key].scopeId,
1105
+ };
1106
+ return acc;
1107
+ }, {});
1108
+ }
1109
+ async processApiUpdates(filePath, fileType) {
1199
1110
  let yamlPath = filePath;
1200
1111
  if (fileType.type === "python-api-step" ||
1201
1112
  fileType.type === "js-api-step") {
@@ -1213,7 +1124,7 @@ export const ${name} = ${name}Scope.entities;
1213
1124
  const parsedData = { apiPb: apiContent?.api };
1214
1125
  if (!(yamlPath in this.apiFiles &&
1215
1126
  isEqual(this.apiFiles[yamlPath]?.apiPb, parsedData.apiPb))) {
1216
- const { updatedApi, pageName, isNewApi } = this.updateInternalApiData({
1127
+ const { updatedApi, pageName, isNewApi } = this.updateApi({
1217
1128
  api: parsedData.apiPb,
1218
1129
  stepPathMap: apiContent.stepPathMap,
1219
1130
  }, yamlPath);
@@ -1224,7 +1135,7 @@ export const ${name} = ${name}Scope.entities;
1224
1135
  return;
1225
1136
  }
1226
1137
  if (isNewApi) {
1227
- await this.addApiToScope(updatedApi);
1138
+ await this.createScopedApi(updatedApi);
1228
1139
  }
1229
1140
  this.emit("apiManualUpdate", {
1230
1141
  api: this.createClientApi(updatedApi),
@@ -1240,56 +1151,9 @@ export const ${name} = ${name}Scope.entities;
1240
1151
  logger.error(`Error updating API: ${yamlPath}, error: ${error}`);
1241
1152
  }
1242
1153
  }
1243
- updateInternalApiData = (content, path) => {
1244
- if (!this.rootDir) {
1245
- throw new Error("Root directory not set");
1246
- }
1247
- const { api: apiContents, stepPathMap } = content;
1248
- const pageName = getPageName(path);
1249
- let scopeId = this.sourceTracker?.getScopeDefinitionForPage(pageName)?.id;
1250
- if (!scopeId) {
1251
- console.warn("Scope ID not found for API", apiContents.metadata.name);
1252
- scopeId = "";
1253
- }
1254
- const updatedApi = {
1255
- apiPb: yaml.parse(JSON.stringify(apiContents)),
1256
- pageName,
1257
- stepPathMap,
1258
- scopeId,
1259
- };
1260
- const isNewApi = !this.apiFiles[path];
1261
- this.apiFiles[path] = updatedApi;
1262
- return { updatedApi, pageName, isNewApi };
1263
- };
1264
- renameIdentifierInApis = async ({ elementId, oldName, newName, parentBinding, }) => {
1265
- const apisInScope = structuredClone(this.getApisInScope(elementId));
1266
- await this.renameManager.renameEntityInApis({
1267
- oldName,
1268
- newName,
1269
- parentBinding,
1270
- apis: apisInScope,
1271
- });
1272
- // only save the APIs that have changed
1273
- await Promise.all(apisInScope.map(({ api, filePath }) => {
1274
- if (isEqual(api?.apiPb, this.apiFiles[filePath]?.apiPb)) {
1275
- return Promise.resolve();
1276
- }
1277
- return this.writeFile(filePath, yaml.stringify(api?.apiPb), "api");
1278
- }));
1279
- };
1280
- getApisInScope = (elementId) => {
1281
- const filePath = this.sourceTracker?.getElementToFilePath(elementId);
1282
- if (!filePath) {
1283
- return [];
1284
- }
1285
- const pagePath = path.dirname(filePath);
1286
- return Object.entries(this.apiFiles)
1287
- .filter(([filePath]) => filePath.includes(pagePath))
1288
- .map(([filePath, api]) => ({ api, filePath }));
1289
- };
1290
1154
  }
1291
1155
  // Add new mock implementation
1292
- export class MockFileSyncManager extends FileSystemManager {
1156
+ export class MockFileSyncManager extends FileSyncManager {
1293
1157
  tsFiles = {};
1294
1158
  apiFiles = {};
1295
1159
  async watch(_watcher, _path) {
@@ -1343,7 +1207,8 @@ async function readFile(path) {
1343
1207
  // in order for try-catch to work, we need to intentionally await the readFile call,
1344
1208
  // otherwise the error won't be be caught
1345
1209
  // see: https://github.com/nodejs/node/issues/51894#issuecomment-1974017737
1346
- return await fs.readFile(path, "utf-8");
1210
+ const content = await fs.readFile(path, "utf-8");
1211
+ return content;
1347
1212
  }
1348
1213
  catch (e) {
1349
1214
  getLogger().error(`error reading file: ${path}`, getErrorMeta(e));
@@ -1364,4 +1229,12 @@ async function readFiles(dir) {
1364
1229
  const getMergedApiContent = async (path) => {
1365
1230
  return readAppApiYamlFile(path.split("/").slice(0, -1).join("/"));
1366
1231
  };
1232
+ const getPageName = (path) => {
1233
+ const parts = path.split("/");
1234
+ const pagesIndex = parts.findIndex((part) => part === "pages");
1235
+ if (pagesIndex !== -1 && parts[pagesIndex + 1]) {
1236
+ return parts[pagesIndex + 1] ?? "";
1237
+ }
1238
+ return "";
1239
+ };
1367
1240
  //# sourceMappingURL=file-system-manager.js.map