opencode-dux 1.0.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 (302) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +452 -0
  3. package/dist/agents/descriptions.d.ts +6 -0
  4. package/dist/agents/designer.d.ts +2 -0
  5. package/dist/agents/explorer.d.ts +2 -0
  6. package/dist/agents/fixer.d.ts +2 -0
  7. package/dist/agents/index.d.ts +22 -0
  8. package/dist/agents/interpreter.d.ts +2 -0
  9. package/dist/agents/librarian.d.ts +2 -0
  10. package/dist/agents/oracle.d.ts +2 -0
  11. package/dist/agents/orchestrator.d.ts +27 -0
  12. package/dist/agents/overrides.d.ts +18 -0
  13. package/dist/agents/prompt-blocks.d.ts +97 -0
  14. package/dist/agents/steward.d.ts +3 -0
  15. package/dist/cli/config-io.d.ts +24 -0
  16. package/dist/cli/config-manager.d.ts +4 -0
  17. package/dist/cli/index.d.ts +2 -0
  18. package/dist/cli/index.js +1006 -0
  19. package/dist/cli/install.d.ts +2 -0
  20. package/dist/cli/mcps.d.ts +13 -0
  21. package/dist/cli/model-key-normalization.d.ts +1 -0
  22. package/dist/cli/paths.d.ts +35 -0
  23. package/dist/cli/providers.d.ts +137 -0
  24. package/dist/cli/skills.d.ts +22 -0
  25. package/dist/cli/system.d.ts +5 -0
  26. package/dist/cli/types.d.ts +38 -0
  27. package/dist/config/constants.d.ts +12 -0
  28. package/dist/config/index.d.ts +4 -0
  29. package/dist/config/loader.d.ts +40 -0
  30. package/dist/config/runtime-preset.d.ts +12 -0
  31. package/dist/config/schema.d.ts +281 -0
  32. package/dist/config/utils.d.ts +10 -0
  33. package/dist/discovery/local/types.d.ts +79 -0
  34. package/dist/discovery/local.d.ts +73 -0
  35. package/dist/discovery/mcp-servers.d.ts +88 -0
  36. package/dist/discovery/skills.d.ts +94 -0
  37. package/dist/hooks/apply-patch/codec.d.ts +7 -0
  38. package/dist/hooks/apply-patch/errors.d.ts +25 -0
  39. package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
  40. package/dist/hooks/apply-patch/index.d.ts +15 -0
  41. package/dist/hooks/apply-patch/matching.d.ts +26 -0
  42. package/dist/hooks/apply-patch/operations.d.ts +3 -0
  43. package/dist/hooks/apply-patch/patch.d.ts +2 -0
  44. package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
  45. package/dist/hooks/apply-patch/resolution.d.ts +19 -0
  46. package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
  47. package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
  48. package/dist/hooks/apply-patch/types.d.ts +80 -0
  49. package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
  50. package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
  51. package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
  52. package/dist/hooks/auto-update-checker/index.d.ts +18 -0
  53. package/dist/hooks/auto-update-checker/types.d.ts +22 -0
  54. package/dist/hooks/chat-headers.d.ts +16 -0
  55. package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
  56. package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
  57. package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
  58. package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
  59. package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
  60. package/dist/hooks/filter-available-skills/index.d.ts +32 -0
  61. package/dist/hooks/foreground-fallback/index.d.ts +72 -0
  62. package/dist/hooks/image-hook.d.ts +5 -0
  63. package/dist/hooks/index.d.ts +14 -0
  64. package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
  65. package/dist/hooks/json-error-recovery/index.d.ts +1 -0
  66. package/dist/hooks/phase-reminder/index.d.ts +26 -0
  67. package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
  68. package/dist/hooks/task-session-manager/index.d.ts +52 -0
  69. package/dist/hooks/todo-continuation/index.d.ts +53 -0
  70. package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
  71. package/dist/index.d.ts +5 -0
  72. package/dist/index.js +31782 -0
  73. package/dist/mcp/context7.d.ts +6 -0
  74. package/dist/mcp/grep-app.d.ts +6 -0
  75. package/dist/mcp/index.d.ts +13 -0
  76. package/dist/mcp/types.d.ts +12 -0
  77. package/dist/mcp/websearch.d.ts +9 -0
  78. package/dist/skills/registry.d.ts +29 -0
  79. package/dist/subscriptions/accounts-store.d.ts +57 -0
  80. package/dist/subscriptions/index.d.ts +13 -0
  81. package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
  82. package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
  83. package/dist/subscriptions/types.d.ts +115 -0
  84. package/dist/subscriptions/usage-service.d.ts +74 -0
  85. package/dist/tools/ast-grep/cli.d.ts +15 -0
  86. package/dist/tools/ast-grep/constants.d.ts +25 -0
  87. package/dist/tools/ast-grep/downloader.d.ts +5 -0
  88. package/dist/tools/ast-grep/index.d.ts +10 -0
  89. package/dist/tools/ast-grep/tools.d.ts +3 -0
  90. package/dist/tools/ast-grep/types.d.ts +30 -0
  91. package/dist/tools/ast-grep/utils.d.ts +4 -0
  92. package/dist/tools/delegate.d.ts +14 -0
  93. package/dist/tools/index.d.ts +5 -0
  94. package/dist/tools/preset-manager.d.ts +27 -0
  95. package/dist/tools/smartfetch/binary.d.ts +3 -0
  96. package/dist/tools/smartfetch/cache.d.ts +6 -0
  97. package/dist/tools/smartfetch/constants.d.ts +12 -0
  98. package/dist/tools/smartfetch/index.d.ts +3 -0
  99. package/dist/tools/smartfetch/network.d.ts +38 -0
  100. package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
  101. package/dist/tools/smartfetch/tool.d.ts +3 -0
  102. package/dist/tools/smartfetch/types.d.ts +122 -0
  103. package/dist/tools/smartfetch/utils.d.ts +18 -0
  104. package/dist/tui-state.d.ts +168 -0
  105. package/dist/tui.d.ts +37 -0
  106. package/dist/tui.js +1896 -0
  107. package/dist/utils/agent-variant.d.ts +63 -0
  108. package/dist/utils/compat.d.ts +30 -0
  109. package/dist/utils/env.d.ts +1 -0
  110. package/dist/utils/index.d.ts +9 -0
  111. package/dist/utils/internal-initiator.d.ts +6 -0
  112. package/dist/utils/logger.d.ts +8 -0
  113. package/dist/utils/polling.d.ts +21 -0
  114. package/dist/utils/session-manager.d.ts +55 -0
  115. package/dist/utils/session.d.ts +90 -0
  116. package/dist/utils/subagent-depth.d.ts +35 -0
  117. package/dist/utils/system-collapse.d.ts +6 -0
  118. package/dist/utils/task.d.ts +4 -0
  119. package/dist/utils/zip-extractor.d.ts +1 -0
  120. package/index.ts +1 -0
  121. package/opencode-dux.schema.json +634 -0
  122. package/package.json +103 -0
  123. package/src/agents/descriptions.ts +55 -0
  124. package/src/agents/designer.test.ts +86 -0
  125. package/src/agents/designer.ts +154 -0
  126. package/src/agents/display-name.test.ts +186 -0
  127. package/src/agents/explorer.test.ts +79 -0
  128. package/src/agents/explorer.ts +144 -0
  129. package/src/agents/fixer.test.ts +79 -0
  130. package/src/agents/fixer.ts +145 -0
  131. package/src/agents/index.test.ts +472 -0
  132. package/src/agents/index.ts +248 -0
  133. package/src/agents/interpreter.ts +136 -0
  134. package/src/agents/librarian.test.ts +80 -0
  135. package/src/agents/librarian.ts +145 -0
  136. package/src/agents/oracle.test.ts +89 -0
  137. package/src/agents/oracle.ts +184 -0
  138. package/src/agents/orchestrator.test.ts +116 -0
  139. package/src/agents/orchestrator.ts +574 -0
  140. package/src/agents/overrides.ts +95 -0
  141. package/src/agents/prompt-blocks.test.ts +114 -0
  142. package/src/agents/prompt-blocks.ts +640 -0
  143. package/src/agents/steward.ts +146 -0
  144. package/src/cli/config-io.test.ts +536 -0
  145. package/src/cli/config-io.ts +473 -0
  146. package/src/cli/config-manager.test.ts +141 -0
  147. package/src/cli/config-manager.ts +4 -0
  148. package/src/cli/index.ts +88 -0
  149. package/src/cli/install.ts +282 -0
  150. package/src/cli/mcps.test.ts +62 -0
  151. package/src/cli/mcps.ts +39 -0
  152. package/src/cli/model-key-normalization.test.ts +21 -0
  153. package/src/cli/model-key-normalization.ts +60 -0
  154. package/src/cli/paths.test.ts +167 -0
  155. package/src/cli/paths.ts +144 -0
  156. package/src/cli/providers.test.ts +118 -0
  157. package/src/cli/providers.ts +141 -0
  158. package/src/cli/skills.test.ts +111 -0
  159. package/src/cli/skills.ts +103 -0
  160. package/src/cli/system.test.ts +91 -0
  161. package/src/cli/system.ts +180 -0
  162. package/src/cli/types.ts +43 -0
  163. package/src/config/constants.ts +58 -0
  164. package/src/config/index.ts +4 -0
  165. package/src/config/loader.test.ts +1194 -0
  166. package/src/config/loader.ts +269 -0
  167. package/src/config/model-resolution.test.ts +176 -0
  168. package/src/config/runtime-preset.test.ts +61 -0
  169. package/src/config/runtime-preset.ts +37 -0
  170. package/src/config/schema.ts +248 -0
  171. package/src/config/utils.test.ts +41 -0
  172. package/src/config/utils.ts +23 -0
  173. package/src/discovery/local/types.ts +85 -0
  174. package/src/discovery/local.ts +322 -0
  175. package/src/discovery/mcp-servers.ts +804 -0
  176. package/src/discovery/skills.ts +959 -0
  177. package/src/hooks/apply-patch/codec.test.ts +184 -0
  178. package/src/hooks/apply-patch/codec.ts +352 -0
  179. package/src/hooks/apply-patch/errors.ts +117 -0
  180. package/src/hooks/apply-patch/execution-context.ts +432 -0
  181. package/src/hooks/apply-patch/hook.test.ts +768 -0
  182. package/src/hooks/apply-patch/index.ts +126 -0
  183. package/src/hooks/apply-patch/matching.test.ts +215 -0
  184. package/src/hooks/apply-patch/matching.ts +586 -0
  185. package/src/hooks/apply-patch/operations.test.ts +1535 -0
  186. package/src/hooks/apply-patch/operations.ts +3 -0
  187. package/src/hooks/apply-patch/patch.ts +9 -0
  188. package/src/hooks/apply-patch/prepared-changes.ts +400 -0
  189. package/src/hooks/apply-patch/resolution.test.ts +420 -0
  190. package/src/hooks/apply-patch/resolution.ts +437 -0
  191. package/src/hooks/apply-patch/rewrite.ts +496 -0
  192. package/src/hooks/apply-patch/test-helpers.ts +52 -0
  193. package/src/hooks/apply-patch/types.ts +111 -0
  194. package/src/hooks/auto-update-checker/cache.test.ts +179 -0
  195. package/src/hooks/auto-update-checker/cache.ts +188 -0
  196. package/src/hooks/auto-update-checker/checker.test.ts +159 -0
  197. package/src/hooks/auto-update-checker/checker.ts +308 -0
  198. package/src/hooks/auto-update-checker/constants.ts +33 -0
  199. package/src/hooks/auto-update-checker/index.test.ts +282 -0
  200. package/src/hooks/auto-update-checker/index.ts +225 -0
  201. package/src/hooks/auto-update-checker/types.ts +26 -0
  202. package/src/hooks/chat-headers.test.ts +236 -0
  203. package/src/hooks/chat-headers.ts +97 -0
  204. package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
  205. package/src/hooks/context-pressure-reminder/index.ts +137 -0
  206. package/src/hooks/delegate-task-retry/guidance.ts +41 -0
  207. package/src/hooks/delegate-task-retry/hook.ts +23 -0
  208. package/src/hooks/delegate-task-retry/index.test.ts +38 -0
  209. package/src/hooks/delegate-task-retry/index.ts +7 -0
  210. package/src/hooks/delegate-task-retry/patterns.ts +79 -0
  211. package/src/hooks/filter-available-skills/index.test.ts +297 -0
  212. package/src/hooks/filter-available-skills/index.ts +160 -0
  213. package/src/hooks/foreground-fallback/index.test.ts +624 -0
  214. package/src/hooks/foreground-fallback/index.ts +374 -0
  215. package/src/hooks/image-hook.ts +6 -0
  216. package/src/hooks/index.ts +17 -0
  217. package/src/hooks/json-error-recovery/hook.ts +73 -0
  218. package/src/hooks/json-error-recovery/index.test.ts +111 -0
  219. package/src/hooks/json-error-recovery/index.ts +6 -0
  220. package/src/hooks/phase-reminder/index.test.ts +74 -0
  221. package/src/hooks/phase-reminder/index.ts +85 -0
  222. package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
  223. package/src/hooks/post-file-tool-nudge/index.ts +63 -0
  224. package/src/hooks/task-session-manager/index.test.ts +833 -0
  225. package/src/hooks/task-session-manager/index.ts +434 -0
  226. package/src/hooks/todo-continuation/index.test.ts +3026 -0
  227. package/src/hooks/todo-continuation/index.ts +878 -0
  228. package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
  229. package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
  230. package/src/index.ts +1672 -0
  231. package/src/mcp/context7.ts +14 -0
  232. package/src/mcp/grep-app.ts +11 -0
  233. package/src/mcp/index.test.ts +96 -0
  234. package/src/mcp/index.ts +66 -0
  235. package/src/mcp/types.ts +16 -0
  236. package/src/mcp/websearch.ts +47 -0
  237. package/src/skills/codemap/README.md +60 -0
  238. package/src/skills/codemap/SKILL.md +174 -0
  239. package/src/skills/codemap/scripts/codemap.mjs +483 -0
  240. package/src/skills/codemap/scripts/codemap.test.ts +129 -0
  241. package/src/skills/registry.ts +218 -0
  242. package/src/skills/simplify/README.md +19 -0
  243. package/src/skills/simplify/SKILL.md +138 -0
  244. package/src/subscriptions/accounts-store.test.ts +236 -0
  245. package/src/subscriptions/accounts-store.ts +184 -0
  246. package/src/subscriptions/index.ts +30 -0
  247. package/src/subscriptions/neuralwatt-scraper.ts +108 -0
  248. package/src/subscriptions/opencode-go-scraper.ts +301 -0
  249. package/src/subscriptions/types.ts +145 -0
  250. package/src/subscriptions/usage-service.test.ts +202 -0
  251. package/src/subscriptions/usage-service.ts +651 -0
  252. package/src/tools/ast-grep/cli.ts +257 -0
  253. package/src/tools/ast-grep/constants.ts +214 -0
  254. package/src/tools/ast-grep/downloader.ts +131 -0
  255. package/src/tools/ast-grep/index.ts +24 -0
  256. package/src/tools/ast-grep/tools.ts +117 -0
  257. package/src/tools/ast-grep/types.ts +51 -0
  258. package/src/tools/ast-grep/utils.ts +126 -0
  259. package/src/tools/delegate-handoff.test.ts +18 -0
  260. package/src/tools/delegate.ts +508 -0
  261. package/src/tools/index.ts +8 -0
  262. package/src/tools/preset-manager.test.ts +795 -0
  263. package/src/tools/preset-manager.ts +332 -0
  264. package/src/tools/smartfetch/binary.ts +58 -0
  265. package/src/tools/smartfetch/cache.test.ts +34 -0
  266. package/src/tools/smartfetch/cache.ts +112 -0
  267. package/src/tools/smartfetch/constants.ts +29 -0
  268. package/src/tools/smartfetch/index.ts +8 -0
  269. package/src/tools/smartfetch/network.test.ts +178 -0
  270. package/src/tools/smartfetch/network.ts +614 -0
  271. package/src/tools/smartfetch/secondary-model.test.ts +85 -0
  272. package/src/tools/smartfetch/secondary-model.ts +276 -0
  273. package/src/tools/smartfetch/tool.test.ts +60 -0
  274. package/src/tools/smartfetch/tool.ts +832 -0
  275. package/src/tools/smartfetch/types.ts +135 -0
  276. package/src/tools/smartfetch/utils.test.ts +24 -0
  277. package/src/tools/smartfetch/utils.ts +456 -0
  278. package/src/tui-state.test.ts +867 -0
  279. package/src/tui-state.ts +1255 -0
  280. package/src/tui.test.ts +336 -0
  281. package/src/tui.ts +1539 -0
  282. package/src/utils/agent-variant.test.ts +244 -0
  283. package/src/utils/agent-variant.ts +187 -0
  284. package/src/utils/compat.ts +91 -0
  285. package/src/utils/env.ts +12 -0
  286. package/src/utils/index.ts +9 -0
  287. package/src/utils/internal-initiator.ts +28 -0
  288. package/src/utils/logger.test.ts +220 -0
  289. package/src/utils/logger.ts +136 -0
  290. package/src/utils/polling.test.ts +191 -0
  291. package/src/utils/polling.ts +67 -0
  292. package/src/utils/session-manager.test.ts +173 -0
  293. package/src/utils/session-manager.ts +356 -0
  294. package/src/utils/session.test.ts +110 -0
  295. package/src/utils/session.ts +389 -0
  296. package/src/utils/subagent-depth.test.ts +170 -0
  297. package/src/utils/subagent-depth.ts +75 -0
  298. package/src/utils/system-collapse.test.ts +86 -0
  299. package/src/utils/system-collapse.ts +24 -0
  300. package/src/utils/task.test.ts +24 -0
  301. package/src/utils/task.ts +20 -0
  302. package/src/utils/zip-extractor.ts +102 -0
@@ -0,0 +1,86 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { collapseSystemInPlace } from './system-collapse';
3
+
4
+ /**
5
+ * Regression tests for the system message collapse logic.
6
+ *
7
+ * PR #336's collapse was a silent no-op because it reassigned
8
+ * output.system to a new array, but OpenCode core reads from the
9
+ * original array reference. The fix mutates in-place.
10
+ *
11
+ * These tests import the actual shared function used by the hook,
12
+ * so any regression in the implementation is caught immediately.
13
+ */
14
+
15
+ describe('collapseSystemInPlace', () => {
16
+ test('mutates multi-element array in-place', () => {
17
+ const system = ['part one', 'part two'];
18
+ const output = { system };
19
+
20
+ collapseSystemInPlace(output.system);
21
+
22
+ // Same reference - callers holding the original array see the change
23
+ expect(output.system).toBe(system);
24
+ expect(system).toHaveLength(1);
25
+ expect(system[0]).toBe('part one\n\npart two');
26
+ });
27
+
28
+ test('mutates three-element array in-place', () => {
29
+ const system = ['header', 'todo reminder', 'file nudge'];
30
+ const output = { system };
31
+
32
+ collapseSystemInPlace(output.system);
33
+
34
+ expect(output.system).toBe(system);
35
+ expect(system).toHaveLength(1);
36
+ expect(system[0]).toBe('header\n\ntodo reminder\n\nfile nudge');
37
+ });
38
+
39
+ test('handles single-element array', () => {
40
+ const system = ['only element'];
41
+ const output = { system };
42
+
43
+ collapseSystemInPlace(output.system);
44
+
45
+ expect(output.system).toBe(system);
46
+ expect(system).toHaveLength(1);
47
+ expect(system[0]).toBe('only element');
48
+ });
49
+
50
+ test('handles empty array', () => {
51
+ const system: string[] = [];
52
+ const output = { system };
53
+
54
+ collapseSystemInPlace(output.system);
55
+
56
+ expect(output.system).toBe(system);
57
+ expect(system).toHaveLength(0);
58
+ });
59
+
60
+ test('preserves previous empty-string cleanup behavior', () => {
61
+ const system = [''];
62
+ const output = { system };
63
+
64
+ collapseSystemInPlace(output.system);
65
+
66
+ expect(output.system).toBe(system);
67
+ expect(system).toHaveLength(0);
68
+ });
69
+
70
+ test('reassignment would NOT be visible (regression guard)', () => {
71
+ // This test documents WHY we mutate in-place and not via reassignment.
72
+ // Simulating the broken PR #336 approach to prove it fails.
73
+ const system = ['a', 'b'];
74
+ const output = { system };
75
+
76
+ // Broken approach: reassign the property
77
+ output.system = [output.system.join('\n\n')];
78
+
79
+ // The output object sees the new array...
80
+ expect(output.system).toEqual(['a\n\nb']);
81
+
82
+ // ...but the original reference is untouched - this is the bug.
83
+ expect(system).toEqual(['a', 'b']);
84
+ expect(system).not.toBe(output.system);
85
+ });
86
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Collapse a system message array into a single element by joining all
3
+ * entries with double-newline separators. Mutates the array in-place so
4
+ * that callers holding a reference to the original array see the change.
5
+ */
6
+ export function collapseSystemInPlace(system: string[]): void {
7
+ if (system.length === 0) {
8
+ return;
9
+ }
10
+
11
+ if (system.length === 1) {
12
+ if (system[0]) {
13
+ return;
14
+ }
15
+ system.length = 0;
16
+ return;
17
+ }
18
+
19
+ const joined = system.join('\n\n');
20
+ system.length = 0;
21
+ if (joined) {
22
+ system.push(joined);
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { parseTaskIdFromTaskOutput } from './task';
3
+
4
+ describe('parseTaskIdFromTaskOutput', () => {
5
+ test('parses task_id line from successful task tool output', () => {
6
+ const output = [
7
+ 'task_id: session-abc-123 (for resuming to continue this task if needed)',
8
+ '',
9
+ '<task_result>',
10
+ 'done',
11
+ '</task_result>',
12
+ ].join('\n');
13
+
14
+ expect(parseTaskIdFromTaskOutput(output)).toBe('session-abc-123');
15
+ });
16
+
17
+ test('returns undefined when task_id is absent', () => {
18
+ const output = ['<task_result>', 'no task id here', '</task_result>'].join(
19
+ '\n',
20
+ );
21
+
22
+ expect(parseTaskIdFromTaskOutput(output)).toBeUndefined();
23
+ });
24
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Parse Task tool output to recover a session/task ID for resumption.
3
+ */
4
+
5
+ export function parseTaskIdFromTaskOutput(output: string): string | undefined {
6
+ const lines = output.split(/\r?\n/);
7
+
8
+ for (const line of lines) {
9
+ const trimmed = line.trim();
10
+ const match = /^task_id:\s*([^\s()]+)(?:\s*\(.*)?$/.exec(trimmed);
11
+
12
+ if (!match) {
13
+ continue;
14
+ }
15
+
16
+ return match[1];
17
+ }
18
+
19
+ return undefined;
20
+ }
@@ -0,0 +1,102 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { release } from 'node:os';
3
+ import { crossSpawn } from './compat';
4
+
5
+ const WINDOWS_BUILD_WITH_TAR = 17134;
6
+
7
+ function getWindowsBuildNumber(): number | null {
8
+ if (process.platform !== 'win32') return null;
9
+
10
+ const parts = release().split('.');
11
+ if (parts.length >= 3) {
12
+ const build = parseInt(parts[2], 10);
13
+ if (!Number.isNaN(build)) return build;
14
+ }
15
+ return null;
16
+ }
17
+
18
+ function isPwshAvailable(): boolean {
19
+ if (process.platform !== 'win32') return false;
20
+ const result = spawnSync('where', ['pwsh'], {
21
+ stdio: ['ignore', 'pipe', 'pipe'],
22
+ });
23
+ return result.status === 0;
24
+ }
25
+
26
+ function escapePowerShellPath(path: string): string {
27
+ return path.replace(/'/g, "''");
28
+ }
29
+
30
+ type WindowsZipExtractor = 'tar' | 'pwsh' | 'powershell';
31
+
32
+ function getWindowsZipExtractor(): WindowsZipExtractor {
33
+ const buildNumber = getWindowsBuildNumber();
34
+
35
+ if (buildNumber !== null && buildNumber >= WINDOWS_BUILD_WITH_TAR) {
36
+ return 'tar';
37
+ }
38
+
39
+ if (isPwshAvailable()) {
40
+ return 'pwsh';
41
+ }
42
+
43
+ return 'powershell';
44
+ }
45
+
46
+ export async function extractZip(
47
+ archivePath: string,
48
+ destDir: string,
49
+ ): Promise<void> {
50
+ let proc: ReturnType<typeof crossSpawn>;
51
+
52
+ if (process.platform === 'win32') {
53
+ const extractor = getWindowsZipExtractor();
54
+
55
+ switch (extractor) {
56
+ case 'tar':
57
+ proc = crossSpawn(['tar', '-xf', archivePath, '-C', destDir], {
58
+ stdout: 'ignore',
59
+ stderr: 'pipe',
60
+ });
61
+ break;
62
+ case 'pwsh':
63
+ proc = crossSpawn(
64
+ [
65
+ 'pwsh',
66
+ '-Command',
67
+ `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`,
68
+ ],
69
+ {
70
+ stdout: 'ignore',
71
+ stderr: 'pipe',
72
+ },
73
+ );
74
+ break;
75
+ default:
76
+ proc = crossSpawn(
77
+ [
78
+ 'powershell',
79
+ '-Command',
80
+ `Expand-Archive -Path '${escapePowerShellPath(archivePath)}' -DestinationPath '${escapePowerShellPath(destDir)}' -Force`,
81
+ ],
82
+ {
83
+ stdout: 'ignore',
84
+ stderr: 'pipe',
85
+ },
86
+ );
87
+ break;
88
+ }
89
+ } else {
90
+ proc = crossSpawn(['unzip', '-o', archivePath, '-d', destDir], {
91
+ stdout: 'ignore',
92
+ stderr: 'pipe',
93
+ });
94
+ }
95
+
96
+ const exitCode = await proc.exited;
97
+
98
+ if (exitCode !== 0) {
99
+ const stderr = await proc.stderr();
100
+ throw new Error(`zip extraction failed (exit ${exitCode}): ${stderr}`);
101
+ }
102
+ }