@selesai/code 0.9.14 → 0.10.0

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 (263) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +79 -29
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +3 -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/skills/design-references/SKILL.md +31 -0
  118. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  119. package/dist/themes/adventure.json +84 -0
  120. package/dist/themes/adwaita-dark.json +84 -0
  121. package/dist/themes/arcoiris.json +84 -0
  122. package/dist/themes/arthur.json +84 -0
  123. package/dist/themes/atom.json +84 -0
  124. package/dist/themes/aura.json +84 -0
  125. package/dist/themes/black-metal-bathory.json +84 -0
  126. package/dist/themes/black-metal-burzum.json +84 -0
  127. package/dist/themes/black-metal-khold.json +84 -0
  128. package/dist/themes/box.json +84 -0
  129. package/dist/themes/brogrammer.json +84 -0
  130. package/dist/themes/carbonfox.json +84 -0
  131. package/dist/themes/catppuccin-mocha.json +84 -0
  132. package/dist/themes/citruszest.json +84 -0
  133. package/dist/themes/cursor-dark.json +84 -0
  134. package/dist/themes/cutie-pro.json +84 -0
  135. package/dist/themes/dark-modern.json +84 -0
  136. package/dist/themes/dark-pastel.json +84 -0
  137. package/dist/themes/dimmed-monokai.json +84 -0
  138. package/dist/themes/doom-peacock.json +84 -0
  139. package/dist/themes/dracula-plus.json +84 -0
  140. package/dist/themes/dracula.json +80 -0
  141. package/dist/themes/earthsong.json +84 -0
  142. package/dist/themes/everforest-dark-hard.json +84 -0
  143. package/dist/themes/everforest-dark.json +80 -0
  144. package/dist/themes/fahrenheit.json +84 -0
  145. package/dist/themes/flatland.json +84 -0
  146. package/dist/themes/flexoki-dark.json +84 -0
  147. package/dist/themes/front-end-delight.json +84 -0
  148. package/dist/themes/fun-forrest.json +84 -0
  149. package/dist/themes/galizur.json +84 -0
  150. package/dist/themes/github-dark-colorblind.json +84 -0
  151. package/dist/themes/github-dark-high-contrast.json +84 -0
  152. package/dist/themes/glacier.json +84 -0
  153. package/dist/themes/gruber-darker.json +84 -0
  154. package/dist/themes/gruvbox-dark-hard.json +84 -0
  155. package/dist/themes/gruvbox-dark.json +84 -0
  156. package/dist/themes/gruvbox-material.json +84 -0
  157. package/dist/themes/guezwhoz.json +84 -0
  158. package/dist/themes/hacktober.json +84 -0
  159. package/dist/themes/hardcore.json +84 -0
  160. package/dist/themes/havn-skumring.json +84 -0
  161. package/dist/themes/ic-orange-ppl.json +84 -0
  162. package/dist/themes/iterm2-smoooooth.json +84 -0
  163. package/dist/themes/iterm2-tango-dark.json +84 -0
  164. package/dist/themes/japanesque.json +84 -0
  165. package/dist/themes/jellybeans.json +84 -0
  166. package/dist/themes/kanagawa-wave.json +84 -0
  167. package/dist/themes/kurokula.json +84 -0
  168. package/dist/themes/later-this-evening.json +84 -0
  169. package/dist/themes/lovelace.json +84 -0
  170. package/dist/themes/material-darker.json +84 -0
  171. package/dist/themes/matte-black.json +84 -0
  172. package/dist/themes/mellow.json +84 -0
  173. package/dist/themes/miasma.json +84 -0
  174. package/dist/themes/nord.json +80 -0
  175. package/dist/themes/nvim-dark.json +84 -0
  176. package/dist/themes/one-dark.json +80 -0
  177. package/dist/themes/popping-and-locking.json +84 -0
  178. package/dist/themes/rose-pine.json +80 -0
  179. package/dist/themes/sea-shells.json +84 -0
  180. package/dist/themes/sleepy-hollow.json +84 -0
  181. package/dist/themes/smyck.json +84 -0
  182. package/dist/themes/solarized-dark.json +80 -0
  183. package/dist/themes/tokyo-night.json +80 -0
  184. package/dist/themes/tomorrow-night-bright.json +84 -0
  185. package/dist/themes/tomorrow-night-burns.json +84 -0
  186. package/dist/themes/tomorrow-night.json +84 -0
  187. package/dist/themes/twilight.json +84 -0
  188. package/dist/themes/vague.json +84 -0
  189. package/dist/themes/vesper.json +84 -0
  190. package/dist/themes/xcode-dark-hc.json +84 -0
  191. package/docs/custom-provider.md +0 -1
  192. package/docs/plans/auto-model-routing-extension.md +490 -0
  193. package/package.json +3 -3
  194. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  195. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  196. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  197. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  198. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  199. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  200. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  201. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  202. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  203. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  204. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  205. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  206. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  207. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  208. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  209. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  210. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  211. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  212. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  213. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  214. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  215. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  216. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  217. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  218. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  219. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  220. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  221. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  222. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  223. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  224. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  225. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  226. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  227. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  228. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  229. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  230. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  231. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  232. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  233. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  234. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  235. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  236. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  237. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  238. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  239. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  240. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  241. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  242. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  243. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  244. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  245. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  246. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  247. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  248. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  249. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  250. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  251. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  252. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  253. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  254. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  255. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  256. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  257. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  258. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  259. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  260. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  261. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  262. package/dist/extensions/preview-tools-disabled.ts +0 -11
  263. 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
+ });