@detergent-software/atk 3.0.0 → 5.0.0-dev.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 (332) hide show
  1. package/README.md +361 -0
  2. package/build/commands/_app.d.ts.map +1 -1
  3. package/build/commands/_app.js +23 -3
  4. package/build/commands/_app.js.map +1 -1
  5. package/build/commands/audit.d.ts.map +1 -1
  6. package/build/commands/audit.js +76 -37
  7. package/build/commands/audit.js.map +1 -1
  8. package/build/commands/browse.d.ts.map +1 -1
  9. package/build/commands/browse.js +126 -154
  10. package/build/commands/browse.js.map +1 -1
  11. package/build/commands/cache.d.ts.map +1 -1
  12. package/build/commands/cache.js +13 -15
  13. package/build/commands/cache.js.map +1 -1
  14. package/build/commands/config.d.ts +1 -1
  15. package/build/commands/config.d.ts.map +1 -1
  16. package/build/commands/config.js +7 -3
  17. package/build/commands/config.js.map +1 -1
  18. package/build/commands/diff.d.ts.map +1 -1
  19. package/build/commands/diff.js +4 -117
  20. package/build/commands/diff.js.map +1 -1
  21. package/build/commands/freeze.d.ts +1 -2
  22. package/build/commands/freeze.d.ts.map +1 -1
  23. package/build/commands/freeze.js +17 -37
  24. package/build/commands/freeze.js.map +1 -1
  25. package/build/commands/info.d.ts +1 -2
  26. package/build/commands/info.d.ts.map +1 -1
  27. package/build/commands/info.js +43 -38
  28. package/build/commands/info.js.map +1 -1
  29. package/build/commands/init.d.ts.map +1 -1
  30. package/build/commands/init.js +41 -414
  31. package/build/commands/init.js.map +1 -1
  32. package/build/commands/install.d.ts.map +1 -1
  33. package/build/commands/install.js +79 -72
  34. package/build/commands/install.js.map +1 -1
  35. package/build/commands/list.d.ts +2 -2
  36. package/build/commands/list.d.ts.map +1 -1
  37. package/build/commands/list.js +138 -27
  38. package/build/commands/list.js.map +1 -1
  39. package/build/commands/outdated.d.ts.map +1 -1
  40. package/build/commands/outdated.js +20 -10
  41. package/build/commands/outdated.js.map +1 -1
  42. package/build/commands/pin.d.ts +1 -2
  43. package/build/commands/pin.d.ts.map +1 -1
  44. package/build/commands/pin.js +44 -47
  45. package/build/commands/pin.js.map +1 -1
  46. package/build/commands/prune.d.ts.map +1 -1
  47. package/build/commands/prune.js +76 -151
  48. package/build/commands/prune.js.map +1 -1
  49. package/build/commands/publish.d.ts +4 -1
  50. package/build/commands/publish.d.ts.map +1 -1
  51. package/build/commands/publish.js +139 -388
  52. package/build/commands/publish.js.map +1 -1
  53. package/build/commands/search.d.ts.map +1 -1
  54. package/build/commands/search.js +14 -19
  55. package/build/commands/search.js.map +1 -1
  56. package/build/commands/setup.d.ts +1 -1
  57. package/build/commands/setup.d.ts.map +1 -1
  58. package/build/commands/setup.js +48 -253
  59. package/build/commands/setup.js.map +1 -1
  60. package/build/commands/sync.d.ts.map +1 -1
  61. package/build/commands/sync.js +43 -169
  62. package/build/commands/sync.js.map +1 -1
  63. package/build/commands/thaw.d.ts +1 -2
  64. package/build/commands/thaw.d.ts.map +1 -1
  65. package/build/commands/thaw.js +19 -37
  66. package/build/commands/thaw.js.map +1 -1
  67. package/build/commands/uninstall.d.ts.map +1 -1
  68. package/build/commands/uninstall.js +106 -221
  69. package/build/commands/uninstall.js.map +1 -1
  70. package/build/commands/unpin.d.ts +1 -2
  71. package/build/commands/unpin.d.ts.map +1 -1
  72. package/build/commands/unpin.js +23 -38
  73. package/build/commands/unpin.js.map +1 -1
  74. package/build/commands/update.d.ts.map +1 -1
  75. package/build/commands/update.js +57 -54
  76. package/build/commands/update.js.map +1 -1
  77. package/build/commands/why.d.ts +1 -1
  78. package/build/commands/why.d.ts.map +1 -1
  79. package/build/commands/why.js +14 -6
  80. package/build/commands/why.js.map +1 -1
  81. package/build/components/AssetDetail.d.ts +1 -3
  82. package/build/components/AssetDetail.d.ts.map +1 -1
  83. package/build/components/AssetDetail.js +22 -21
  84. package/build/components/AssetDetail.js.map +1 -1
  85. package/build/components/AssetTable.d.ts +5 -2
  86. package/build/components/AssetTable.d.ts.map +1 -1
  87. package/build/components/AssetTable.js +37 -40
  88. package/build/components/AssetTable.js.map +1 -1
  89. package/build/components/AssetVersionLine.d.ts +27 -0
  90. package/build/components/AssetVersionLine.d.ts.map +1 -0
  91. package/build/components/AssetVersionLine.js +10 -0
  92. package/build/components/AssetVersionLine.js.map +1 -0
  93. package/build/components/BrowseExpandedBundle.d.ts +10 -1
  94. package/build/components/BrowseExpandedBundle.d.ts.map +1 -1
  95. package/build/components/BrowseExpandedBundle.js +28 -3
  96. package/build/components/BrowseExpandedBundle.js.map +1 -1
  97. package/build/components/BrowseExpandedRow.d.ts +9 -1
  98. package/build/components/BrowseExpandedRow.d.ts.map +1 -1
  99. package/build/components/BrowseExpandedRow.js +35 -3
  100. package/build/components/BrowseExpandedRow.js.map +1 -1
  101. package/build/components/BrowseList.d.ts +5 -1
  102. package/build/components/BrowseList.d.ts.map +1 -1
  103. package/build/components/BrowseList.js +57 -47
  104. package/build/components/BrowseList.js.map +1 -1
  105. package/build/components/BundleDetail.d.ts +2 -1
  106. package/build/components/BundleDetail.d.ts.map +1 -1
  107. package/build/components/BundleDetail.js +3 -2
  108. package/build/components/BundleDetail.js.map +1 -1
  109. package/build/components/DiffView.d.ts +0 -2
  110. package/build/components/DiffView.d.ts.map +1 -1
  111. package/build/components/DiffView.js +11 -5
  112. package/build/components/DiffView.js.map +1 -1
  113. package/build/components/DryRunBanner.d.ts +3 -3
  114. package/build/components/DryRunBanner.d.ts.map +1 -1
  115. package/build/components/DryRunBanner.js +2 -2
  116. package/build/components/DryRunBanner.js.map +1 -1
  117. package/build/components/FilterBar.d.ts +10 -3
  118. package/build/components/FilterBar.d.ts.map +1 -1
  119. package/build/components/FilterBar.js +4 -4
  120. package/build/components/FilterBar.js.map +1 -1
  121. package/build/components/FrozenSkippedHint.d.ts +11 -0
  122. package/build/components/FrozenSkippedHint.d.ts.map +1 -0
  123. package/build/components/FrozenSkippedHint.js +12 -0
  124. package/build/components/FrozenSkippedHint.js.map +1 -0
  125. package/build/components/HelpBar.d.ts.map +1 -1
  126. package/build/components/HelpBar.js +3 -3
  127. package/build/components/HelpBar.js.map +1 -1
  128. package/build/components/InitSuccess.d.ts +5 -0
  129. package/build/components/InitSuccess.d.ts.map +1 -0
  130. package/build/components/InitSuccess.js +20 -0
  131. package/build/components/InitSuccess.js.map +1 -0
  132. package/build/components/InstallSummary.d.ts +3 -3
  133. package/build/components/InstallSummary.d.ts.map +1 -1
  134. package/build/components/InstallSummary.js +2 -2
  135. package/build/components/InstallSummary.js.map +1 -1
  136. package/build/components/Link.d.ts +7 -0
  137. package/build/components/Link.d.ts.map +1 -0
  138. package/build/components/Link.js +6 -0
  139. package/build/components/Link.js.map +1 -0
  140. package/build/components/ListActionBar.d.ts +11 -0
  141. package/build/components/ListActionBar.d.ts.map +1 -0
  142. package/build/components/ListActionBar.js +30 -0
  143. package/build/components/ListActionBar.js.map +1 -0
  144. package/build/components/ListBrowseList.d.ts +27 -0
  145. package/build/components/ListBrowseList.d.ts.map +1 -0
  146. package/build/components/ListBrowseList.js +57 -0
  147. package/build/components/ListBrowseList.js.map +1 -0
  148. package/build/components/ListExpandedRow.d.ts +38 -0
  149. package/build/components/ListExpandedRow.d.ts.map +1 -0
  150. package/build/components/ListExpandedRow.js +49 -0
  151. package/build/components/ListExpandedRow.js.map +1 -0
  152. package/build/components/ListHelpBar.d.ts +19 -0
  153. package/build/components/ListHelpBar.d.ts.map +1 -0
  154. package/build/components/ListHelpBar.js +11 -0
  155. package/build/components/ListHelpBar.js.map +1 -0
  156. package/build/components/OrphanListItem.d.ts +15 -0
  157. package/build/components/OrphanListItem.d.ts.map +1 -0
  158. package/build/components/OrphanListItem.js +6 -0
  159. package/build/components/OrphanListItem.js.map +1 -0
  160. package/build/components/PromptField.d.ts +19 -0
  161. package/build/components/PromptField.d.ts.map +1 -0
  162. package/build/components/PromptField.js +7 -0
  163. package/build/components/PromptField.js.map +1 -0
  164. package/build/components/SkippedAssetSection.d.ts +35 -0
  165. package/build/components/SkippedAssetSection.d.ts.map +1 -0
  166. package/build/components/SkippedAssetSection.js +15 -0
  167. package/build/components/SkippedAssetSection.js.map +1 -0
  168. package/build/components/StatusBadge.d.ts +1 -1
  169. package/build/components/StatusBadge.d.ts.map +1 -1
  170. package/build/components/StatusBadge.js +4 -0
  171. package/build/components/StatusBadge.js.map +1 -1
  172. package/build/components/SyncAllInSync.d.ts +14 -0
  173. package/build/components/SyncAllInSync.d.ts.map +1 -0
  174. package/build/components/SyncAllInSync.js +10 -0
  175. package/build/components/SyncAllInSync.js.map +1 -0
  176. package/build/components/SyncDriftedList.d.ts +24 -0
  177. package/build/components/SyncDriftedList.d.ts.map +1 -0
  178. package/build/components/SyncDriftedList.js +17 -0
  179. package/build/components/SyncDriftedList.js.map +1 -0
  180. package/build/components/SyncNoAssets.d.ts +12 -0
  181. package/build/components/SyncNoAssets.d.ts.map +1 -0
  182. package/build/components/SyncNoAssets.js +9 -0
  183. package/build/components/SyncNoAssets.js.map +1 -0
  184. package/build/components/index.d.ts +16 -2
  185. package/build/components/index.d.ts.map +1 -1
  186. package/build/components/index.js +16 -2
  187. package/build/components/index.js.map +1 -1
  188. package/build/hooks/useBrowseState.d.ts +44 -1
  189. package/build/hooks/useBrowseState.d.ts.map +1 -1
  190. package/build/hooks/useBrowseState.js +148 -13
  191. package/build/hooks/useBrowseState.js.map +1 -1
  192. package/build/hooks/useConfirmation.d.ts +18 -0
  193. package/build/hooks/useConfirmation.d.ts.map +1 -0
  194. package/build/hooks/useConfirmation.js +56 -0
  195. package/build/hooks/useConfirmation.js.map +1 -0
  196. package/build/hooks/useInitState.d.ts +109 -0
  197. package/build/hooks/useInitState.d.ts.map +1 -0
  198. package/build/hooks/useInitState.js +474 -0
  199. package/build/hooks/useInitState.js.map +1 -0
  200. package/build/hooks/useListActions.d.ts +15 -0
  201. package/build/hooks/useListActions.d.ts.map +1 -0
  202. package/build/hooks/useListActions.js +155 -0
  203. package/build/hooks/useListActions.js.map +1 -0
  204. package/build/hooks/useListState.d.ts +151 -0
  205. package/build/hooks/useListState.d.ts.map +1 -0
  206. package/build/hooks/useListState.js +448 -0
  207. package/build/hooks/useListState.js.map +1 -0
  208. package/build/hooks/usePruneState.d.ts +76 -0
  209. package/build/hooks/usePruneState.d.ts.map +1 -0
  210. package/build/hooks/usePruneState.js +252 -0
  211. package/build/hooks/usePruneState.js.map +1 -0
  212. package/build/hooks/usePublishState.d.ts +147 -0
  213. package/build/hooks/usePublishState.d.ts.map +1 -0
  214. package/build/hooks/usePublishState.js +916 -0
  215. package/build/hooks/usePublishState.js.map +1 -0
  216. package/build/hooks/useSetupState.d.ts +57 -0
  217. package/build/hooks/useSetupState.d.ts.map +1 -0
  218. package/build/hooks/useSetupState.js +236 -0
  219. package/build/hooks/useSetupState.js.map +1 -0
  220. package/build/hooks/useUninstallState.d.ts +102 -0
  221. package/build/hooks/useUninstallState.d.ts.map +1 -0
  222. package/build/hooks/useUninstallState.js +342 -0
  223. package/build/hooks/useUninstallState.js.map +1 -0
  224. package/build/lib/adapter.d.ts +21 -0
  225. package/build/lib/adapter.d.ts.map +1 -1
  226. package/build/lib/adapter.js +59 -1
  227. package/build/lib/adapter.js.map +1 -1
  228. package/build/lib/checksum.d.ts +39 -0
  229. package/build/lib/checksum.d.ts.map +1 -1
  230. package/build/lib/checksum.js +98 -13
  231. package/build/lib/checksum.js.map +1 -1
  232. package/build/lib/config.d.ts +38 -18
  233. package/build/lib/config.d.ts.map +1 -1
  234. package/build/lib/config.js +103 -55
  235. package/build/lib/config.js.map +1 -1
  236. package/build/lib/detector.d.ts +2 -11
  237. package/build/lib/detector.d.ts.map +1 -1
  238. package/build/lib/detector.js +2 -64
  239. package/build/lib/detector.js.map +1 -1
  240. package/build/lib/diagnostics.d.ts +7 -0
  241. package/build/lib/diagnostics.d.ts.map +1 -1
  242. package/build/lib/diagnostics.js +54 -34
  243. package/build/lib/diagnostics.js.map +1 -1
  244. package/build/lib/diff-command.d.ts +40 -0
  245. package/build/lib/diff-command.d.ts.map +1 -0
  246. package/build/lib/diff-command.js +145 -0
  247. package/build/lib/diff-command.js.map +1 -0
  248. package/build/lib/diff.d.ts +7 -0
  249. package/build/lib/diff.d.ts.map +1 -1
  250. package/build/lib/diff.js +15 -10
  251. package/build/lib/diff.js.map +1 -1
  252. package/build/lib/format.d.ts +6 -0
  253. package/build/lib/format.d.ts.map +1 -0
  254. package/build/lib/format.js +18 -0
  255. package/build/lib/format.js.map +1 -0
  256. package/build/lib/github.d.ts +35 -0
  257. package/build/lib/github.d.ts.map +1 -1
  258. package/build/lib/github.js +44 -0
  259. package/build/lib/github.js.map +1 -1
  260. package/build/lib/gitignore.d.ts +17 -0
  261. package/build/lib/gitignore.d.ts.map +1 -1
  262. package/build/lib/gitignore.js +17 -1
  263. package/build/lib/gitignore.js.map +1 -1
  264. package/build/lib/init.d.ts +22 -0
  265. package/build/lib/init.d.ts.map +1 -1
  266. package/build/lib/init.js +163 -18
  267. package/build/lib/init.js.map +1 -1
  268. package/build/lib/installer.d.ts +1 -0
  269. package/build/lib/installer.d.ts.map +1 -1
  270. package/build/lib/installer.js +7 -7
  271. package/build/lib/installer.js.map +1 -1
  272. package/build/lib/lockfile.d.ts +19 -17
  273. package/build/lib/lockfile.d.ts.map +1 -1
  274. package/build/lib/lockfile.js +45 -68
  275. package/build/lib/lockfile.js.map +1 -1
  276. package/build/lib/publisher.d.ts +41 -12
  277. package/build/lib/publisher.d.ts.map +1 -1
  278. package/build/lib/publisher.js +226 -64
  279. package/build/lib/publisher.js.map +1 -1
  280. package/build/lib/registry.d.ts +20 -5
  281. package/build/lib/registry.d.ts.map +1 -1
  282. package/build/lib/registry.js +45 -33
  283. package/build/lib/registry.js.map +1 -1
  284. package/build/lib/sanitize.d.ts +25 -0
  285. package/build/lib/sanitize.d.ts.map +1 -0
  286. package/build/lib/sanitize.js +61 -0
  287. package/build/lib/sanitize.js.map +1 -0
  288. package/build/lib/schemas/config.d.ts +31 -3
  289. package/build/lib/schemas/config.d.ts.map +1 -1
  290. package/build/lib/schemas/config.js +21 -2
  291. package/build/lib/schemas/config.js.map +1 -1
  292. package/build/lib/schemas/index.d.ts +1 -1
  293. package/build/lib/schemas/index.d.ts.map +1 -1
  294. package/build/lib/schemas/index.js +1 -1
  295. package/build/lib/schemas/index.js.map +1 -1
  296. package/build/lib/schemas/lockfile.d.ts +20 -4
  297. package/build/lib/schemas/lockfile.d.ts.map +1 -1
  298. package/build/lib/schemas/lockfile.js +7 -1
  299. package/build/lib/schemas/lockfile.js.map +1 -1
  300. package/build/lib/schemas/manifest.d.ts +1 -1
  301. package/build/lib/schemas/manifest.d.ts.map +1 -1
  302. package/build/lib/schemas/manifest.js +7 -2
  303. package/build/lib/schemas/manifest.js.map +1 -1
  304. package/build/lib/schemas/registry.d.ts +2 -4
  305. package/build/lib/schemas/registry.d.ts.map +1 -1
  306. package/build/lib/schemas/registry.js +1 -1
  307. package/build/lib/schemas/registry.js.map +1 -1
  308. package/build/lib/search.d.ts +15 -1
  309. package/build/lib/search.d.ts.map +1 -1
  310. package/build/lib/search.js +65 -65
  311. package/build/lib/search.js.map +1 -1
  312. package/build/lib/setup.d.ts +28 -0
  313. package/build/lib/setup.d.ts.map +1 -0
  314. package/build/lib/setup.js +98 -0
  315. package/build/lib/setup.js.map +1 -0
  316. package/build/lib/sync.d.ts +109 -0
  317. package/build/lib/sync.d.ts.map +1 -0
  318. package/build/lib/sync.js +217 -0
  319. package/build/lib/sync.js.map +1 -0
  320. package/build/lib/tool-resolver.d.ts +29 -6
  321. package/build/lib/tool-resolver.d.ts.map +1 -1
  322. package/build/lib/tool-resolver.js +54 -15
  323. package/build/lib/tool-resolver.js.map +1 -1
  324. package/build/lib/updater.d.ts +7 -4
  325. package/build/lib/updater.d.ts.map +1 -1
  326. package/build/lib/updater.js +10 -6
  327. package/build/lib/updater.js.map +1 -1
  328. package/build/lib/version.js +1 -1
  329. package/build/lib/version.js.map +1 -1
  330. package/package.json +79 -65
  331. package/tool-adapters/claude-code.json +1 -1
  332. package/tool-adapters/copilot-cli.json +1 -1
@@ -0,0 +1,916 @@
1
+ import { cp, mkdir, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { useEffect, useReducer, useRef } from 'react';
5
+ import { loadAllAdapters, resolveInstalledPaths } from '../lib/adapter.js';
6
+ import { getGitHubToken } from '../lib/auth.js';
7
+ import { hashFile } from '../lib/checksum.js';
8
+ import { fetchAssetFiles } from '../lib/github.js';
9
+ import { detectCompatibleTools, getGitUserName } from '../lib/init.js';
10
+ import { addAssetToLockfile, findInstalledAsset, readLockfile, withLockfileLock, writeLockfile, } from '../lib/lockfile.js';
11
+ import { parseOrgFromName } from '../lib/org.js';
12
+ import { findProjectRoot } from '../lib/paths.js';
13
+ import { buildPublishPlan, buildStagingArea, bumpVersion, checkRegistryVersion, detectAssetFromPath, detectPublishType, executePublish, mapPublishError, updateManifestField, updateManifestVersion, validatePublishTarget, } from '../lib/publisher.js';
14
+ import { clearCache, fetchRegistry, findAsset, findBundle } from '../lib/registry.js';
15
+ import { resolveTools } from '../lib/tool-resolver.js';
16
+ // ---------------------------------------------------------------------------
17
+ // Initial state factory
18
+ // ---------------------------------------------------------------------------
19
+ export function createInitialState(flags) {
20
+ return {
21
+ authorValue: '',
22
+ bumpSelection: '',
23
+ confirmValue: '',
24
+ descriptionValue: '',
25
+ errorMessage: '',
26
+ flags,
27
+ isRawPublish: false,
28
+ isUpdate: false,
29
+ metadataSubPhase: 'description',
30
+ orgValue: '',
31
+ phase: flags.update ? 'resolving-update' : flags.fromInstalled ? 'resolving-installed' : 'detecting',
32
+ plan: undefined,
33
+ progressMessage: '',
34
+ promptTagsValue: '',
35
+ publishConfirmValue: '',
36
+ publishResult: undefined,
37
+ rawDetection: null,
38
+ resolvedPath: flags.update ? '' : flags.fromInstalled ? '' : resolve(flags.rawPath),
39
+ resolvedVersion: '',
40
+ selectedPromptTools: [],
41
+ tagsValue: '',
42
+ target: undefined,
43
+ token: undefined,
44
+ toolAdapters: [],
45
+ toolCursorIndex: 0,
46
+ toolsDetected: [],
47
+ validation: undefined,
48
+ versionCheck: undefined,
49
+ };
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // Pure reducer
53
+ // ---------------------------------------------------------------------------
54
+ export function publishReducer(state, action) {
55
+ switch (action.type) {
56
+ case 'BUMP_DONE':
57
+ return {
58
+ ...state,
59
+ errorMessage: action.message ? action.message : state.errorMessage,
60
+ phase: action.message ? 'error' : 'building-plan',
61
+ resolvedVersion: action.version,
62
+ target: action.updatedTarget,
63
+ };
64
+ case 'CONFIRM_PROMPT_TOOLS': {
65
+ if (state.selectedPromptTools.length === 0)
66
+ return state;
67
+ const parsedTools = state.selectedPromptTools.map((tool) => ({ tool }));
68
+ const updatedToolsTarget = state.target
69
+ ? {
70
+ ...state.target,
71
+ data: { ...state.target.data, tools: parsedTools },
72
+ }
73
+ : undefined;
74
+ return {
75
+ ...state,
76
+ phase: 'checking-registry',
77
+ target: updatedToolsTarget,
78
+ };
79
+ }
80
+ case 'DETECT_DONE':
81
+ return {
82
+ ...state,
83
+ phase: 'validating',
84
+ target: action.target,
85
+ };
86
+ case 'ERROR':
87
+ return { ...state, errorMessage: action.message, phase: 'error' };
88
+ case 'INSTALL_DONE':
89
+ return { ...state, phase: 'done' };
90
+ case 'PLAN_DONE': {
91
+ const nextPhase = state.flags.dryRun ? 'dry-run-result' : 'confirm-publish';
92
+ return {
93
+ ...state,
94
+ phase: nextPhase,
95
+ plan: action.plan,
96
+ };
97
+ }
98
+ case 'PUBLISH_DONE': {
99
+ const nextPhase = state.isRawPublish ? 'installing' : 'done';
100
+ return {
101
+ ...state,
102
+ phase: nextPhase,
103
+ publishResult: action.result,
104
+ };
105
+ }
106
+ case 'PUBLISH_PROGRESS':
107
+ return { ...state, progressMessage: action.message };
108
+ case 'RAW_DETECT_DONE':
109
+ return {
110
+ ...state,
111
+ authorValue: action.authorValue,
112
+ isRawPublish: true,
113
+ orgValue: action.orgValue,
114
+ phase: 'gathering-metadata',
115
+ rawDetection: action.rawDetection,
116
+ toolsDetected: action.toolsDetected,
117
+ };
118
+ case 'RAW_DETECT_DONE_NON_INTERACTIVE':
119
+ return {
120
+ ...state,
121
+ isRawPublish: true,
122
+ phase: 'validating',
123
+ rawDetection: action.rawDetection,
124
+ target: action.target,
125
+ };
126
+ case 'REGISTRY_CHECK_DONE': {
127
+ const { isUpdate, resolvedVersion, token, versionCheck } = action;
128
+ let nextPhase;
129
+ if (versionCheck.status === 'new-asset') {
130
+ nextPhase = 'building-plan';
131
+ }
132
+ else if (versionCheck.status === 'version-already-bumped') {
133
+ nextPhase = 'confirm-update';
134
+ }
135
+ else {
136
+ // needs-bump
137
+ nextPhase = 'bump-prompt';
138
+ }
139
+ return {
140
+ ...state,
141
+ isUpdate,
142
+ phase: nextPhase,
143
+ resolvedVersion,
144
+ token,
145
+ versionCheck,
146
+ };
147
+ }
148
+ case 'RESOLVED_INSTALLED':
149
+ return {
150
+ ...state,
151
+ phase: 'detecting',
152
+ resolvedPath: action.resolvedPath,
153
+ };
154
+ case 'RESOLVED_UPDATE':
155
+ return {
156
+ ...state,
157
+ phase: 'detecting',
158
+ resolvedPath: action.resolvedPath,
159
+ };
160
+ case 'SET_BUMP_SELECTION':
161
+ return { ...state, bumpSelection: action.value };
162
+ case 'SET_CONFIRM_VALUE':
163
+ return { ...state, confirmValue: action.value };
164
+ case 'SET_DESCRIPTION':
165
+ return { ...state, descriptionValue: action.value };
166
+ case 'SET_PROMPT_TAGS':
167
+ return { ...state, promptTagsValue: action.value };
168
+ case 'SET_PUBLISH_CONFIRM_VALUE':
169
+ return { ...state, publishConfirmValue: action.value };
170
+ case 'SET_TAGS':
171
+ return { ...state, tagsValue: action.value };
172
+ case 'STAGING_DONE':
173
+ return {
174
+ ...state,
175
+ phase: 'validating',
176
+ target: action.target,
177
+ };
178
+ case 'SUBMIT_BUMP': {
179
+ // Validation happens in the effect; reducer just acknowledges intent
180
+ // The effect will dispatch BUMP_DONE or ERROR
181
+ const trimmed = state.bumpSelection.trim().toLowerCase();
182
+ if (trimmed !== 'patch' && trimmed !== 'minor' && trimmed !== 'major') {
183
+ return state; // Ignore invalid input
184
+ }
185
+ if (!state.target || !state.versionCheck || state.versionCheck.status !== 'needs-bump') {
186
+ return state;
187
+ }
188
+ // Compute the bumped version in the reducer (pure, synchronous)
189
+ const bumpType = trimmed;
190
+ try {
191
+ const newVersion = bumpVersion(state.versionCheck.latestVersion, bumpType);
192
+ return {
193
+ ...state,
194
+ // Move to a transitional sub-state — the effect for bump-prompt
195
+ // will see resolvedVersion is set and perform the async manifest update.
196
+ // We keep phase as 'bump-prompt' so the effect can detect the submission.
197
+ phase: 'building-plan',
198
+ resolvedVersion: newVersion,
199
+ };
200
+ }
201
+ catch {
202
+ return {
203
+ ...state,
204
+ errorMessage: 'Failed to compute bumped version.',
205
+ phase: 'error',
206
+ };
207
+ }
208
+ }
209
+ case 'SUBMIT_CONFIRM': {
210
+ const trimmed = state.confirmValue.trim().toLowerCase();
211
+ if (trimmed === 'y' || trimmed === 'yes') {
212
+ return { ...state, phase: 'building-plan' };
213
+ }
214
+ else if (trimmed === 'n' || trimmed === 'no') {
215
+ return { ...state, errorMessage: 'Publish cancelled.', phase: 'error' };
216
+ }
217
+ // Otherwise ignore (wait for valid input)
218
+ return state;
219
+ }
220
+ case 'SUBMIT_DESCRIPTION': {
221
+ const trimmed = state.descriptionValue.trim();
222
+ if (!trimmed)
223
+ return state; // Don't allow empty description
224
+ return {
225
+ ...state,
226
+ descriptionValue: trimmed,
227
+ metadataSubPhase: 'tags',
228
+ };
229
+ }
230
+ case 'SUBMIT_PROMPT_TAGS': {
231
+ // Parse comma-separated tags from the prompt value
232
+ const parsedTags = state.promptTagsValue
233
+ .split(',')
234
+ .map((t) => t.trim())
235
+ .filter(Boolean);
236
+ // Update the target's data with the new tags
237
+ const updatedTagsTarget = state.target
238
+ ? {
239
+ ...state.target,
240
+ data: { ...state.target.data, tags: parsedTags.length > 0 ? parsedTags : undefined },
241
+ }
242
+ : undefined;
243
+ // Check if tools are also missing (only for assets)
244
+ const needsToolsPrompt = updatedTagsTarget?.type === 'asset' &&
245
+ (!('tools' in updatedTagsTarget.data) ||
246
+ !Array.isArray(updatedTagsTarget.data.tools) ||
247
+ (updatedTagsTarget.data.tools?.length ?? 0) === 0);
248
+ return {
249
+ ...state,
250
+ phase: needsToolsPrompt ? 'loading-tools' : 'checking-registry',
251
+ target: updatedTagsTarget,
252
+ };
253
+ }
254
+ case 'SUBMIT_PUBLISH_CONFIRM': {
255
+ const trimmed = state.publishConfirmValue.trim().toLowerCase();
256
+ if (trimmed === 'y' || trimmed === 'yes') {
257
+ return { ...state, phase: 'publishing' };
258
+ }
259
+ else if (trimmed === 'n' || trimmed === 'no') {
260
+ return { ...state, errorMessage: 'Publish cancelled.', phase: 'error' };
261
+ }
262
+ // Otherwise ignore (wait for valid input)
263
+ return state;
264
+ }
265
+ case 'SUBMIT_TAGS':
266
+ return {
267
+ ...state,
268
+ metadataSubPhase: 'building-staging',
269
+ };
270
+ case 'TOGGLE_PROMPT_TOOL': {
271
+ const toolSet = new Set(state.selectedPromptTools);
272
+ if (toolSet.has(action.tool)) {
273
+ toolSet.delete(action.tool);
274
+ }
275
+ else {
276
+ toolSet.add(action.tool);
277
+ }
278
+ return {
279
+ ...state,
280
+ selectedPromptTools: [...toolSet],
281
+ };
282
+ }
283
+ case 'TOOL_CURSOR_DOWN': {
284
+ if (state.toolAdapters.length === 0)
285
+ return state;
286
+ return {
287
+ ...state,
288
+ toolCursorIndex: state.toolCursorIndex >= state.toolAdapters.length - 1 ? 0 : state.toolCursorIndex + 1,
289
+ };
290
+ }
291
+ case 'TOOL_CURSOR_UP': {
292
+ if (state.toolAdapters.length === 0)
293
+ return state;
294
+ return {
295
+ ...state,
296
+ toolCursorIndex: state.toolCursorIndex <= 0 ? state.toolAdapters.length - 1 : state.toolCursorIndex - 1,
297
+ };
298
+ }
299
+ case 'TOOLS_LOADED':
300
+ return {
301
+ ...state,
302
+ phase: 'prompting-tools',
303
+ toolAdapters: action.adapters,
304
+ toolCursorIndex: 0,
305
+ };
306
+ case 'VALIDATE_DONE': {
307
+ if (!action.validation.valid) {
308
+ return {
309
+ ...state,
310
+ errorMessage: `Validation failed:\n${action.validation.errors.map((e) => ` - ${e}`).join('\n')}`,
311
+ phase: 'error',
312
+ validation: action.validation,
313
+ };
314
+ }
315
+ // Determine next phase based on missing tags/tools
316
+ const manifest = state.target?.data;
317
+ const hasTags = manifest && 'tags' in manifest && Array.isArray(manifest.tags) && manifest.tags.length > 0;
318
+ const hasTools =
319
+ // Only assets have tools; bundles skip the tools prompt
320
+ state.target?.type !== 'asset' ||
321
+ (manifest && 'tools' in manifest && Array.isArray(manifest.tools) && manifest.tools.length > 0);
322
+ let nextPhase;
323
+ if (!hasTags) {
324
+ nextPhase = 'prompting-tags';
325
+ }
326
+ else if (!hasTools) {
327
+ nextPhase = 'loading-tools';
328
+ }
329
+ else {
330
+ nextPhase = 'checking-registry';
331
+ }
332
+ return {
333
+ ...state,
334
+ phase: nextPhase,
335
+ validation: action.validation,
336
+ };
337
+ }
338
+ default:
339
+ return state;
340
+ }
341
+ }
342
+ // ---------------------------------------------------------------------------
343
+ // Hook
344
+ // ---------------------------------------------------------------------------
345
+ export function usePublishState(flags) {
346
+ const [state, dispatch] = useReducer(publishReducer, undefined, () => createInitialState(flags));
347
+ // Temp directory ref for staging / --from-installed cleanup
348
+ const tempDirRef = useRef(null);
349
+ // --- Phase: resolving-installed ---
350
+ useEffect(() => {
351
+ if (state.phase !== 'resolving-installed')
352
+ return;
353
+ let cancelled = false;
354
+ const resolveInstalled = async () => {
355
+ const assetName = state.flags.rawPath;
356
+ const { name, org } = parseOrgFromName(assetName);
357
+ const projectRoot = findProjectRoot(state.flags.projectRoot);
358
+ const tools = await resolveTools(state.flags.tool);
359
+ const adapter = tools[0].adapter;
360
+ const lockfile = await readLockfile(projectRoot);
361
+ const installed = findInstalledAsset(lockfile, name, undefined, org);
362
+ if (!installed) {
363
+ if (!cancelled) {
364
+ dispatch({ message: `Asset '${assetName}' is not installed.`, type: 'ERROR' });
365
+ }
366
+ return;
367
+ }
368
+ // Create temp directory and copy installed files
369
+ const tempDir = await mkdir(join(tmpdir(), 'atk-publish-'), { recursive: true })
370
+ .then(() => join(tmpdir(), `atk-publish-${Date.now()}-${Math.random().toString(36).slice(2)}`))
371
+ .then(async (dir) => {
372
+ await mkdir(dir, { recursive: true });
373
+ return dir;
374
+ });
375
+ if (cancelled) {
376
+ await rm(tempDir, { force: true, recursive: true });
377
+ return;
378
+ }
379
+ tempDirRef.current = tempDir;
380
+ // Resolve installed paths via the adapter
381
+ const resolved = resolveInstalledPaths(installed, adapter, projectRoot);
382
+ // Copy each installed file preserving sourcePath directory structure
383
+ for (const file of resolved.files) {
384
+ const sourceAbsolute = join(projectRoot, file.installedPath);
385
+ const destPath = join(tempDir, file.sourcePath);
386
+ const destDir = dirname(destPath);
387
+ await mkdir(destDir, { recursive: true });
388
+ await cp(sourceAbsolute, destPath);
389
+ }
390
+ // Generate manifest.json in the temp dir
391
+ const manifest = {
392
+ author: 'unknown',
393
+ description: `Published from installed asset ${installed.name}`,
394
+ entrypoint: installed.files[0]?.sourcePath ?? 'index.md',
395
+ name: installed.name,
396
+ ...(installed.org ? { org: installed.org } : {}),
397
+ type: installed.type,
398
+ version: installed.version,
399
+ };
400
+ await writeFile(join(tempDir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
401
+ if (!cancelled) {
402
+ dispatch({ resolvedPath: tempDir, type: 'RESOLVED_INSTALLED' });
403
+ }
404
+ };
405
+ resolveInstalled().catch((err) => {
406
+ if (!cancelled) {
407
+ dispatch({ message: err instanceof Error ? err.message : String(err), type: 'ERROR' });
408
+ }
409
+ });
410
+ return () => {
411
+ cancelled = true;
412
+ };
413
+ // Phase-gated effect: only runs when entering 'resolving-installed'
414
+ // eslint-disable-next-line react-hooks/exhaustive-deps
415
+ }, [state.phase]);
416
+ // --- Phase: resolving-update ---
417
+ useEffect(() => {
418
+ if (state.phase !== 'resolving-update')
419
+ return;
420
+ let cancelled = false;
421
+ const resolveUpdate = async () => {
422
+ const assetName = state.flags.rawPath;
423
+ const { name, org } = parseOrgFromName(assetName);
424
+ const projectRoot = findProjectRoot(state.flags.projectRoot);
425
+ const tools = await resolveTools(state.flags.tool);
426
+ const adapter = tools[0].adapter;
427
+ const lockfile = await readLockfile(projectRoot);
428
+ const installed = findInstalledAsset(lockfile, name, undefined, org);
429
+ if (!installed) {
430
+ if (!cancelled) {
431
+ dispatch({ message: `Asset '${assetName}' is not installed.`, type: 'ERROR' });
432
+ }
433
+ return;
434
+ }
435
+ // Get a GitHub token and fetch the registry to find the latest version
436
+ const token = await getGitHubToken();
437
+ if (cancelled)
438
+ return;
439
+ const registry = await fetchRegistry({ force: state.flags.refresh });
440
+ if (cancelled)
441
+ return;
442
+ // Look up the asset in the registry: try findAsset first, then findBundle
443
+ const resolvedAsset = findAsset(registry, installed.name, installed.type, undefined, installed.org ?? org);
444
+ const resolvedBundle = resolvedAsset ? undefined : findBundle(registry, installed.name);
445
+ if (!resolvedAsset && !resolvedBundle) {
446
+ if (!cancelled) {
447
+ dispatch({ message: `Asset '${assetName}' not found in the registry.`, type: 'ERROR' });
448
+ }
449
+ return;
450
+ }
451
+ const latestVersion = resolvedAsset ? resolvedAsset.version : resolvedBundle.version;
452
+ const assetType = resolvedAsset ? resolvedAsset.asset.type : 'bundle';
453
+ // Fetch the authoritative manifest (and optional README) from the registry
454
+ const fetched = await fetchAssetFiles(token, { name: installed.name, org: installed.org ?? org, type: assetType }, latestVersion);
455
+ if (cancelled)
456
+ return;
457
+ // Create temp staging directory
458
+ const tempDir = await mkdir(join(tmpdir(), 'atk-publish-'), { recursive: true })
459
+ .then(() => join(tmpdir(), `atk-publish-${Date.now()}-${Math.random().toString(36).slice(2)}`))
460
+ .then(async (dir) => {
461
+ await mkdir(dir, { recursive: true });
462
+ return dir;
463
+ });
464
+ if (cancelled) {
465
+ await rm(tempDir, { force: true, recursive: true });
466
+ return;
467
+ }
468
+ tempDirRef.current = tempDir;
469
+ // Resolve installed paths and copy the user's locally modified files into staging
470
+ const resolved = resolveInstalledPaths(installed, adapter, projectRoot);
471
+ for (const file of resolved.files) {
472
+ const sourceAbsolute = join(projectRoot, file.installedPath);
473
+ const destPath = join(tempDir, file.sourcePath);
474
+ const destDir = dirname(destPath);
475
+ await mkdir(destDir, { recursive: true });
476
+ await cp(sourceAbsolute, destPath);
477
+ }
478
+ // Write the fetched manifest into the staging directory
479
+ await writeFile(join(tempDir, fetched.manifestFileName), fetched.manifestContent, 'utf-8');
480
+ // Write README if present
481
+ if (fetched.readmeContent) {
482
+ await writeFile(join(tempDir, 'README.md'), fetched.readmeContent, 'utf-8');
483
+ }
484
+ if (!cancelled) {
485
+ dispatch({ resolvedPath: tempDir, type: 'RESOLVED_UPDATE' });
486
+ }
487
+ };
488
+ resolveUpdate().catch((err) => {
489
+ if (!cancelled) {
490
+ dispatch({ message: err instanceof Error ? err.message : String(err), type: 'ERROR' });
491
+ }
492
+ });
493
+ return () => {
494
+ cancelled = true;
495
+ };
496
+ // Phase-gated effect: only runs when entering 'resolving-update'
497
+ // eslint-disable-next-line react-hooks/exhaustive-deps
498
+ }, [state.phase]);
499
+ // --- Phase: detecting ---
500
+ useEffect(() => {
501
+ if (state.phase !== 'detecting')
502
+ return;
503
+ let cancelled = false;
504
+ const detect = async () => {
505
+ // First, try manifest-based detection (existing flow)
506
+ try {
507
+ const detected = await detectPublishType(state.resolvedPath);
508
+ if (cancelled)
509
+ return;
510
+ dispatch({ target: detected, type: 'DETECT_DONE' });
511
+ return;
512
+ }
513
+ catch (manifestError) {
514
+ // Only attempt raw detection if the error is specifically about missing manifest/bundle
515
+ const manifestMsg = manifestError instanceof Error ? manifestError.message : String(manifestError);
516
+ if (!manifestMsg.includes('No manifest.json or bundle.json found')) {
517
+ // This is a different error (e.g., invalid JSON in manifest) — surface it directly
518
+ if (!cancelled) {
519
+ dispatch({ message: manifestMsg, type: 'ERROR' });
520
+ }
521
+ return;
522
+ }
523
+ }
524
+ // Manifest not found — attempt raw asset detection
525
+ const resolvedTools = await resolveTools(state.flags.tool);
526
+ const adapter = resolvedTools[0].adapter;
527
+ if (cancelled)
528
+ return;
529
+ const projectRoot = findProjectRoot(state.flags.projectRoot);
530
+ const detection = await detectAssetFromPath(state.resolvedPath, adapter, projectRoot);
531
+ if (cancelled)
532
+ return;
533
+ // Pre-fill metadata
534
+ const author = (await getGitUserName()) ?? '';
535
+ if (cancelled)
536
+ return;
537
+ const lockfile = await readLockfile(projectRoot);
538
+ if (cancelled)
539
+ return;
540
+ const tools = await detectCompatibleTools(detection.assetType);
541
+ if (cancelled)
542
+ return;
543
+ const orgVal = lockfile.org ?? '';
544
+ // Non-interactive mode: both --description and --tags flags provided
545
+ if (state.flags.descriptionFlag && state.flags.tagsFlag) {
546
+ const tags = state.flags.tagsFlag
547
+ .split(',')
548
+ .map((t) => t.trim())
549
+ .filter(Boolean);
550
+ const stagingDir = await buildStagingArea(detection, {
551
+ author,
552
+ description: state.flags.descriptionFlag,
553
+ ...(lockfile.org ? { org: lockfile.org } : {}),
554
+ tags,
555
+ tools: tools.map((t) => ({ tool: t })),
556
+ });
557
+ if (cancelled)
558
+ return;
559
+ tempDirRef.current = stagingDir;
560
+ const detected = await detectPublishType(stagingDir);
561
+ if (cancelled)
562
+ return;
563
+ dispatch({ rawDetection: detection, target: detected, type: 'RAW_DETECT_DONE_NON_INTERACTIVE' });
564
+ }
565
+ else {
566
+ // Interactive mode — gather metadata from the user
567
+ dispatch({
568
+ authorValue: author,
569
+ orgValue: orgVal,
570
+ rawDetection: detection,
571
+ toolsDetected: tools.map((t) => ({ tool: t })),
572
+ type: 'RAW_DETECT_DONE',
573
+ });
574
+ }
575
+ };
576
+ detect().catch((err) => {
577
+ if (!cancelled) {
578
+ dispatch({ message: err instanceof Error ? err.message : String(err), type: 'ERROR' });
579
+ }
580
+ });
581
+ return () => {
582
+ cancelled = true;
583
+ };
584
+ // Phase-gated effect: only runs when entering 'detecting'
585
+ // eslint-disable-next-line react-hooks/exhaustive-deps
586
+ }, [state.phase]);
587
+ // --- Phase: gathering-metadata / building-staging sub-phase ---
588
+ useEffect(() => {
589
+ if (state.phase !== 'gathering-metadata' || state.metadataSubPhase !== 'building-staging')
590
+ return;
591
+ let cancelled = false;
592
+ const build = async () => {
593
+ // Parse tags from comma-separated string
594
+ const tags = state.tagsValue
595
+ .split(',')
596
+ .map((t) => t.trim())
597
+ .filter(Boolean);
598
+ // Build staging area
599
+ const stagingDir = await buildStagingArea(state.rawDetection, {
600
+ author: state.authorValue,
601
+ description: state.descriptionValue,
602
+ org: state.orgValue || undefined,
603
+ tags,
604
+ tools: state.toolsDetected,
605
+ });
606
+ if (cancelled)
607
+ return;
608
+ // Store for cleanup
609
+ tempDirRef.current = stagingDir;
610
+ // Re-detect using the staging area (now has manifest.json)
611
+ const detectedTarget = await detectPublishType(stagingDir);
612
+ if (cancelled)
613
+ return;
614
+ dispatch({ target: detectedTarget, type: 'STAGING_DONE' });
615
+ };
616
+ build().catch((err) => {
617
+ if (!cancelled) {
618
+ dispatch({ message: err instanceof Error ? err.message : String(err), type: 'ERROR' });
619
+ }
620
+ });
621
+ return () => {
622
+ cancelled = true;
623
+ };
624
+ // Phase-gated effect: only runs when entering building-staging sub-phase
625
+ // eslint-disable-next-line react-hooks/exhaustive-deps
626
+ }, [state.phase, state.metadataSubPhase]);
627
+ // --- Phase: validating ---
628
+ useEffect(() => {
629
+ if (state.phase !== 'validating')
630
+ return;
631
+ if (!state.target)
632
+ return;
633
+ let cancelled = false;
634
+ const validate = async () => {
635
+ const result = await validatePublishTarget(state.target);
636
+ if (cancelled)
637
+ return;
638
+ dispatch({ type: 'VALIDATE_DONE', validation: result });
639
+ };
640
+ validate().catch((err) => {
641
+ if (!cancelled) {
642
+ dispatch({ message: err instanceof Error ? err.message : String(err), type: 'ERROR' });
643
+ }
644
+ });
645
+ return () => {
646
+ cancelled = true;
647
+ };
648
+ // Phase-gated effect: only runs when entering 'validating'
649
+ // eslint-disable-next-line react-hooks/exhaustive-deps
650
+ }, [state.phase]);
651
+ // --- Phase: loading-tools ---
652
+ useEffect(() => {
653
+ if (state.phase !== 'loading-tools')
654
+ return;
655
+ let cancelled = false;
656
+ const load = async () => {
657
+ const adapters = await loadAllAdapters();
658
+ if (cancelled)
659
+ return;
660
+ dispatch({ adapters, type: 'TOOLS_LOADED' });
661
+ };
662
+ load().catch((err) => {
663
+ if (!cancelled) {
664
+ dispatch({ message: err instanceof Error ? err.message : String(err), type: 'ERROR' });
665
+ }
666
+ });
667
+ return () => {
668
+ cancelled = true;
669
+ };
670
+ // Phase-gated effect: only runs when entering 'loading-tools'
671
+ }, [state.phase]);
672
+ // --- Phase: checking-registry ---
673
+ useEffect(() => {
674
+ if (state.phase !== 'checking-registry')
675
+ return;
676
+ if (!state.target)
677
+ return;
678
+ let cancelled = false;
679
+ const check = async () => {
680
+ const authToken = await getGitHubToken();
681
+ if (cancelled)
682
+ return;
683
+ const reg = await fetchRegistry({ force: state.flags.refresh });
684
+ if (cancelled)
685
+ return;
686
+ const versionResult = checkRegistryVersion(state.target, reg);
687
+ if (cancelled)
688
+ return;
689
+ let isUpdate = false;
690
+ let resolvedVersion = '';
691
+ if (versionResult.status === 'new-asset') {
692
+ isUpdate = false;
693
+ resolvedVersion = state.target.data.version;
694
+ }
695
+ else if (versionResult.status === 'version-already-bumped') {
696
+ isUpdate = true;
697
+ resolvedVersion = state.target.data.version;
698
+ }
699
+ else {
700
+ // needs-bump
701
+ isUpdate = true;
702
+ }
703
+ if (cancelled)
704
+ return;
705
+ dispatch({
706
+ isUpdate,
707
+ resolvedVersion,
708
+ token: authToken,
709
+ type: 'REGISTRY_CHECK_DONE',
710
+ versionCheck: versionResult,
711
+ });
712
+ };
713
+ check().catch((err) => {
714
+ if (!cancelled) {
715
+ dispatch({ message: mapPublishError(err), type: 'ERROR' });
716
+ }
717
+ });
718
+ return () => {
719
+ cancelled = true;
720
+ };
721
+ // Phase-gated effect: only runs when entering 'checking-registry'
722
+ // eslint-disable-next-line react-hooks/exhaustive-deps
723
+ }, [state.phase]);
724
+ // --- Phase: building-plan ---
725
+ // This effect also handles the async manifest update when coming from bump-prompt
726
+ useEffect(() => {
727
+ if (state.phase !== 'building-plan')
728
+ return;
729
+ if (!state.target)
730
+ return;
731
+ let cancelled = false;
732
+ const build = async () => {
733
+ let currentTarget = state.target;
734
+ const currentVersion = state.resolvedVersion;
735
+ // If we came from a bump submission, update the manifest file first
736
+ if (state.versionCheck?.status === 'needs-bump' && currentVersion) {
737
+ const manifestFile = currentTarget.type === 'asset' ? 'manifest.json' : 'bundle.json';
738
+ await updateManifestVersion(currentTarget.sourceDir, manifestFile, currentVersion);
739
+ if (cancelled)
740
+ return;
741
+ // Re-read the target so it has the updated version
742
+ currentTarget = await detectPublishType(currentTarget.sourceDir);
743
+ if (cancelled)
744
+ return;
745
+ }
746
+ // Persist user-entered tags/tools to the manifest file on disk (manifest-based assets only)
747
+ if (currentTarget.type === 'asset') {
748
+ const manifestFile = 'manifest.json';
749
+ if (state.promptTagsValue) {
750
+ const parsedTags = state.promptTagsValue
751
+ .split(',')
752
+ .map((t) => t.trim())
753
+ .filter(Boolean);
754
+ if (parsedTags.length > 0) {
755
+ await updateManifestField(currentTarget.sourceDir, manifestFile, 'tags', parsedTags);
756
+ if (cancelled)
757
+ return;
758
+ }
759
+ }
760
+ if (state.selectedPromptTools.length > 0) {
761
+ const parsedTools = state.selectedPromptTools.map((tool) => ({ tool }));
762
+ await updateManifestField(currentTarget.sourceDir, manifestFile, 'tools', parsedTools);
763
+ if (cancelled)
764
+ return;
765
+ }
766
+ }
767
+ const publishPlan = await buildPublishPlan(currentTarget, currentVersion, state.isUpdate, state.flags.message);
768
+ if (cancelled)
769
+ return;
770
+ dispatch({ plan: publishPlan, type: 'PLAN_DONE' });
771
+ };
772
+ build().catch((err) => {
773
+ if (!cancelled) {
774
+ dispatch({ message: err instanceof Error ? err.message : String(err), type: 'ERROR' });
775
+ }
776
+ });
777
+ return () => {
778
+ cancelled = true;
779
+ };
780
+ // Phase-gated effect: only runs when entering 'building-plan'
781
+ // eslint-disable-next-line react-hooks/exhaustive-deps
782
+ }, [state.phase]);
783
+ // --- Phase: publishing ---
784
+ useEffect(() => {
785
+ if (state.phase !== 'publishing')
786
+ return;
787
+ if (!state.plan || !state.token)
788
+ return;
789
+ let cancelled = false;
790
+ const publish = async () => {
791
+ const progressCallback = (msg) => {
792
+ if (!cancelled) {
793
+ dispatch({ message: msg, type: 'PUBLISH_PROGRESS' });
794
+ }
795
+ };
796
+ const result = await executePublish(state.plan, state.token, progressCallback);
797
+ if (cancelled)
798
+ return;
799
+ // Clear registry cache so subsequent commands see the new version
800
+ try {
801
+ await clearCache();
802
+ }
803
+ catch {
804
+ // Best-effort — don't fail publish over cache
805
+ }
806
+ // Update lockfile in-place when publishing from an installed asset
807
+ if (state.flags.fromInstalled || state.flags.update) {
808
+ try {
809
+ const projectRoot = findProjectRoot(state.flags.projectRoot);
810
+ const pubTools = await resolveTools(state.flags.tool);
811
+ const pubAdapter = pubTools[0].adapter;
812
+ await withLockfileLock(projectRoot, async () => {
813
+ const lockfile = await readLockfile(projectRoot);
814
+ const { name, org } = parseOrgFromName(state.flags.rawPath);
815
+ const installed = findInstalledAsset(lockfile, name, undefined, org);
816
+ if (installed) {
817
+ const resolved = resolveInstalledPaths(installed, pubAdapter, projectRoot);
818
+ const updatedFiles = await Promise.all(resolved.files.map(async (file) => ({
819
+ checksum: await hashFile(join(projectRoot, file.installedPath)),
820
+ sourcePath: file.sourcePath,
821
+ })));
822
+ const updatedLockfile = addAssetToLockfile(lockfile, {
823
+ ...installed,
824
+ files: updatedFiles,
825
+ version: state.resolvedVersion,
826
+ });
827
+ await writeLockfile(projectRoot, updatedLockfile);
828
+ }
829
+ });
830
+ }
831
+ catch {
832
+ // Best-effort — don't fail publish over lockfile update
833
+ }
834
+ }
835
+ if (cancelled)
836
+ return;
837
+ dispatch({ result, type: 'PUBLISH_DONE' });
838
+ };
839
+ publish().catch((err) => {
840
+ if (!cancelled) {
841
+ dispatch({ message: mapPublishError(err), type: 'ERROR' });
842
+ }
843
+ });
844
+ return () => {
845
+ cancelled = true;
846
+ };
847
+ // Phase-gated effect: only runs when entering 'publishing'
848
+ // eslint-disable-next-line react-hooks/exhaustive-deps
849
+ }, [state.phase]);
850
+ // --- Phase: installing (auto-install raw publish to lockfile) ---
851
+ useEffect(() => {
852
+ if (state.phase !== 'installing')
853
+ return;
854
+ let cancelled = false;
855
+ const install = async () => {
856
+ try {
857
+ const projectRoot = findProjectRoot(state.flags.projectRoot);
858
+ await withLockfileLock(projectRoot, async () => {
859
+ // Read current lockfile
860
+ const lockfile = await readLockfile(projectRoot);
861
+ // Compute checksums from actual project files (not staging copies)
862
+ const files = [];
863
+ for (const file of state.rawDetection.files) {
864
+ const checksum = await hashFile(file.absolutePath);
865
+ files.push({
866
+ checksum,
867
+ sourcePath: file.relativePath,
868
+ });
869
+ }
870
+ // Build InstalledAsset entry
871
+ const installedAsset = {
872
+ files,
873
+ installedAt: new Date().toISOString(),
874
+ installReason: 'direct',
875
+ name: state.rawDetection.name,
876
+ org: state.orgValue || undefined,
877
+ type: state.rawDetection.assetType,
878
+ version: state.resolvedVersion || state.target?.data.version || '1.0.0',
879
+ };
880
+ // Add to lockfile and write
881
+ const updatedLockfile = addAssetToLockfile(lockfile, installedAsset);
882
+ await writeLockfile(projectRoot, updatedLockfile);
883
+ });
884
+ if (!cancelled) {
885
+ dispatch({ type: 'INSTALL_DONE' });
886
+ }
887
+ }
888
+ catch {
889
+ // Publish already succeeded, so just warn and move to done
890
+ if (!cancelled) {
891
+ dispatch({ type: 'INSTALL_DONE' });
892
+ }
893
+ }
894
+ };
895
+ install();
896
+ return () => {
897
+ cancelled = true;
898
+ };
899
+ // Phase-gated effect: only runs when entering 'installing'
900
+ // eslint-disable-next-line react-hooks/exhaustive-deps
901
+ }, [state.phase]);
902
+ // --- Cleanup temp directory on error or done ---
903
+ useEffect(() => {
904
+ if (state.phase !== 'error' && state.phase !== 'done' && state.phase !== 'dry-run-result')
905
+ return;
906
+ if (tempDirRef.current) {
907
+ const dir = tempDirRef.current;
908
+ tempDirRef.current = null;
909
+ rm(dir, { force: true, recursive: true }).catch(() => {
910
+ // Best-effort cleanup — ignore errors
911
+ });
912
+ }
913
+ }, [state.phase]);
914
+ return [state, dispatch];
915
+ }
916
+ //# sourceMappingURL=usePublishState.js.map