@selesai/code 0.9.15 → 0.10.1

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 (269) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +2 -2
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +2 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/modes/interactive/components/settings-selector.d.ts +1 -1
  118. package/dist/modes/interactive/interactive-mode.js +1 -1
  119. package/dist/skills/design-references/SKILL.md +31 -0
  120. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  121. package/dist/themes/adventure.json +84 -0
  122. package/dist/themes/adwaita-dark.json +84 -0
  123. package/dist/themes/arcoiris.json +84 -0
  124. package/dist/themes/arthur.json +84 -0
  125. package/dist/themes/atom.json +84 -0
  126. package/dist/themes/aura.json +84 -0
  127. package/dist/themes/black-metal-bathory.json +84 -0
  128. package/dist/themes/black-metal-burzum.json +84 -0
  129. package/dist/themes/black-metal-khold.json +84 -0
  130. package/dist/themes/box.json +84 -0
  131. package/dist/themes/brogrammer.json +84 -0
  132. package/dist/themes/carbonfox.json +84 -0
  133. package/dist/themes/catppuccin-mocha.json +84 -0
  134. package/dist/themes/citruszest.json +84 -0
  135. package/dist/themes/cursor-dark.json +84 -0
  136. package/dist/themes/cutie-pro.json +84 -0
  137. package/dist/themes/dark-modern.json +84 -0
  138. package/dist/themes/dark-pastel.json +84 -0
  139. package/dist/themes/dimmed-monokai.json +84 -0
  140. package/dist/themes/doom-peacock.json +84 -0
  141. package/dist/themes/dracula-plus.json +84 -0
  142. package/dist/themes/dracula.json +80 -0
  143. package/dist/themes/earthsong.json +84 -0
  144. package/dist/themes/everforest-dark-hard.json +84 -0
  145. package/dist/themes/everforest-dark.json +80 -0
  146. package/dist/themes/fahrenheit.json +84 -0
  147. package/dist/themes/flatland.json +84 -0
  148. package/dist/themes/flexoki-dark.json +84 -0
  149. package/dist/themes/front-end-delight.json +84 -0
  150. package/dist/themes/fun-forrest.json +84 -0
  151. package/dist/themes/galizur.json +84 -0
  152. package/dist/themes/github-dark-colorblind.json +84 -0
  153. package/dist/themes/github-dark-high-contrast.json +84 -0
  154. package/dist/themes/glacier.json +84 -0
  155. package/dist/themes/gruber-darker.json +84 -0
  156. package/dist/themes/gruvbox-dark-hard.json +84 -0
  157. package/dist/themes/gruvbox-dark.json +84 -0
  158. package/dist/themes/gruvbox-material.json +84 -0
  159. package/dist/themes/guezwhoz.json +84 -0
  160. package/dist/themes/hacktober.json +84 -0
  161. package/dist/themes/hardcore.json +84 -0
  162. package/dist/themes/havn-skumring.json +84 -0
  163. package/dist/themes/ic-orange-ppl.json +84 -0
  164. package/dist/themes/iterm2-smoooooth.json +84 -0
  165. package/dist/themes/iterm2-tango-dark.json +84 -0
  166. package/dist/themes/japanesque.json +84 -0
  167. package/dist/themes/jellybeans.json +84 -0
  168. package/dist/themes/kanagawa-wave.json +84 -0
  169. package/dist/themes/kurokula.json +84 -0
  170. package/dist/themes/later-this-evening.json +84 -0
  171. package/dist/themes/lovelace.json +84 -0
  172. package/dist/themes/material-darker.json +84 -0
  173. package/dist/themes/matte-black.json +84 -0
  174. package/dist/themes/mellow.json +84 -0
  175. package/dist/themes/miasma.json +84 -0
  176. package/dist/themes/nord.json +80 -0
  177. package/dist/themes/nvim-dark.json +84 -0
  178. package/dist/themes/one-dark.json +80 -0
  179. package/dist/themes/popping-and-locking.json +84 -0
  180. package/dist/themes/rose-pine.json +80 -0
  181. package/dist/themes/sea-shells.json +84 -0
  182. package/dist/themes/sleepy-hollow.json +84 -0
  183. package/dist/themes/smyck.json +84 -0
  184. package/dist/themes/solarized-dark.json +80 -0
  185. package/dist/themes/tokyo-night.json +80 -0
  186. package/dist/themes/tomorrow-night-bright.json +84 -0
  187. package/dist/themes/tomorrow-night-burns.json +84 -0
  188. package/dist/themes/tomorrow-night.json +84 -0
  189. package/dist/themes/twilight.json +84 -0
  190. package/dist/themes/vague.json +84 -0
  191. package/dist/themes/vesper.json +84 -0
  192. package/dist/themes/xcode-dark-hc.json +84 -0
  193. package/docs/custom-provider.md +0 -1
  194. package/package.json +3 -3
  195. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  196. package/dist/extensions/auto-model/classifier.ts +0 -23
  197. package/dist/extensions/auto-model/config.ts +0 -51
  198. package/dist/extensions/auto-model/index.ts +0 -22
  199. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  200. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  201. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  202. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  203. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  204. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  205. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  206. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  207. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  208. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  209. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  210. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  211. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  212. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  213. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  214. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  215. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  216. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  217. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  218. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  219. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  220. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  221. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  222. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  223. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  224. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  225. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  226. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  227. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  228. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  229. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  230. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  231. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  232. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  233. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  234. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  235. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  236. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  237. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  238. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  239. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  240. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  241. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  242. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  243. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  244. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  245. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  246. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  247. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  248. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  249. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  250. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  251. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  252. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  253. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  254. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  255. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  256. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  257. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  258. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  259. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  260. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  261. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  262. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  263. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  264. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  265. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  266. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  267. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  268. package/dist/extensions/preview-tools-disabled.ts +0 -11
  269. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -0,0 +1,321 @@
1
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it } from "vitest";
5
+ import {
6
+ __test__,
7
+ PACKAGE_VERSION_ECOSYSTEMS,
8
+ readPackageVersion,
9
+ } from "../extensions/zentui/package-version";
10
+
11
+ function makeProject(files: Record<string, string>): string {
12
+ const cwd = mkdtempSync(join(tmpdir(), "zentui-package-version-"));
13
+ for (const [name, content] of Object.entries(files)) {
14
+ const full = join(cwd, name);
15
+ if (name.includes("/")) {
16
+ mkdirSync(join(cwd, name.slice(0, name.indexOf("/"))), { recursive: true });
17
+ }
18
+ writeFileSync(full, content, "utf8");
19
+ }
20
+ return cwd;
21
+ }
22
+
23
+ function expectEcosystem(cwd: string, ecosystem: string, version: string) {
24
+ const result = readPackageVersion(cwd);
25
+ expect(result).not.toBeNull();
26
+ if (result === null) throw new Error("expected package version result");
27
+ expect(result.ecosystem).toBe(ecosystem);
28
+ expect(result.version).toBe(version);
29
+ }
30
+
31
+ describe("readPackageVersion", () => {
32
+ it("returns null when no manifest is present", () => {
33
+ const cwd = makeProject({});
34
+ expect(readPackageVersion(cwd)).toBeNull();
35
+ });
36
+
37
+ it("parses package.json for npm/bun projects (top-level `version`)", () => {
38
+ const cwd = makeProject({
39
+ "package.json": JSON.stringify({ name: "demo", version: "1.2.3" }),
40
+ });
41
+ expectEcosystem(cwd, "nodejs", "1.2.3");
42
+ });
43
+
44
+ it("strips surrounding quotes from package.json values", () => {
45
+ const cwd = makeProject({
46
+ "package.json": JSON.stringify({ name: "demo", version: '"2.0.0"' }, null, 2),
47
+ });
48
+ expect(readPackageVersion(cwd)?.version).toBe("2.0.0");
49
+ });
50
+
51
+ it("returns null for malformed package.json JSON without throwing", () => {
52
+ const cwd = makeProject({ "package.json": "{ this is not json" });
53
+ expect(readPackageVersion(cwd)).toBeNull();
54
+ });
55
+
56
+ it("rejects terminal controls in a JSON-decoded package version", () => {
57
+ const cwd = makeProject({
58
+ "package.json": JSON.stringify({ version: "1.2.3\x1b]52;c;c2VjcmV0\x07" }),
59
+ });
60
+ expect(readPackageVersion(cwd)).toBeNull();
61
+ });
62
+
63
+ it("parses deno.json and deno.jsonc", () => {
64
+ const denoJson = makeProject({ "deno.json": JSON.stringify({ version: "0.1.0" }) });
65
+ const denoJsonc = makeProject({
66
+ "deno.jsonc": `// leading comment\n${JSON.stringify({ version: "0.2.0" })}`,
67
+ });
68
+ expect(readPackageVersion(denoJson)?.version).toBe("0.1.0");
69
+ expect(readPackageVersion(denoJsonc)?.version).toBe("0.2.0");
70
+ });
71
+
72
+ it("parses Maven pom.xml first child <version>", () => {
73
+ const cwd = makeProject({
74
+ "pom.xml":
75
+ "<project><modelVersion>4.0.0</modelVersion><version>3.1.4</version><artifactId>x</artifactId></project>",
76
+ });
77
+ expectEcosystem(cwd, "maven", "3.1.4");
78
+ });
79
+
80
+ it("parses Gradle gradle.properties", () => {
81
+ const cwd = makeProject({ "gradle.properties": "version=4.5.6\ngroup=com.example\n" });
82
+ expectEcosystem(cwd, "gradle", "4.5.6");
83
+ });
84
+
85
+ it("parses Python pyproject.toml [project].version", () => {
86
+ const cwd = makeProject({
87
+ "pyproject.toml": `[project]\nname = "x"\nversion = "1.0.0"\n`,
88
+ });
89
+ expectEcosystem(cwd, "python", "1.0.0");
90
+ });
91
+
92
+ it("parses Python pyproject.toml poetry fallback", () => {
93
+ const cwd = makeProject({
94
+ "pyproject.toml": `[tool.poetry]\nname = "x"\nversion = "2.0.0"\n`,
95
+ });
96
+ expectEcosystem(cwd, "python", "2.0.0");
97
+ });
98
+
99
+ it("parses Python setup.cfg [metadata].version", () => {
100
+ const cwd = makeProject({
101
+ "setup.cfg": "[metadata]\nname = x\nversion = 3.2.1\n[options]\ninstall_requires = []\n",
102
+ });
103
+ expectEcosystem(cwd, "python", "3.2.1");
104
+ });
105
+
106
+ it("parses Rust Cargo.toml [package].version", () => {
107
+ const cwd = makeProject({
108
+ "Cargo.toml": `[package]\nname = "x"\nversion = "0.4.2"\nedition = "2021"\n`,
109
+ });
110
+ expectEcosystem(cwd, "rust", "0.4.2");
111
+ });
112
+
113
+ it("parses Rust Cargo.toml workspace-inherited version", () => {
114
+ const cwd = makeProject({
115
+ "Cargo.toml": `[package]\nname = "x"\nversion.workspace = true\n\n[workspace]\nversion = "0.9.0"\n`,
116
+ });
117
+ expectEcosystem(cwd, "rust", "0.9.0");
118
+ });
119
+
120
+ it("parses PHP composer.json version", () => {
121
+ const cwd = makeProject({
122
+ "composer.json": JSON.stringify({ name: "demo/demo", version: "1.4.0" }),
123
+ });
124
+ expectEcosystem(cwd, "php", "1.4.0");
125
+ });
126
+
127
+ it("parses Crystal shard.yml top-level version", () => {
128
+ const cwd = makeProject({
129
+ "shard.yml": `name: x\nversion: 0.3.0\n\ndependencies:\n yaml: ~> 1.0\n`,
130
+ });
131
+ expectEcosystem(cwd, "crystal", "0.3.0");
132
+ });
133
+
134
+ it("parses Dart pubspec.yaml version", () => {
135
+ const cwd = makeProject({
136
+ "pubspec.yaml": `name: x\nversion: 2.5.0\nenvironment:\n sdk: ">=3.0.0"\n`,
137
+ });
138
+ expectEcosystem(cwd, "dart", "2.5.0");
139
+ });
140
+
141
+ it("parses Dart pubspec.yml fallback", () => {
142
+ const cwd = makeProject({
143
+ "pubspec.yml": `name: x\nversion: 2.6.0\n`,
144
+ });
145
+ expectEcosystem(cwd, "dart", "2.6.0");
146
+ });
147
+
148
+ it("parses Elixir mix.exs project version literal", () => {
149
+ const cwd = makeProject({
150
+ "mix.exs":
151
+ 'defmodule Demo.MixProject do\n use Mix.Project\n\n def project do\n [\n app: :demo,\n version: "1.7.3",\n elixir: "~> 1.14"\n ]\n end\nend\n',
152
+ });
153
+ expectEcosystem(cwd, "elixir", "1.7.3");
154
+ });
155
+
156
+ it("parses elm.json top-level version", () => {
157
+ const cwd = makeProject({
158
+ "elm.json": JSON.stringify({
159
+ type: "application",
160
+ "source-directories": ["src"],
161
+ "elm-version": "0.19.1",
162
+ version: "1.0.0",
163
+ dependencies: { direct: {}, indirect: {} },
164
+ "test-dependencies": { direct: {}, indirect: {} },
165
+ }),
166
+ });
167
+ expectEcosystem(cwd, "elm", "1.0.0");
168
+ });
169
+
170
+ it("parses Fortran fpm.toml version", () => {
171
+ const cwd = makeProject({
172
+ "fpm.toml": `[package]\nname = "x"\nversion = "0.2.1"\n`,
173
+ });
174
+ expectEcosystem(cwd, "fortran", "0.2.1");
175
+ });
176
+
177
+ it("parses Gleam gleam.toml top-level version", () => {
178
+ const cwd = makeProject({
179
+ "gleam.toml": `name = "x"\nversion = "1.0.0"\ndescription = "demo"\n`,
180
+ });
181
+ expectEcosystem(cwd, "gleam", "1.0.0");
182
+ });
183
+
184
+ it("parses Haskell *.cabal version in package stanza", () => {
185
+ const cwd = makeProject({
186
+ "x.cabal": `cabal-version: 2.4\nname: x\nversion: 0.6.0\n\nlibrary\n exposed-modules: X\n build-depends: base >= 4.14\n`,
187
+ });
188
+ expectEcosystem(cwd, "haskell", "0.6.0");
189
+ });
190
+
191
+ it("parses Helm Chart.yaml chart version", () => {
192
+ const cwd = makeProject({
193
+ "Chart.yaml":
194
+ 'apiVersion: v2\nname: x\ndescription: demo\nversion: 0.1.2\nappVersion: "1.0.0"\n',
195
+ });
196
+ expectEcosystem(cwd, "helm", "0.1.2");
197
+ });
198
+
199
+ it("parses Julia Project.toml top-level version", () => {
200
+ const cwd = makeProject({
201
+ "Project.toml": `name = "X"\nversion = "0.5.2"\n\n[deps]\n\n[compat]\n`,
202
+ });
203
+ expectEcosystem(cwd, "julia", "0.5.2");
204
+ });
205
+
206
+ it("parses Meson meson.build static project() version literal", () => {
207
+ const cwd = makeProject({
208
+ "meson.build":
209
+ "project('x', 'c', version: '0.3.0', license: 'MIT')\n\n executable('x', 'main.c')\n",
210
+ });
211
+ expectEcosystem(cwd, "meson", "0.3.0");
212
+ });
213
+
214
+ it("parses Nim *.nimble top-level version", () => {
215
+ const cwd = makeProject({
216
+ "x.nimble": `# Package\nversion = "0.2.1"\nauthor = "x"\ndescription = "demo"\nlicense = "MIT"\n\nrequires "nim >= 1.6"\n`,
217
+ });
218
+ expectEcosystem(cwd, "nim", "0.2.1");
219
+ });
220
+
221
+ it("parses Ruby *.gemspec spec.version assignment", () => {
222
+ const cwd = makeProject({
223
+ "x.gemspec": `Gem::Specification.new do |s|\n s.name = 'x'\n s.version = '1.2.0'\n s.summary = 'demo'\nend\n`,
224
+ });
225
+ expectEcosystem(cwd, "ruby", "1.2.0");
226
+ });
227
+
228
+ it("parses Vlang v.mod module version", () => {
229
+ const cwd = makeProject({
230
+ "v.mod": `Module {\n\tname: 'x'\n\tversion: '0.4.0'\n\tdescription: 'demo'\n\tauthor: 'x'\n}\n`,
231
+ });
232
+ expectEcosystem(cwd, "vlang", "0.4.0");
233
+ });
234
+
235
+ it("parses Xmake xmake.lua static set_version literal", () => {
236
+ const cwd = makeProject({
237
+ "xmake.lua": `add_rules("mode.debug", "mode.release")\nset_version("1.6.0")\n\ntarget("demo")\n`,
238
+ });
239
+ expectEcosystem(cwd, "xmake", "1.6.0");
240
+ });
241
+
242
+ it("returns null for manifests without a parseable version (silent fallback)", () => {
243
+ const cases: Array<[string, string]> = [
244
+ ["package.json", JSON.stringify({ name: "demo" })],
245
+ ["deno.json", JSON.stringify({ name: "demo" })],
246
+ ["Cargo.toml", `[package]\nname = "x"\nedition = "2021"\n`],
247
+ ["pyproject.toml", `[project]\nname = "x"\n`],
248
+ ["meson.build", "project('x', 'c')\n"],
249
+ ["xmake.lua", "add_rules('mode.debug')\n"],
250
+ ];
251
+ for (const [file, content] of cases) {
252
+ const cwd = makeProject({ [file]: content });
253
+ expect(readPackageVersion(cwd)).toBeNull();
254
+ }
255
+ });
256
+
257
+ it("prefers package.json over Chart.yaml when both exist (deterministic priority)", () => {
258
+ const cwd = makeProject({
259
+ "package.json": JSON.stringify({ version: "1.0.0" }),
260
+ "Chart.yaml": "apiVersion: v2\nversion: 2.0.0\n",
261
+ });
262
+ const result = readPackageVersion(cwd);
263
+ expect(result).not.toBeNull();
264
+ expect(result?.ecosystem).toBe("nodejs");
265
+ expect(result?.version).toBe("1.0.0");
266
+ });
267
+
268
+ it("strips a single leading 'v' from semver versions", () => {
269
+ const cwd = makeProject({
270
+ "package.json": JSON.stringify({ version: "v3.2.1" }),
271
+ });
272
+ expect(readPackageVersion(cwd)?.version).toBe("3.2.1");
273
+ });
274
+
275
+ it("does not shell out and survives missing manifests silently", () => {
276
+ const cwd = mkdtempSync(join(tmpdir(), "zentui-empty-project-"));
277
+ expect(readPackageVersion(cwd)).toBeNull();
278
+ });
279
+
280
+ it("exposes PACKAGE_VERSION_ECOSYSTEMS as a stable, sorted-ish list", () => {
281
+ expect(PACKAGE_VERSION_ECOSYSTEMS).toContain("bun");
282
+ expect(PACKAGE_VERSION_ECOSYSTEMS).toContain("nodejs");
283
+ expect(PACKAGE_VERSION_ECOSYSTEMS).toContain("python");
284
+ expect(PACKAGE_VERSION_ECOSYSTEMS).toContain("rust");
285
+ expect(PACKAGE_VERSION_ECOSYSTEMS).toContain("haskell");
286
+ expect(PACKAGE_VERSION_ECOSYSTEMS).toContain("ruby");
287
+ expect(PACKAGE_VERSION_ECOSYSTEMS).toContain("nim");
288
+ });
289
+ });
290
+
291
+ describe("cleanVersion", () => {
292
+ const { cleanVersion } = __test__;
293
+
294
+ it.each([
295
+ ["1.2.3", "1.2.3"],
296
+ ['"1.2.3"', "1.2.3"],
297
+ ["'1.2.3'", "1.2.3"],
298
+ [" 1.2.3 ", "1.2.3"],
299
+ ["v3.2.1", "3.2.1"],
300
+ ["0.0.1-alpha", "0.0.1-alpha"],
301
+ ])("cleans %j -> %j", (input, expected) => {
302
+ expect(cleanVersion(input)).toBe(expected);
303
+ });
304
+
305
+ it.each(["", " ", "not a version", "1.2.3 with spaces", "{1.2.3}"])("rejects %j", (input) => {
306
+ expect(cleanVersion(input)).toBeUndefined();
307
+ });
308
+
309
+ it.each([
310
+ "1.2.3\x07",
311
+ "1.2.3\x1b",
312
+ "1.2.3\x7f",
313
+ "1.2.3\x9d",
314
+ "1.2.3\x9c",
315
+ "1.2.3\x1b[31m",
316
+ "1.2.3\n",
317
+ "1.2.3\t",
318
+ ])("rejects terminal control input %j", (input) => {
319
+ expect(cleanVersion(input)).toBeUndefined();
320
+ });
321
+ });
@@ -0,0 +1,250 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import {
3
+ createProjectRefreshScheduler,
4
+ startProjectRefreshInterval,
5
+ } from "../extensions/zentui/project-refresh";
6
+
7
+ async function flushPromises(): Promise<void> {
8
+ await Promise.resolve();
9
+ await Promise.resolve();
10
+ }
11
+
12
+ describe("startProjectRefreshInterval", () => {
13
+ afterEach(() => {
14
+ vi.useRealTimers();
15
+ });
16
+
17
+ it("runs the refresh callback at the configured interval", () => {
18
+ vi.useFakeTimers();
19
+ const refresh = vi.fn();
20
+
21
+ const stop = startProjectRefreshInterval(30_000, refresh);
22
+
23
+ vi.advanceTimersByTime(29_999);
24
+ expect(refresh).not.toHaveBeenCalled();
25
+
26
+ vi.advanceTimersByTime(1);
27
+ expect(refresh).toHaveBeenCalledTimes(1);
28
+
29
+ vi.advanceTimersByTime(30_000);
30
+ expect(refresh).toHaveBeenCalledTimes(2);
31
+
32
+ stop();
33
+ vi.advanceTimersByTime(30_000);
34
+ expect(refresh).toHaveBeenCalledTimes(2);
35
+ });
36
+
37
+ it("does not start a timer when polling is disabled", () => {
38
+ vi.useFakeTimers();
39
+ const refresh = vi.fn();
40
+
41
+ const stop = startProjectRefreshInterval(0, refresh);
42
+
43
+ vi.advanceTimersByTime(120_000);
44
+ expect(refresh).not.toHaveBeenCalled();
45
+ expect(() => stop()).not.toThrow();
46
+ });
47
+ });
48
+
49
+ describe("createProjectRefreshScheduler", () => {
50
+ afterEach(() => {
51
+ vi.useRealTimers();
52
+ });
53
+
54
+ it("throttles bursty project refresh requests", async () => {
55
+ vi.useFakeTimers();
56
+ const refresh = vi.fn<(...args: [string]) => Promise<void>>(() => Promise.resolve());
57
+ const afterRefresh = vi.fn();
58
+ const scheduler = createProjectRefreshScheduler(refresh, afterRefresh, 5_000);
59
+
60
+ scheduler.schedule("initial");
61
+ await flushPromises();
62
+
63
+ expect(refresh).toHaveBeenCalledTimes(1);
64
+ expect(refresh).toHaveBeenLastCalledWith("initial", expect.anything());
65
+ expect(afterRefresh).toHaveBeenCalledTimes(1);
66
+
67
+ scheduler.schedule("first-pending");
68
+ scheduler.schedule("latest-pending");
69
+ await flushPromises();
70
+
71
+ expect(refresh).toHaveBeenCalledTimes(1);
72
+
73
+ vi.advanceTimersByTime(4_999);
74
+ await flushPromises();
75
+ expect(refresh).toHaveBeenCalledTimes(1);
76
+
77
+ vi.advanceTimersByTime(1);
78
+ await flushPromises();
79
+
80
+ expect(refresh).toHaveBeenCalledTimes(2);
81
+ expect(refresh).toHaveBeenLastCalledWith("latest-pending", expect.anything());
82
+ expect(afterRefresh).toHaveBeenCalledTimes(2);
83
+ });
84
+
85
+ it("coalesces refreshes requested while a refresh is in flight", async () => {
86
+ vi.useFakeTimers();
87
+ let finishRefresh: (() => void) | undefined;
88
+ const refresh = vi.fn(
89
+ () =>
90
+ new Promise<void>((resolve) => {
91
+ finishRefresh = resolve;
92
+ }),
93
+ );
94
+ const afterRefresh = vi.fn();
95
+ const scheduler = createProjectRefreshScheduler(refresh, afterRefresh, 5_000);
96
+
97
+ scheduler.schedule("initial");
98
+ scheduler.schedule("pending");
99
+ await flushPromises();
100
+
101
+ expect(refresh).toHaveBeenCalledTimes(1);
102
+ expect(afterRefresh).not.toHaveBeenCalled();
103
+
104
+ finishRefresh?.();
105
+ await flushPromises();
106
+
107
+ expect(afterRefresh).toHaveBeenCalledTimes(1);
108
+ expect(refresh).toHaveBeenCalledTimes(1);
109
+
110
+ vi.advanceTimersByTime(5_000);
111
+ await flushPromises();
112
+
113
+ expect(refresh).toHaveBeenCalledTimes(2);
114
+ expect(refresh).toHaveBeenLastCalledWith("pending", expect.anything());
115
+ });
116
+
117
+ it("preserves force intent while a refresh is in flight", async () => {
118
+ vi.useFakeTimers();
119
+ let finishInitialRefresh: (() => void) | undefined;
120
+ const refresh = vi.fn((target: string) => {
121
+ if (target !== "initial") return Promise.resolve();
122
+ return new Promise<void>((resolve) => {
123
+ finishInitialRefresh = resolve;
124
+ });
125
+ });
126
+ const afterRefresh = vi.fn();
127
+ const scheduler = createProjectRefreshScheduler(refresh, afterRefresh, 5_000);
128
+
129
+ scheduler.schedule("initial");
130
+ scheduler.schedule("dependency-change", { force: true });
131
+ await flushPromises();
132
+
133
+ expect(refresh).toHaveBeenCalledTimes(1);
134
+
135
+ finishInitialRefresh?.();
136
+ await flushPromises();
137
+ await flushPromises();
138
+
139
+ expect(refresh).toHaveBeenCalledTimes(2);
140
+ expect(refresh).toHaveBeenLastCalledWith("dependency-change", expect.anything());
141
+ expect(afterRefresh).toHaveBeenCalledTimes(2);
142
+
143
+ scheduler.schedule("ordinary-follow-up");
144
+ await flushPromises();
145
+ expect(refresh).toHaveBeenCalledTimes(2);
146
+
147
+ vi.advanceTimersByTime(4_999);
148
+ await flushPromises();
149
+ expect(refresh).toHaveBeenCalledTimes(2);
150
+
151
+ vi.advanceTimersByTime(1);
152
+ await flushPromises();
153
+ expect(refresh).toHaveBeenCalledTimes(3);
154
+ expect(refresh).toHaveBeenLastCalledWith("ordinary-follow-up", expect.anything());
155
+ });
156
+
157
+ it("supports forced refreshes for initial status reads", async () => {
158
+ vi.useFakeTimers();
159
+ const refresh = vi.fn<(...args: [string]) => Promise<void>>(() => Promise.resolve());
160
+ const afterRefresh = vi.fn();
161
+ const scheduler = createProjectRefreshScheduler(refresh, afterRefresh, 5_000);
162
+
163
+ scheduler.schedule("initial");
164
+ await flushPromises();
165
+ scheduler.schedule("forced", { force: true });
166
+ await flushPromises();
167
+
168
+ expect(refresh).toHaveBeenCalledTimes(2);
169
+ expect(refresh).toHaveBeenLastCalledWith("forced", expect.anything());
170
+ });
171
+
172
+ it("recovers from failed refreshes", async () => {
173
+ vi.useFakeTimers();
174
+ const refresh = vi
175
+ .fn<(...args: [string]) => Promise<void>>()
176
+ .mockRejectedValueOnce(new Error("slow git command failed"))
177
+ .mockResolvedValue(undefined);
178
+ const afterRefresh = vi.fn();
179
+ const scheduler = createProjectRefreshScheduler(refresh, afterRefresh, 5_000);
180
+
181
+ scheduler.schedule("initial");
182
+ await flushPromises();
183
+ scheduler.schedule("next", { force: true });
184
+ await flushPromises();
185
+
186
+ expect(refresh).toHaveBeenCalledTimes(2);
187
+ expect(refresh).toHaveBeenLastCalledWith("next", expect.anything());
188
+ expect(afterRefresh).toHaveBeenCalledTimes(2);
189
+ });
190
+
191
+ it("invalidates an active run before starting a dependency replacement", async () => {
192
+ const resolvers = new Map<string, () => void>();
193
+ const applied: string[] = [];
194
+ const refresh = vi.fn(
195
+ (target: string, run: { isCurrent: () => boolean }) =>
196
+ new Promise<void>((resolve) => {
197
+ resolvers.set(target, () => {
198
+ if (run.isCurrent()) applied.push(target);
199
+ resolve();
200
+ });
201
+ }),
202
+ );
203
+ const afterRefresh = vi.fn();
204
+ const scheduler = createProjectRefreshScheduler(refresh, afterRefresh, 0);
205
+
206
+ scheduler.schedule("old");
207
+ scheduler.invalidate();
208
+ scheduler.schedule("replacement", { force: true });
209
+ expect(refresh).toHaveBeenCalledTimes(2);
210
+
211
+ resolvers.get("old")?.();
212
+ await flushPromises();
213
+ expect(applied).toEqual([]);
214
+ expect(afterRefresh).not.toHaveBeenCalled();
215
+
216
+ resolvers.get("replacement")?.();
217
+ await flushPromises();
218
+ expect(applied).toEqual(["replacement"]);
219
+ expect(afterRefresh).toHaveBeenCalledTimes(1);
220
+ });
221
+
222
+ it("makes stopped runs stale when a restarted run finishes first", async () => {
223
+ const resolvers = new Map<string, () => void>();
224
+ const applied: string[] = [];
225
+ const refresh = vi.fn(
226
+ (target: string, run: { isCurrent: () => boolean }) =>
227
+ new Promise<void>((resolve) => {
228
+ resolvers.set(target, () => {
229
+ if (run.isCurrent()) applied.push(target);
230
+ resolve();
231
+ });
232
+ }),
233
+ );
234
+ const afterRefresh = vi.fn();
235
+ const scheduler = createProjectRefreshScheduler(refresh, afterRefresh, 0);
236
+
237
+ scheduler.schedule("old");
238
+ scheduler.stop();
239
+ scheduler.schedule("new");
240
+ expect(refresh).toHaveBeenCalledTimes(2);
241
+
242
+ resolvers.get("new")?.();
243
+ await flushPromises();
244
+ resolvers.get("old")?.();
245
+ await flushPromises();
246
+
247
+ expect(applied).toEqual(["new"]);
248
+ expect(afterRefresh).toHaveBeenCalledTimes(1);
249
+ });
250
+ });