@detergent-software/atk 8.9.0 → 8.9.1-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 (291) hide show
  1. package/CHANGELOG.md +312 -0
  2. package/README.md +22 -12
  3. package/build/cli.js +18 -6
  4. package/build/cli.js.map +1 -1
  5. package/build/command-manifest.json +1507 -0
  6. package/build/commands/_app.d.ts.map +1 -1
  7. package/build/commands/_app.js +8 -4
  8. package/build/commands/_app.js.map +1 -1
  9. package/build/commands/audit.d.ts.map +1 -1
  10. package/build/commands/audit.js +1 -1
  11. package/build/commands/audit.js.map +1 -1
  12. package/build/commands/browse.js +2 -2
  13. package/build/commands/browse.js.map +1 -1
  14. package/build/commands/cache.d.ts +1 -1
  15. package/build/commands/cache.d.ts.map +1 -1
  16. package/build/commands/cache.js +1 -16
  17. package/build/commands/cache.js.map +1 -1
  18. package/build/commands/config.d.ts.map +1 -1
  19. package/build/commands/config.js +1 -1
  20. package/build/commands/config.js.map +1 -1
  21. package/build/commands/diff.d.ts.map +1 -1
  22. package/build/commands/diff.js +4 -0
  23. package/build/commands/diff.js.map +1 -1
  24. package/build/commands/drift.d.ts.map +1 -1
  25. package/build/commands/drift.js +4 -0
  26. package/build/commands/drift.js.map +1 -1
  27. package/build/commands/index.d.ts.map +1 -1
  28. package/build/commands/index.js +5 -40
  29. package/build/commands/index.js.map +1 -1
  30. package/build/commands/info.d.ts.map +1 -1
  31. package/build/commands/info.js +50 -33
  32. package/build/commands/info.js.map +1 -1
  33. package/build/commands/init.d.ts.map +1 -1
  34. package/build/commands/init.js +4 -2
  35. package/build/commands/init.js.map +1 -1
  36. package/build/commands/install.d.ts.map +1 -1
  37. package/build/commands/install.js +28 -7
  38. package/build/commands/install.js.map +1 -1
  39. package/build/commands/list.js +2 -2
  40. package/build/commands/list.js.map +1 -1
  41. package/build/commands/login.d.ts.map +1 -1
  42. package/build/commands/login.js +12 -2
  43. package/build/commands/login.js.map +1 -1
  44. package/build/commands/logout.d.ts.map +1 -1
  45. package/build/commands/logout.js +9 -1
  46. package/build/commands/logout.js.map +1 -1
  47. package/build/commands/outdated.js +1 -1
  48. package/build/commands/outdated.js.map +1 -1
  49. package/build/commands/prune.d.ts.map +1 -1
  50. package/build/commands/prune.js +4 -2
  51. package/build/commands/prune.js.map +1 -1
  52. package/build/commands/publish.d.ts +1 -1
  53. package/build/commands/publish.d.ts.map +1 -1
  54. package/build/commands/publish.js +33 -37
  55. package/build/commands/publish.js.map +1 -1
  56. package/build/commands/release-notes.d.ts.map +1 -1
  57. package/build/commands/release-notes.js +4 -0
  58. package/build/commands/release-notes.js.map +1 -1
  59. package/build/commands/schema.d.ts +3 -3
  60. package/build/commands/search.js +1 -1
  61. package/build/commands/search.js.map +1 -1
  62. package/build/commands/sync.d.ts +1 -1
  63. package/build/commands/sync.d.ts.map +1 -1
  64. package/build/commands/sync.js +27 -12
  65. package/build/commands/sync.js.map +1 -1
  66. package/build/commands/update.d.ts.map +1 -1
  67. package/build/commands/update.js +58 -9
  68. package/build/commands/update.js.map +1 -1
  69. package/build/commands/whoami.d.ts.map +1 -1
  70. package/build/commands/whoami.js +16 -4
  71. package/build/commands/whoami.js.map +1 -1
  72. package/build/commands/why.js +3 -4
  73. package/build/commands/why.js.map +1 -1
  74. package/build/components/Header.d.ts +2 -3
  75. package/build/components/Header.d.ts.map +1 -1
  76. package/build/components/Header.js +2 -9
  77. package/build/components/Header.js.map +1 -1
  78. package/build/components/LocalEditsHint.d.ts +16 -0
  79. package/build/components/LocalEditsHint.d.ts.map +1 -0
  80. package/build/components/LocalEditsHint.js +18 -0
  81. package/build/components/LocalEditsHint.js.map +1 -0
  82. package/build/components/SyncAllInSync.d.ts +5 -1
  83. package/build/components/SyncAllInSync.d.ts.map +1 -1
  84. package/build/components/SyncAllInSync.js +2 -2
  85. package/build/components/SyncAllInSync.js.map +1 -1
  86. package/build/components/header-art.d.ts +7 -0
  87. package/build/components/header-art.d.ts.map +1 -0
  88. package/build/components/header-art.js +14 -0
  89. package/build/components/header-art.js.map +1 -0
  90. package/build/components/index.d.ts +1 -0
  91. package/build/components/index.d.ts.map +1 -1
  92. package/build/components/index.js +1 -0
  93. package/build/components/index.js.map +1 -1
  94. package/build/hooks/useListActions.d.ts.map +1 -1
  95. package/build/hooks/useListActions.js +4 -0
  96. package/build/hooks/useListActions.js.map +1 -1
  97. package/build/hooks/usePruneState.d.ts +1 -7
  98. package/build/hooks/usePruneState.d.ts.map +1 -1
  99. package/build/hooks/usePruneState.js +18 -62
  100. package/build/hooks/usePruneState.js.map +1 -1
  101. package/build/hooks/usePublishState.d.ts +38 -207
  102. package/build/hooks/usePublishState.d.ts.map +1 -1
  103. package/build/hooks/usePublishState.js +83 -1501
  104. package/build/hooks/usePublishState.js.map +1 -1
  105. package/build/lib/adapter.d.ts +10 -1
  106. package/build/lib/adapter.d.ts.map +1 -1
  107. package/build/lib/adapter.js +46 -3
  108. package/build/lib/adapter.js.map +1 -1
  109. package/build/lib/api-client.d.ts +9 -5
  110. package/build/lib/api-client.d.ts.map +1 -1
  111. package/build/lib/api-client.js +35 -8
  112. package/build/lib/api-client.js.map +1 -1
  113. package/build/lib/atk-api/errors.d.ts +5 -2
  114. package/build/lib/atk-api/errors.d.ts.map +1 -1
  115. package/build/lib/atk-api/errors.js +1 -0
  116. package/build/lib/atk-api/errors.js.map +1 -1
  117. package/build/lib/atk-api/generated/types.gen.d.ts +12 -12
  118. package/build/lib/atk-api/index.d.ts +2 -2
  119. package/build/lib/atk-api/index.d.ts.map +1 -1
  120. package/build/lib/atk-api/index.js +1 -1
  121. package/build/lib/atk-api/index.js.map +1 -1
  122. package/build/lib/atk-api/web-routes.d.ts +25 -6
  123. package/build/lib/atk-api/web-routes.d.ts.map +1 -1
  124. package/build/lib/atk-api/web-routes.js +27 -8
  125. package/build/lib/atk-api/web-routes.js.map +1 -1
  126. package/build/lib/auth.d.ts +12 -2
  127. package/build/lib/auth.d.ts.map +1 -1
  128. package/build/lib/auth.js +38 -3
  129. package/build/lib/auth.js.map +1 -1
  130. package/build/lib/checksum.d.ts +40 -14
  131. package/build/lib/checksum.d.ts.map +1 -1
  132. package/build/lib/checksum.js +110 -57
  133. package/build/lib/checksum.js.map +1 -1
  134. package/build/lib/config.d.ts +4 -4
  135. package/build/lib/config.d.ts.map +1 -1
  136. package/build/lib/config.js +31 -8
  137. package/build/lib/config.js.map +1 -1
  138. package/build/lib/diagnostics.d.ts.map +1 -1
  139. package/build/lib/diagnostics.js +10 -7
  140. package/build/lib/diagnostics.js.map +1 -1
  141. package/build/lib/diff-command.js +1 -1
  142. package/build/lib/diff-command.js.map +1 -1
  143. package/build/lib/errors.d.ts +4 -1
  144. package/build/lib/errors.d.ts.map +1 -1
  145. package/build/lib/errors.js.map +1 -1
  146. package/build/lib/format-age.d.ts +3 -0
  147. package/build/lib/format-age.d.ts.map +1 -0
  148. package/build/lib/format-age.js +18 -0
  149. package/build/lib/format-age.js.map +1 -0
  150. package/build/lib/installer.d.ts +8 -11
  151. package/build/lib/installer.d.ts.map +1 -1
  152. package/build/lib/installer.js +83 -28
  153. package/build/lib/installer.js.map +1 -1
  154. package/build/lib/local-edits.d.ts +72 -0
  155. package/build/lib/local-edits.d.ts.map +1 -0
  156. package/build/lib/local-edits.js +145 -0
  157. package/build/lib/local-edits.js.map +1 -0
  158. package/build/lib/lockfile.d.ts +12 -8
  159. package/build/lib/lockfile.d.ts.map +1 -1
  160. package/build/lib/lockfile.js +209 -59
  161. package/build/lib/lockfile.js.map +1 -1
  162. package/build/lib/mover.d.ts.map +1 -1
  163. package/build/lib/mover.js +11 -12
  164. package/build/lib/mover.js.map +1 -1
  165. package/build/lib/multi-tool-install.d.ts +4 -2
  166. package/build/lib/multi-tool-install.d.ts.map +1 -1
  167. package/build/lib/multi-tool-install.js +12 -2
  168. package/build/lib/multi-tool-install.js.map +1 -1
  169. package/build/lib/offline.d.ts +19 -0
  170. package/build/lib/offline.d.ts.map +1 -0
  171. package/build/lib/offline.js +26 -0
  172. package/build/lib/offline.js.map +1 -0
  173. package/build/lib/owned-paths.d.ts +7 -4
  174. package/build/lib/owned-paths.d.ts.map +1 -1
  175. package/build/lib/owned-paths.js +23 -8
  176. package/build/lib/owned-paths.js.map +1 -1
  177. package/build/lib/parse-json.d.ts +12 -0
  178. package/build/lib/parse-json.d.ts.map +1 -1
  179. package/build/lib/parse-json.js +15 -2
  180. package/build/lib/parse-json.js.map +1 -1
  181. package/build/lib/pastel/commander-specs.d.ts +7 -0
  182. package/build/lib/pastel/commander-specs.d.ts.map +1 -0
  183. package/build/lib/pastel/commander-specs.js +50 -0
  184. package/build/lib/pastel/commander-specs.js.map +1 -0
  185. package/build/lib/pastel/generate-arguments.d.ts +3 -3
  186. package/build/lib/pastel/generate-arguments.d.ts.map +1 -1
  187. package/build/lib/pastel/generate-arguments.js +25 -22
  188. package/build/lib/pastel/generate-arguments.js.map +1 -1
  189. package/build/lib/pastel/generate-command.d.ts +7 -5
  190. package/build/lib/pastel/generate-command.d.ts.map +1 -1
  191. package/build/lib/pastel/generate-command.js +32 -67
  192. package/build/lib/pastel/generate-command.js.map +1 -1
  193. package/build/lib/pastel/generate-commands.d.ts +3 -5
  194. package/build/lib/pastel/generate-commands.d.ts.map +1 -1
  195. package/build/lib/pastel/generate-commands.js +6 -10
  196. package/build/lib/pastel/generate-commands.js.map +1 -1
  197. package/build/lib/pastel/generate-options.d.ts +3 -3
  198. package/build/lib/pastel/generate-options.d.ts.map +1 -1
  199. package/build/lib/pastel/generate-options.js +16 -13
  200. package/build/lib/pastel/generate-options.js.map +1 -1
  201. package/build/lib/pastel/index.d.ts +26 -4
  202. package/build/lib/pastel/index.d.ts.map +1 -1
  203. package/build/lib/pastel/index.js +104 -18
  204. package/build/lib/pastel/index.js.map +1 -1
  205. package/build/lib/pastel/internal-types.d.ts +39 -8
  206. package/build/lib/pastel/internal-types.d.ts.map +1 -1
  207. package/build/lib/pastel/manifest.d.ts +8 -0
  208. package/build/lib/pastel/manifest.d.ts.map +1 -0
  209. package/build/lib/pastel/manifest.js +7 -0
  210. package/build/lib/pastel/manifest.js.map +1 -0
  211. package/build/lib/pastel/read-commands.d.ts +7 -2
  212. package/build/lib/pastel/read-commands.d.ts.map +1 -1
  213. package/build/lib/pastel/read-commands.js +51 -30
  214. package/build/lib/pastel/read-commands.js.map +1 -1
  215. package/build/lib/pastel/render-command.d.ts +13 -0
  216. package/build/lib/pastel/render-command.d.ts.map +1 -0
  217. package/build/lib/pastel/render-command.js +56 -0
  218. package/build/lib/pastel/render-command.js.map +1 -0
  219. package/build/lib/process-alive.d.ts +11 -0
  220. package/build/lib/process-alive.d.ts.map +1 -0
  221. package/build/lib/process-alive.js +22 -0
  222. package/build/lib/process-alive.js.map +1 -0
  223. package/build/lib/pruner.d.ts +26 -0
  224. package/build/lib/pruner.d.ts.map +1 -0
  225. package/build/lib/pruner.js +42 -0
  226. package/build/lib/pruner.js.map +1 -0
  227. package/build/lib/publish-flow.d.ts +224 -0
  228. package/build/lib/publish-flow.d.ts.map +1 -0
  229. package/build/lib/publish-flow.js +1258 -0
  230. package/build/lib/publish-flow.js.map +1 -0
  231. package/build/lib/publish-run.d.ts +55 -0
  232. package/build/lib/publish-run.d.ts.map +1 -0
  233. package/build/lib/publish-run.js +169 -0
  234. package/build/lib/publish-run.js.map +1 -0
  235. package/build/lib/publisher.d.ts +25 -17
  236. package/build/lib/publisher.d.ts.map +1 -1
  237. package/build/lib/publisher.js +54 -53
  238. package/build/lib/publisher.js.map +1 -1
  239. package/build/lib/registrar.d.ts.map +1 -1
  240. package/build/lib/registrar.js +2 -2
  241. package/build/lib/registrar.js.map +1 -1
  242. package/build/lib/registry.d.ts +18 -7
  243. package/build/lib/registry.d.ts.map +1 -1
  244. package/build/lib/registry.js +113 -32
  245. package/build/lib/registry.js.map +1 -1
  246. package/build/lib/root-path.d.ts +11 -0
  247. package/build/lib/root-path.d.ts.map +1 -1
  248. package/build/lib/root-path.js +15 -1
  249. package/build/lib/root-path.js.map +1 -1
  250. package/build/lib/schemas/config.d.ts +3 -1
  251. package/build/lib/schemas/config.d.ts.map +1 -1
  252. package/build/lib/schemas/config.js +1 -1
  253. package/build/lib/schemas/config.js.map +1 -1
  254. package/build/lib/schemas/lockfile.d.ts +156 -23
  255. package/build/lib/schemas/lockfile.d.ts.map +1 -1
  256. package/build/lib/schemas/lockfile.js +100 -79
  257. package/build/lib/schemas/lockfile.js.map +1 -1
  258. package/build/lib/setup.d.ts.map +1 -1
  259. package/build/lib/setup.js +53 -48
  260. package/build/lib/setup.js.map +1 -1
  261. package/build/lib/staging.d.ts +15 -0
  262. package/build/lib/staging.d.ts.map +1 -0
  263. package/build/lib/staging.js +33 -0
  264. package/build/lib/staging.js.map +1 -0
  265. package/build/lib/sync.d.ts +22 -6
  266. package/build/lib/sync.d.ts.map +1 -1
  267. package/build/lib/sync.js +59 -7
  268. package/build/lib/sync.js.map +1 -1
  269. package/build/lib/uninstaller.d.ts.map +1 -1
  270. package/build/lib/uninstaller.js +8 -5
  271. package/build/lib/uninstaller.js.map +1 -1
  272. package/build/lib/update-check.d.ts +4 -0
  273. package/build/lib/update-check.d.ts.map +1 -1
  274. package/build/lib/update-check.js +55 -56
  275. package/build/lib/update-check.js.map +1 -1
  276. package/build/lib/updater.d.ts +31 -1
  277. package/build/lib/updater.d.ts.map +1 -1
  278. package/build/lib/updater.js +143 -4
  279. package/build/lib/updater.js.map +1 -1
  280. package/build/lib/usage-error.d.ts +20 -0
  281. package/build/lib/usage-error.d.ts.map +1 -0
  282. package/build/lib/usage-error.js +41 -0
  283. package/build/lib/usage-error.js.map +1 -0
  284. package/build/lib/user-setup.d.ts.map +1 -1
  285. package/build/lib/user-setup.js +62 -59
  286. package/build/lib/user-setup.js.map +1 -1
  287. package/build/lib/write-file.d.ts +5 -0
  288. package/build/lib/write-file.d.ts.map +1 -1
  289. package/build/lib/write-file.js +68 -13
  290. package/build/lib/write-file.js.map +1 -1
  291. package/package.json +81 -81
@@ -0,0 +1,1258 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
+ import { compare as semverCompare, valid as semverValid } from 'semver';
4
+ import { loadAllAdapters, resolveInstalledPaths } from './adapter.js';
5
+ import { isApiError } from './api-client.js';
6
+ import { getCredential } from './auth.js';
7
+ import { errorGuidance, errorMessage } from './errors.js';
8
+ import { detectCompatibleTools, getEntrypointName, getSignedInUserName } from './init.js';
9
+ import { findInstalledAsset, readLockfile } from './lockfile.js';
10
+ import { diffManifests, fetchExistingManifest } from './manifest-diff.js';
11
+ import { parseOrgFromName } from './org.js';
12
+ import { findProjectRoot } from './paths.js';
13
+ import { buildPublishPlan, buildStagingArea, bumpVersion, checkRegistryVersion, detectAssetFromPath, detectPublishType, executePublish, filledManifestFields, findSiblingDependencies, isOrgScopedTarget, loadPublishBatch, loadPublishEntries, mapPublishError, missingDependencies, readManifestFile, resolvePlanDependencies, unknownToolsError, updateManifestField, updateManifestVersion, validatePublishTarget, } from './publisher.js';
14
+ import { clearCache, fetchAssetFileBytes, fetchAssetReadme, fetchRegistry, findAsset } from './registry.js';
15
+ import { parseReviewersInput, validateGithubUsername } from './reviewers.js';
16
+ import { stageInstalledFiles } from './staging.js';
17
+ import { resolveTools } from './tool-resolver.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Helpers
20
+ // ---------------------------------------------------------------------------
21
+ export function createInitialState(flags) {
22
+ // Non-interactive when --yes, --dry-run, --json, or stdin is not a TTY (raw mode
23
+ // unsupported). Defaults isTTY to true when the flag is omitted (call site responsible
24
+ // for threading useStdin().isRawModeSupported in). --dry-run and --json force
25
+ // non-interactive regardless of TTY — JSON output cannot interleave with prompts.
26
+ const isTTY = flags.isTTY ?? true;
27
+ const nonInteractive = Boolean(flags.yes) || flags.dryRun || Boolean(flags.json) || !isTTY;
28
+ const flagError = batchFlagError(flags);
29
+ return {
30
+ authorValue: '',
31
+ bumpSelection: '',
32
+ confirmValue: '',
33
+ descriptionValue: '',
34
+ entries: [],
35
+ errorMessage: flagError ?? '',
36
+ flags,
37
+ isRawPublish: false,
38
+ isUpdate: false,
39
+ manifestDiff: undefined,
40
+ metadataSubPhase: 'description',
41
+ missingDependencies: [],
42
+ nonInteractive,
43
+ orgValue: '',
44
+ phase: flagError
45
+ ? 'error'
46
+ : flags.update
47
+ ? 'resolving-update'
48
+ : flags.fromInstalled
49
+ ? 'resolving-installed'
50
+ : 'detecting',
51
+ plan: undefined,
52
+ progressMessage: '',
53
+ promptTagsValue: '',
54
+ publishConfirmValue: '',
55
+ publishResult: undefined,
56
+ rawDetection: null,
57
+ resolvedPath: flags.update ? '' : flags.fromInstalled ? '' : resolve(flags.rawPath),
58
+ resolvedVersion: '',
59
+ reviewerValue: '',
60
+ selectedPromptTools: [],
61
+ suggestedEntries: [],
62
+ tagsValue: '',
63
+ target: undefined,
64
+ token: undefined,
65
+ toolAdapters: [],
66
+ toolsDetected: [],
67
+ validation: undefined,
68
+ versionCheck: undefined,
69
+ withConfirmValue: '',
70
+ };
71
+ }
72
+ /** Several folders published together (`atk publish a b c`) in one pull request; one folder named twice is one. */
73
+ export function isBatch(flags) {
74
+ return new Set(flags.paths?.map((path) => resolve(path))).size > 1;
75
+ }
76
+ /**
77
+ * Run the step for the state's phase, if it has one: the work the phase waits on,
78
+ * reported through `ctx.dispatch`, which reaches the runner only until the run is
79
+ * cancelled. An error the step throws is dispatched too, so this never rejects.
80
+ * Prompt and terminal phases have no step.
81
+ */
82
+ export async function runPublishStep(state, ctx) {
83
+ const step = stepFor(state);
84
+ if (!step)
85
+ return;
86
+ const guarded = {
87
+ ...ctx,
88
+ dispatch: (action) => {
89
+ if (!ctx.cancelled())
90
+ ctx.dispatch(action);
91
+ },
92
+ };
93
+ try {
94
+ await step.run(state, guarded);
95
+ }
96
+ catch (err) {
97
+ if (ctx.cancelled())
98
+ return;
99
+ const action = step.fail
100
+ ? await step.fail(err, state, guarded).catch(() => toErrorAction(err))
101
+ : toErrorAction(err);
102
+ if (action)
103
+ guarded.dispatch(action);
104
+ }
105
+ }
106
+ /** The comma-separated values in a flag or prompt answer, trimmed, without empty ones. */
107
+ export function splitList(value) {
108
+ return (value ?? '')
109
+ .split(',')
110
+ .map((item) => item.trim())
111
+ .filter(Boolean);
112
+ }
113
+ /** Why the flags can't publish a batch: one installed asset, or one asset's metadata. Undefined otherwise. */
114
+ function batchFlagError(flags) {
115
+ if (!isBatch(flags))
116
+ return undefined;
117
+ if (flags.update || flags.fromInstalled) {
118
+ return `${flags.update ? '--update' : '--from-installed'} publishes one installed asset; leave it out to publish several folders.`;
119
+ }
120
+ const perAsset = [
121
+ ['--description', flags.descriptionFlag],
122
+ ['--tags', flags.tagsFlag],
123
+ ['--tools', flags.toolsFlag],
124
+ ].flatMap(([flag, value]) => (value === undefined ? [] : [flag]));
125
+ if (perAsset.length > 0) {
126
+ return `${perAsset.join(', ')} ${perAsset.length === 1 ? 'sets' : 'set'} one asset's metadata; put it in each folder's manifest.json to publish several folders.`;
127
+ }
128
+ return undefined;
129
+ }
130
+ /** In a batch, what the API calls `Root asset`, as the folder the user named. */
131
+ function batchRootLabel(state) {
132
+ return isBatch(state.flags) ? state.flags.rawPath : undefined;
133
+ }
134
+ /** What the API calls `dependencies[i]`, as the folder the user named. */
135
+ function entryLabels(state) {
136
+ return state.entries.map((entry) => entry.label);
137
+ }
138
+ /** The installed asset `--from-installed` or `--update` names, with the tool's adapter; an error when it isn't installed. */
139
+ async function findInstalledSource(state, ctx) {
140
+ const assetName = state.flags.rawPath;
141
+ const { name, org } = parseOrgFromName(assetName);
142
+ const projectRoot = findProjectRoot(state.flags.projectRoot);
143
+ const tools = await resolveTools(state.flags.tool);
144
+ const adapter = tools[0].adapter;
145
+ const lockfile = await readLockfile(projectRoot);
146
+ const installed = findInstalledAsset(lockfile, name, undefined, org);
147
+ if (!installed) {
148
+ ctx.dispatch({ message: `Asset '${assetName}' is not installed.`, type: 'ERROR' });
149
+ return undefined;
150
+ }
151
+ return { adapter, installed, org, projectRoot };
152
+ }
153
+ /** Run the writes in order until one throws: its index and error, or undefined when all succeed. */
154
+ async function firstFailure(edits) {
155
+ for (const [index, edit] of edits.entries()) {
156
+ try {
157
+ await edit.write();
158
+ }
159
+ catch (error) {
160
+ return { error, index };
161
+ }
162
+ }
163
+ return undefined;
164
+ }
165
+ /**
166
+ * The metadata step of a raw publish, for a raw folder or an installed asset, whose `source`
167
+ * gives its org and any description of its own: the signed-in author, the description from
168
+ * --description or the source, the tags from --tags, and the tools from --tools or detection.
169
+ * An interactive run goes on to ask for what is missing; a non-interactive one stages the
170
+ * manifest now, or fails without a description or tools.
171
+ */
172
+ async function gatherRawMetadata(state, ctx, detection, source) {
173
+ const [signedIn, tools] = await Promise.all([
174
+ getSignedInUserName(),
175
+ state.flags.toolsFlag ? splitList(state.flags.toolsFlag) : detectCompatibleTools(detection.assetType),
176
+ ]);
177
+ if (ctx.cancelled())
178
+ return;
179
+ const author = signedIn ?? '';
180
+ const description = state.flags.descriptionFlag?.trim() || source.description;
181
+ if (!state.nonInteractive) {
182
+ ctx.dispatch({
183
+ authorValue: author,
184
+ ...(description ? { description } : {}),
185
+ orgValue: source.org ?? '',
186
+ rawDetection: detection,
187
+ toolsDetected: tools,
188
+ type: 'RAW_DETECT_DONE',
189
+ });
190
+ return;
191
+ }
192
+ if (!description) {
193
+ ctx.dispatch({
194
+ message: state.flags.fromInstalled
195
+ ? `description required: provide --description (the lockfile has none for '${detection.name}')`
196
+ : 'description required: provide --description or include in manifest.json',
197
+ type: 'ERROR',
198
+ });
199
+ return;
200
+ }
201
+ if (tools.length === 0) {
202
+ ctx.dispatch({
203
+ message: `--tools <comma-separated> required: no tools detected for type ${detection.assetType}`,
204
+ type: 'ERROR',
205
+ });
206
+ return;
207
+ }
208
+ const stagingDir = await buildStagingArea(detection, {
209
+ author,
210
+ description,
211
+ ...(source.org ? { org: source.org } : {}),
212
+ tags: splitList(state.flags.tagsFlag),
213
+ tools,
214
+ });
215
+ ctx.stage(stagingDir);
216
+ if (ctx.cancelled())
217
+ return;
218
+ const detected = await detectPublishType(stagingDir);
219
+ if (ctx.cancelled())
220
+ return;
221
+ ctx.dispatch({ rawDetection: detection, target: detected, type: 'RAW_DETECT_DONE_NON_INTERACTIVE' });
222
+ }
223
+ function stepFor(state) {
224
+ switch (state.phase) {
225
+ case 'building-plan':
226
+ return buildingPlan;
227
+ case 'checking-entries':
228
+ return checkingEntries;
229
+ case 'checking-registry':
230
+ return checkingRegistry;
231
+ case 'detecting':
232
+ return detecting;
233
+ case 'gathering-metadata':
234
+ return state.metadataSubPhase === 'building-staging' ? buildingStaging : undefined;
235
+ case 'loading-tools':
236
+ return loadingTools;
237
+ case 'publishing':
238
+ return publishing;
239
+ case 'resolving-installed':
240
+ return resolvingInstalled;
241
+ case 'resolving-update':
242
+ return resolvingUpdate;
243
+ case 'validating':
244
+ return validating;
245
+ default:
246
+ return undefined;
247
+ }
248
+ }
249
+ /**
250
+ * Folders next to the root asset that could fix the plan's error: an interactive
251
+ * run, a `422` naming root dependencies that aren't in the registry (or have no
252
+ * matching version), and a sibling folder whose manifest has the same type and
253
+ * name that isn't an entry yet. Undefined when there is nothing to offer.
254
+ * `--update` and `--from-installed` roots live in a temp folder, so they never get offers.
255
+ */
256
+ async function suggestSiblingEntries(state, err) {
257
+ const { target } = state;
258
+ if (state.nonInteractive ||
259
+ state.flags.fromInstalled ||
260
+ state.flags.update ||
261
+ target?.type !== 'asset' ||
262
+ !(isApiError(err, 'dependency_not_found') || isApiError(err, 'dependency_version_unsatisfied'))) {
263
+ return undefined;
264
+ }
265
+ const included = (d) => state.entries.some((e) => e.target.assetType === d.type && e.target.data.name === d.name);
266
+ const missing = missingDependencies(target.data, err.issues).filter((d) => !included(d));
267
+ if (missing.length === 0)
268
+ return undefined;
269
+ const rootDir = state.rawDetection?.sourceDir ?? target.sourceDir;
270
+ const suggested = await findSiblingDependencies(rootDir, missing, state.entries.map((e) => e.target.sourceDir), target.data.org);
271
+ return suggested.length > 0 ? { missing, suggested } : undefined;
272
+ }
273
+ /**
274
+ * Build an ERROR dispatch action from a caught value, carrying the `code`,
275
+ * `hint` and `command` typed errors have so commands can render actionable
276
+ * recovery guidance.
277
+ */
278
+ function toErrorAction(err, override) {
279
+ return {
280
+ ...errorGuidance(err),
281
+ message: override ?? errorMessage(err),
282
+ type: 'ERROR',
283
+ };
284
+ }
285
+ /**
286
+ * Write what the run changed in memory to the manifests on disk: the root's bump and each
287
+ * entry's, and the tags and tools the publish sent on top of the root's manifest (from flags,
288
+ * prompts or detection). Only once the publish has opened its PR, so a cancelled or failed one
289
+ * leaves every manifest as it was. A file whose write fails takes none of its later edits and
290
+ * comes back as one warning naming the fields it lacks; the other files are still written.
291
+ */
292
+ async function writeManifestEdits(state) {
293
+ const target = state.target;
294
+ if (!target)
295
+ return [];
296
+ const files = new Map();
297
+ const edit = (dir, file, field, value, write) => {
298
+ const path = join(dir, file);
299
+ files.set(path, [...(files.get(path) ?? []), { field, value, write }]);
300
+ };
301
+ const version = (dir, file, value) => edit(dir, file, 'version', value, () => updateManifestVersion(dir, file, value));
302
+ const manifestFile = target.type === 'asset' ? 'manifest.json' : 'bundle.json';
303
+ if (state.versionCheck?.status === 'needs-bump' && state.resolvedVersion) {
304
+ version(target.sourceDir, manifestFile, state.resolvedVersion);
305
+ }
306
+ // A manifest that can't be read now takes no field edits; its version edit, if any, warns.
307
+ const written = await readManifestFile(target).catch(() => undefined);
308
+ for (const [name, value] of Object.entries(written ? filledManifestFields(written, target) : {})) {
309
+ edit(target.sourceDir, manifestFile, name, value, () => updateManifestField(target.sourceDir, manifestFile, name, value));
310
+ }
311
+ for (const entry of state.entries) {
312
+ if (entry.versionCheck?.status === 'needs-bump' && entry.resolvedVersion) {
313
+ version(entry.target.sourceDir, 'manifest.json', entry.resolvedVersion);
314
+ }
315
+ }
316
+ const unwritten = [];
317
+ for (const [path, edits] of files) {
318
+ const failed = await firstFailure(edits);
319
+ if (!failed)
320
+ continue;
321
+ const missing = edits.slice(failed.index);
322
+ // Only the version matters to the next publish: it fills missing tags and tools again.
323
+ const again = missing.some((e) => e.field === 'version') ? ' before you publish it again' : '';
324
+ unwritten.push(`The PR is open, but ${path} wasn't updated (${errorMessage(failed.error)}): set ${missing.map((e) => `"${e.field}" to ${JSON.stringify(e.value)}`).join(', ')}${again}.`);
325
+ }
326
+ return unwritten;
327
+ }
328
+ // ---------------------------------------------------------------------------
329
+ // Steps, one per phase that waits on work
330
+ // ---------------------------------------------------------------------------
331
+ /**
332
+ * The installed asset's files, at their source paths, through a raw publish's metadata step,
333
+ * with the version, entrypoint, dependencies, org and description its lockfile entry records.
334
+ */
335
+ const resolvingInstalled = {
336
+ async run(state, ctx) {
337
+ const source = await findInstalledSource(state, ctx);
338
+ if (!source)
339
+ return;
340
+ const { adapter, installed, projectRoot } = source;
341
+ const { files } = resolveInstalledPaths(installed, adapter, projectRoot);
342
+ // An older lockfile entry has no entrypoint: the type's usual one when it was installed, else its first file.
343
+ const usual = getEntrypointName(installed.type, installed.name);
344
+ const entrypoint = installed.entrypoint ?? (files.some((file) => file.sourcePath === usual) ? usual : files[0]?.sourcePath);
345
+ const dependencies = (installed.dependencies ?? []).map(({ name, type, version }) => ({
346
+ name,
347
+ type,
348
+ ...(version ? { version } : {}),
349
+ }));
350
+ const detection = {
351
+ assetType: installed.type,
352
+ ...(dependencies.length > 0 ? { dependencies } : {}),
353
+ ...(entrypoint ? { entrypoint } : {}),
354
+ files: files.map((file) => ({
355
+ absolutePath: join(projectRoot, file.installedPath),
356
+ relativePath: file.sourcePath,
357
+ })),
358
+ name: installed.name,
359
+ sourceDir: projectRoot,
360
+ version: installed.version,
361
+ };
362
+ // A --with folder that is missing, a bundle or a repeat fails here, before any prompt, as in detecting.
363
+ if (state.flags.withPaths?.length) {
364
+ const entries = await loadPublishEntries(state.flags.withPaths, projectRoot);
365
+ if (ctx.cancelled())
366
+ return;
367
+ ctx.dispatch({ entries, type: 'ENTRIES_LOADED' });
368
+ }
369
+ await gatherRawMetadata(state, ctx, detection, { description: installed.description, org: installed.org });
370
+ },
371
+ };
372
+ const resolvingUpdate = {
373
+ async run(state, ctx) {
374
+ const source = await findInstalledSource(state, ctx);
375
+ if (!source)
376
+ return;
377
+ const { adapter, installed, org, projectRoot } = source;
378
+ const assetName = state.flags.rawPath;
379
+ // Fetch the registry to find the latest version. An installed entry is always an asset.
380
+ const registry = await fetchRegistry({ force: state.flags.refresh });
381
+ if (ctx.cancelled())
382
+ return;
383
+ const resolvedAsset = findAsset(registry, installed.name, installed.type, undefined, installed.org ?? org);
384
+ if (!resolvedAsset) {
385
+ ctx.dispatch({ message: `Asset '${assetName}' not found in the registry.`, type: 'ERROR' });
386
+ return;
387
+ }
388
+ // The installed files go out on top of the latest's manifest, so they must not be older
389
+ // than it: they would publish the next version without the changes made since.
390
+ const latestVersion = resolvedAsset.version;
391
+ if (!semverValid(installed.version) || semverCompare(installed.version, latestVersion) < 0) {
392
+ ctx.dispatch({
393
+ command: `atk diff ${assetName}`,
394
+ hint: `See what changed since v${installed.version} with \`atk diff ${assetName}\`, get the latest into a folder with \`atk checkout ${assetName}\`, carry your edits into it, and publish that folder.`,
395
+ message: `'${assetName}' is installed at v${installed.version}, but the registry's latest is v${latestVersion}. --update would publish your copy, built on v${installed.version}, as the next version and undo the changes made since.`,
396
+ type: 'ERROR',
397
+ });
398
+ return;
399
+ }
400
+ // The latest's manifest as the registry has it, byte for byte, so a field this CLI's schema
401
+ // doesn't know (from a newer publisher) is published again; and its README, if any.
402
+ const { org: assetOrg, type } = resolvedAsset.asset;
403
+ const ref = { name: installed.name, ...(assetOrg ? { org: assetOrg } : {}), type };
404
+ const [manifest, readme] = await Promise.all([
405
+ fetchAssetFileBytes(ref, latestVersion, 'manifest.json'),
406
+ fetchAssetReadme(ref, latestVersion),
407
+ ]);
408
+ if (ctx.cancelled())
409
+ return;
410
+ // Copy the user's locally modified files into a temp staging directory
411
+ const tempDir = await stageInstalledFiles(installed, adapter, projectRoot);
412
+ ctx.stage(tempDir);
413
+ if (ctx.cancelled())
414
+ return;
415
+ await writeFile(join(tempDir, 'manifest.json'), manifest);
416
+ if (readme) {
417
+ await writeFile(join(tempDir, 'README.md'), readme, 'utf-8');
418
+ }
419
+ ctx.dispatch({ resolvedPath: tempDir, type: 'RESOLVED_UPDATE' });
420
+ },
421
+ };
422
+ const detecting = {
423
+ async run(state, ctx) {
424
+ // A batch reads every folder now; any that is missing, a bundle or not an asset folder
425
+ // fails here, all of them together, before any prompt.
426
+ if (isBatch(state.flags)) {
427
+ const { entries, root } = await loadPublishBatch(state.flags.paths ?? [], state.flags.withPaths);
428
+ if (ctx.cancelled())
429
+ return;
430
+ ctx.dispatch({ entries, type: 'ENTRIES_LOADED' });
431
+ ctx.dispatch({ target: root.target, type: 'DETECT_DONE' });
432
+ return;
433
+ }
434
+ // A --with folder that is missing, a bundle, the root or a repeat fails here, before any
435
+ // prompt; its validation and version check come in checking-entries.
436
+ if (state.flags.withPaths?.length) {
437
+ const entries = await loadPublishEntries(state.flags.withPaths, state.resolvedPath);
438
+ if (ctx.cancelled())
439
+ return;
440
+ ctx.dispatch({ entries, type: 'ENTRIES_LOADED' });
441
+ }
442
+ // First, try manifest-based detection (existing flow)
443
+ try {
444
+ const detected = await detectPublishType(state.resolvedPath);
445
+ if (ctx.cancelled())
446
+ return;
447
+ ctx.dispatch({ target: detected, type: 'DETECT_DONE' });
448
+ return;
449
+ }
450
+ catch (manifestError) {
451
+ // Only attempt raw detection if the error is specifically about missing manifest/bundle
452
+ const manifestMsg = errorMessage(manifestError);
453
+ if (!manifestMsg.includes('No manifest.json or bundle.json found')) {
454
+ // This is a different error (e.g., invalid JSON in manifest) — surface it directly
455
+ ctx.dispatch({ message: manifestMsg, type: 'ERROR' });
456
+ return;
457
+ }
458
+ }
459
+ // Manifest not found — attempt raw asset detection
460
+ const resolvedTools = await resolveTools(state.flags.tool);
461
+ const adapter = resolvedTools[0].adapter;
462
+ if (ctx.cancelled())
463
+ return;
464
+ const projectRoot = findProjectRoot(state.flags.projectRoot);
465
+ const detection = await detectAssetFromPath(state.resolvedPath, adapter, projectRoot);
466
+ if (ctx.cancelled())
467
+ return;
468
+ // No manifest exists yet, so the project's org and the flags and prompts give the metadata.
469
+ const lockfile = await readLockfile(projectRoot);
470
+ if (ctx.cancelled())
471
+ return;
472
+ await gatherRawMetadata(state, ctx, detection, { org: lockfile.org });
473
+ },
474
+ };
475
+ /** The `building-staging` sub-phase of `gathering-metadata`: the manifest from the prompts' answers. */
476
+ const buildingStaging = {
477
+ async run(state, ctx) {
478
+ const stagingDir = await buildStagingArea(state.rawDetection, {
479
+ author: state.authorValue,
480
+ description: state.descriptionValue,
481
+ org: state.orgValue || undefined,
482
+ tags: splitList(state.tagsValue),
483
+ tools: state.toolsDetected,
484
+ });
485
+ ctx.stage(stagingDir);
486
+ if (ctx.cancelled())
487
+ return;
488
+ // Re-detect using the staging area (now has manifest.json)
489
+ const detectedTarget = await detectPublishType(stagingDir);
490
+ if (ctx.cancelled())
491
+ return;
492
+ ctx.dispatch({ target: detectedTarget, type: 'STAGING_DONE' });
493
+ },
494
+ };
495
+ const validating = {
496
+ async run(state, ctx) {
497
+ const target = state.target;
498
+ if (!target)
499
+ return;
500
+ let lockfileOrg;
501
+ let lockfileOrgReviewers;
502
+ try {
503
+ const projectRoot = findProjectRoot(state.flags.projectRoot);
504
+ const lockfile = await readLockfile(projectRoot);
505
+ lockfileOrg = lockfile.org;
506
+ lockfileOrgReviewers = lockfile.orgReviewers;
507
+ }
508
+ catch {
509
+ // No project root / lockfile — treat as no lockfile org / reviewers
510
+ }
511
+ if (ctx.cancelled())
512
+ return;
513
+ // Bundles need the registry so their asset refs can be resolved during
514
+ // validation — an unresolvable ref must fail here, not at install time.
515
+ let registry;
516
+ if (target.type === 'bundle') {
517
+ registry = await fetchRegistry({ force: state.flags.refresh });
518
+ if (ctx.cancelled())
519
+ return;
520
+ }
521
+ const result = await validatePublishTarget(target, {
522
+ force: state.flags.force,
523
+ lockfileOrg,
524
+ registry,
525
+ });
526
+ if (ctx.cancelled())
527
+ return;
528
+ // A batch checks every asset before stopping, so one run lists every folder's problems.
529
+ if (isBatch(state.flags)) {
530
+ const entries = [];
531
+ for (const entry of state.entries) {
532
+ const validation = await validatePublishTarget(entry.target, { force: state.flags.force, lockfileOrg });
533
+ if (ctx.cancelled())
534
+ return;
535
+ entries.push({ ...entry, validation });
536
+ }
537
+ const failed = [{ label: state.flags.rawPath, validation: result }, ...entries].filter((f) => f.validation && !f.validation.valid);
538
+ if (failed.length > 0) {
539
+ const lines = failed.flatMap((f) => [
540
+ ` ${f.label}:`,
541
+ ...(f.validation?.errors ?? []).map((e) => ` - ${e}`),
542
+ ]);
543
+ ctx.dispatch({ message: `Validation failed:\n${lines.join('\n')}`, type: 'ERROR' });
544
+ return;
545
+ }
546
+ // checking-entries keeps these instead of validating each entry again.
547
+ ctx.dispatch({ entries, type: 'ENTRIES_LOADED' });
548
+ }
549
+ ctx.dispatch({ lockfileOrgReviewers, type: 'VALIDATE_DONE', validation: result });
550
+ },
551
+ };
552
+ /**
553
+ * In interactive mode without --tools, loads adapters for the multi-select prompt. Otherwise
554
+ * resolves tools via manifest -> --tools flag -> detectCompatibleTools(assetType) -> exit 1,
555
+ * then dispatches RESOLVE_TOOLS_NONINTERACTIVE to advance past the prompt.
556
+ */
557
+ const loadingTools = {
558
+ async run(state, ctx) {
559
+ if (!state.nonInteractive && !state.flags.toolsFlag) {
560
+ const adapters = await loadAllAdapters();
561
+ if (ctx.cancelled())
562
+ return;
563
+ ctx.dispatch({ adapters, type: 'TOOLS_LOADED' });
564
+ return;
565
+ }
566
+ // Manifest tools win when present (VALIDATE_DONE only routes here when
567
+ // manifest tools are missing for assets — bundles short-circuit hasTools).
568
+ if (state.target?.type !== 'asset') {
569
+ // Bundles don't carry tools — just advance.
570
+ ctx.dispatch({ tools: [], type: 'RESOLVE_TOOLS_NONINTERACTIVE' });
571
+ return;
572
+ }
573
+ // Try --tools flag
574
+ let resolvedToolList = splitList(state.flags.toolsFlag);
575
+ if (state.flags.toolsFlag) {
576
+ // Validation ran before these tools were filled in, so check them here.
577
+ const toolsError = await unknownToolsError(resolvedToolList);
578
+ if (ctx.cancelled())
579
+ return;
580
+ if (toolsError) {
581
+ ctx.dispatch({ message: `--tools: ${toolsError}`, type: 'ERROR' });
582
+ return;
583
+ }
584
+ }
585
+ // Fall back to detectCompatibleTools
586
+ if (resolvedToolList.length === 0) {
587
+ const assetType = state.target.assetType;
588
+ if (!assetType) {
589
+ ctx.dispatch({
590
+ message: '--tools <comma-separated> required: no tools detected for type unknown',
591
+ type: 'ERROR',
592
+ });
593
+ return;
594
+ }
595
+ const detected = await detectCompatibleTools(assetType);
596
+ if (ctx.cancelled())
597
+ return;
598
+ if (detected.length === 0) {
599
+ ctx.dispatch({
600
+ message: `--tools <comma-separated> required: no tools detected for type ${assetType}`,
601
+ type: 'ERROR',
602
+ });
603
+ return;
604
+ }
605
+ resolvedToolList = detected;
606
+ }
607
+ ctx.dispatch({ tools: resolvedToolList, type: 'RESOLVE_TOOLS_NONINTERACTIVE' });
608
+ },
609
+ };
610
+ const checkingRegistry = {
611
+ async fail(err) {
612
+ return toErrorAction(err, mapPublishError(err));
613
+ },
614
+ async run(state, ctx) {
615
+ const target = state.target;
616
+ if (!target)
617
+ return;
618
+ const authToken = (await getCredential()).token;
619
+ if (ctx.cancelled())
620
+ return;
621
+ const reg = await fetchRegistry({ force: state.flags.refresh });
622
+ if (ctx.cancelled())
623
+ return;
624
+ const versionResult = checkRegistryVersion(target, reg);
625
+ // A version the registry doesn't have yet is published as it is; needs-bump settles it later.
626
+ const isUpdate = versionResult.status !== 'new-asset';
627
+ const resolvedVersion = versionResult.status === 'needs-bump' ? '' : target.data.version;
628
+ ctx.dispatch({
629
+ isUpdate,
630
+ resolvedVersion,
631
+ token: authToken,
632
+ type: 'REGISTRY_CHECK_DONE',
633
+ versionCheck: versionResult,
634
+ });
635
+ },
636
+ };
637
+ /**
638
+ * Validate each entry not yet checked the same way as the root asset, and check its
639
+ * version against the registry; advanceToPlan then settles any bump it needs.
640
+ */
641
+ const checkingEntries = {
642
+ async fail(err) {
643
+ return toErrorAction(err, mapPublishError(err));
644
+ },
645
+ async run(state, ctx) {
646
+ let lockfileOrg;
647
+ try {
648
+ lockfileOrg = (await readLockfile(findProjectRoot(state.flags.projectRoot))).org;
649
+ }
650
+ catch {
651
+ // No project root / lockfile — treat as no lockfile org
652
+ }
653
+ const registry = await fetchRegistry({ force: state.flags.refresh });
654
+ if (ctx.cancelled())
655
+ return;
656
+ const entries = [];
657
+ for (const entry of state.entries) {
658
+ if (entry.versionCheck) {
659
+ entries.push(entry);
660
+ continue;
661
+ }
662
+ const validation = entry.validation ?? (await validatePublishTarget(entry.target, { force: state.flags.force, lockfileOrg }));
663
+ if (ctx.cancelled())
664
+ return;
665
+ if (!validation.valid) {
666
+ ctx.dispatch({
667
+ message: `Validation failed for ${entry.label}:\n${validation.errors.map((e) => ` - ${e}`).join('\n')}`,
668
+ type: 'ERROR',
669
+ });
670
+ return;
671
+ }
672
+ const versionCheck = checkRegistryVersion(entry.target, registry);
673
+ entries.push({
674
+ ...entry,
675
+ ...(versionCheck.status === 'needs-bump' ? {} : { resolvedVersion: entry.target.data.version }),
676
+ validation,
677
+ versionCheck,
678
+ });
679
+ }
680
+ ctx.dispatch({ entries, type: 'ENTRIES_CHECKED' });
681
+ },
682
+ };
683
+ /**
684
+ * Plans from the targets in memory, which carry every bump and prompted tag or tool:
685
+ * nothing is written to a manifest until the publish succeeds (writeManifestEdits).
686
+ */
687
+ const buildingPlan = {
688
+ async fail(err, state, ctx) {
689
+ // Worded like the publishing phase: resolvePlanDependencies calls the same API.
690
+ const error = toErrorAction(err, mapPublishError(err, entryLabels(state), batchRootLabel(state)));
691
+ const suggestion = await suggestSiblingEntries(state, err).catch(() => undefined);
692
+ if (ctx.cancelled())
693
+ return undefined;
694
+ return suggestion ? { error, ...suggestion, type: 'SUGGEST_ENTRIES' } : error;
695
+ },
696
+ async run(state, ctx) {
697
+ const currentTarget = state.target;
698
+ if (!currentTarget)
699
+ return;
700
+ const currentVersion = state.resolvedVersion;
701
+ // Reject invalid reviewer usernames here, before any branch/commit/push work happens
702
+ const reviewers = parseReviewersInput(state.reviewerValue);
703
+ for (const reviewer of reviewers) {
704
+ const check = validateGithubUsername(reviewer);
705
+ if (!check.valid) {
706
+ throw new Error(check.error);
707
+ }
708
+ }
709
+ const entries = state.entries.map((entry) => ({ label: entry.label, target: entry.target }));
710
+ const localPlan = await buildPublishPlan(currentTarget, currentVersion, state.isUpdate, state.flags.message, reviewers, entries, ...(isBatch(state.flags) ? [state.flags.rawPath] : []));
711
+ if (ctx.cancelled())
712
+ return;
713
+ // Ask the API for its plan now, so the preview and --dry-run show its dependencies
714
+ // and reviewers, and anything it rejects stops before the confirm prompt.
715
+ const publishPlan = await resolvePlanDependencies(localPlan);
716
+ if (ctx.cancelled())
717
+ return;
718
+ let manifestDiff;
719
+ if (state.isUpdate && state.versionCheck && state.versionCheck.status !== 'new-asset' && state.token) {
720
+ const existing = await fetchExistingManifest(currentTarget, state.versionCheck.latestVersion);
721
+ if (ctx.cancelled())
722
+ return;
723
+ if (existing) {
724
+ manifestDiff = diffManifests(existing, currentTarget.data);
725
+ }
726
+ }
727
+ ctx.dispatch({ manifestDiff, plan: publishPlan, type: 'PLAN_DONE' });
728
+ },
729
+ };
730
+ const publishing = {
731
+ async fail(err, state) {
732
+ return toErrorAction(err, mapPublishError(err, entryLabels(state), batchRootLabel(state)));
733
+ },
734
+ async run(state, ctx) {
735
+ const plan = state.plan;
736
+ if (!plan || !state.token)
737
+ return;
738
+ const published = await executePublish(plan, (message) => ctx.dispatch({ message, type: 'PUBLISH_PROGRESS' }));
739
+ // The PR is open, so the folders take its versions, tags and tools even if the run was cancelled meanwhile.
740
+ const unwritten = await writeManifestEdits(state);
741
+ if (ctx.cancelled())
742
+ return;
743
+ const result = unwritten.length > 0 ? { ...published, warnings: [...(published.warnings ?? []), ...unwritten] } : published;
744
+ // Clear registry cache so subsequent commands see the new version
745
+ try {
746
+ await clearCache();
747
+ }
748
+ catch {
749
+ // Best-effort — don't fail publish over cache
750
+ }
751
+ // The lockfile is left as it was, even for an installed asset or a raw folder: the registry
752
+ // has the new version only once the PR merges, and a fresh clone installs the version the
753
+ // lockfile names (ATK-592). `atk update` (or `atk install`) records it after the merge.
754
+ if (ctx.cancelled())
755
+ return;
756
+ ctx.dispatch({ result, type: 'PUBLISH_DONE' });
757
+ },
758
+ };
759
+ // ---------------------------------------------------------------------------
760
+ // Reducer
761
+ // ---------------------------------------------------------------------------
762
+ export function publishReducer(state, action) {
763
+ switch (action.type) {
764
+ case 'BUMP_DONE': {
765
+ const bumped = {
766
+ ...state,
767
+ errorMessage: action.message ? action.message : state.errorMessage,
768
+ resolvedVersion: action.version,
769
+ target: action.updatedTarget,
770
+ };
771
+ return action.message ? { ...bumped, phase: 'error' } : advanceToPlan(bumped);
772
+ }
773
+ case 'CONFIRM_PROMPT_TOOLS': {
774
+ if (action.tools.length === 0)
775
+ return state;
776
+ const updatedToolsTarget = state.target
777
+ ? {
778
+ ...state.target,
779
+ data: { ...state.target.data, tools: action.tools },
780
+ }
781
+ : undefined;
782
+ return {
783
+ ...state,
784
+ phase: phaseAfterMetadata(updatedToolsTarget),
785
+ selectedPromptTools: action.tools,
786
+ target: updatedToolsTarget,
787
+ };
788
+ }
789
+ case 'DETECT_DONE':
790
+ if (action.target.type === 'bundle' && state.entries.length > 0) {
791
+ return {
792
+ ...state,
793
+ errorMessage: 'A bundle is published on its own. Remove --with, and publish its new assets first.',
794
+ phase: 'error',
795
+ target: action.target,
796
+ };
797
+ }
798
+ return {
799
+ ...state,
800
+ phase: 'validating',
801
+ target: action.target,
802
+ };
803
+ case 'ENTRIES_CHECKED':
804
+ return advanceToPlan({ ...state, entries: action.entries });
805
+ case 'ENTRIES_LOADED':
806
+ return { ...state, entries: action.entries };
807
+ case 'ERROR':
808
+ return {
809
+ ...state,
810
+ errorCode: action.code,
811
+ errorCommand: action.command,
812
+ errorHint: action.hint,
813
+ errorMessage: action.message,
814
+ phase: 'error',
815
+ };
816
+ case 'PLAN_DONE': {
817
+ // dry-run always shows the dry-run result; --yes / non-TTY skip the confirm prompt
818
+ // and proceed directly to publishing.
819
+ let nextPhase;
820
+ if (state.flags.dryRun) {
821
+ nextPhase = 'dry-run-result';
822
+ }
823
+ else if (state.nonInteractive) {
824
+ nextPhase = 'publishing';
825
+ }
826
+ else {
827
+ nextPhase = 'confirm-publish';
828
+ }
829
+ return {
830
+ ...state,
831
+ manifestDiff: action.manifestDiff,
832
+ phase: nextPhase,
833
+ plan: action.plan,
834
+ };
835
+ }
836
+ case 'PUBLISH_DONE':
837
+ return { ...state, phase: 'done', publishResult: action.result };
838
+ case 'PUBLISH_PROGRESS':
839
+ return { ...state, progressMessage: action.message };
840
+ case 'RAW_DETECT_DONE':
841
+ // Ask only for what neither a flag nor the installed asset gave.
842
+ return {
843
+ ...state,
844
+ authorValue: action.authorValue,
845
+ descriptionValue: action.description ?? '',
846
+ isRawPublish: !state.flags.fromInstalled,
847
+ metadataSubPhase: action.description === undefined ? 'description' : metadataAfterDescription(state),
848
+ orgValue: action.orgValue,
849
+ phase: 'gathering-metadata',
850
+ rawDetection: action.rawDetection,
851
+ tagsValue: state.flags.tagsFlag ?? '',
852
+ toolsDetected: action.toolsDetected,
853
+ };
854
+ case 'RAW_DETECT_DONE_NON_INTERACTIVE':
855
+ return {
856
+ ...state,
857
+ isRawPublish: !state.flags.fromInstalled,
858
+ phase: 'validating',
859
+ rawDetection: action.rawDetection,
860
+ target: action.target,
861
+ };
862
+ case 'REGISTRY_CHECK_DONE': {
863
+ const { isUpdate, resolvedVersion, token, versionCheck } = action;
864
+ let nextPhase;
865
+ let errorMessage = state.errorMessage;
866
+ let bumpedVersion = resolvedVersion;
867
+ let bumpedTarget = state.target;
868
+ if (versionCheck.status === 'new-asset') {
869
+ nextPhase = 'building-plan';
870
+ }
871
+ else if (versionCheck.status === 'version-already-bumped') {
872
+ // --yes / non-TTY implicitly confirm the update
873
+ nextPhase = state.nonInteractive ? 'building-plan' : 'confirm-update';
874
+ }
875
+ else {
876
+ // needs-bump
877
+ if (state.flags.bump && state.target) {
878
+ // --bump applies in any run; a successful publish writes it to the manifest
879
+ try {
880
+ bumpedVersion = bumpVersion(versionCheck.latestVersion, state.flags.bump);
881
+ bumpedTarget = {
882
+ ...state.target,
883
+ data: { ...state.target.data, version: bumpedVersion },
884
+ };
885
+ nextPhase = 'building-plan';
886
+ }
887
+ catch {
888
+ errorMessage = 'Failed to compute bumped version.';
889
+ nextPhase = 'error';
890
+ }
891
+ }
892
+ else if (!state.nonInteractive) {
893
+ nextPhase = 'bump-prompt';
894
+ }
895
+ else if (isBatch(state.flags)) {
896
+ // advanceToPlan lists every asset of the batch that needs --bump, once the entries are checked.
897
+ nextPhase = 'building-plan';
898
+ }
899
+ else {
900
+ errorMessage = `--bump <patch|minor|major> required: registry has v${versionCheck.latestVersion} but manifest is not newer`;
901
+ nextPhase = 'error';
902
+ }
903
+ }
904
+ const checked = {
905
+ ...state,
906
+ errorMessage,
907
+ isUpdate,
908
+ phase: nextPhase,
909
+ resolvedVersion: bumpedVersion,
910
+ target: bumpedTarget,
911
+ token,
912
+ versionCheck,
913
+ };
914
+ return nextPhase === 'building-plan' ? advanceToPlan(checked) : checked;
915
+ }
916
+ case 'RESOLVE_TOOLS_NONINTERACTIVE': {
917
+ const updatedTarget = state.target
918
+ ? {
919
+ ...state.target,
920
+ data: { ...state.target.data, tools: action.tools },
921
+ }
922
+ : undefined;
923
+ return {
924
+ ...state,
925
+ // An interactive run's --tools stood in for the tools prompt; the reviewer prompt may still follow.
926
+ phase: state.nonInteractive ? 'checking-registry' : phaseAfterMetadata(updatedTarget),
927
+ target: updatedTarget,
928
+ };
929
+ }
930
+ case 'RESOLVED_UPDATE':
931
+ return {
932
+ ...state,
933
+ phase: 'detecting',
934
+ resolvedPath: action.resolvedPath,
935
+ };
936
+ case 'SET_BUMP_SELECTION':
937
+ return { ...state, bumpSelection: action.value };
938
+ case 'SET_CONFIRM_VALUE':
939
+ return { ...state, confirmValue: action.value };
940
+ case 'SET_DESCRIPTION':
941
+ return { ...state, descriptionValue: action.value };
942
+ case 'SET_PROMPT_TAGS':
943
+ return { ...state, promptTagsValue: action.value };
944
+ case 'SET_PUBLISH_CONFIRM_VALUE':
945
+ return { ...state, publishConfirmValue: action.value };
946
+ case 'SET_REVIEWER':
947
+ return { ...state, reviewerValue: action.value };
948
+ case 'SET_TAGS':
949
+ return { ...state, tagsValue: action.value };
950
+ case 'SET_WITH_CONFIRM_VALUE':
951
+ return { ...state, withConfirmValue: action.value };
952
+ case 'STAGING_DONE':
953
+ return {
954
+ ...state,
955
+ phase: 'validating',
956
+ target: action.target,
957
+ };
958
+ case 'SUBMIT_BUMP': {
959
+ const trimmed = state.bumpSelection.trim().toLowerCase();
960
+ if (trimmed !== 'patch' && trimmed !== 'minor' && trimmed !== 'major') {
961
+ return state; // Ignore invalid input
962
+ }
963
+ // The prompt is for an entry: bump it, then on to the next pending bump or the plan.
964
+ if (state.bumpEntry !== undefined) {
965
+ const entry = state.entries[state.bumpEntry];
966
+ if (entry?.versionCheck?.status !== 'needs-bump')
967
+ return state;
968
+ return bumpEntryAt({ ...state, bumpSelection: '' }, state.bumpEntry, trimmed);
969
+ }
970
+ if (!state.target || !state.versionCheck || state.versionCheck.status !== 'needs-bump') {
971
+ return state;
972
+ }
973
+ // Compute the bumped version in the reducer (pure, synchronous)
974
+ const bumpType = trimmed;
975
+ try {
976
+ const newVersion = bumpVersion(state.versionCheck.latestVersion, bumpType);
977
+ // The bump is only on the target until the publish succeeds, which writes it to
978
+ // the manifest; entries still to check or bump come first.
979
+ return advanceToPlan({
980
+ ...state,
981
+ bumpSelection: '',
982
+ resolvedVersion: newVersion,
983
+ target: { ...state.target, data: { ...state.target.data, version: newVersion } },
984
+ });
985
+ }
986
+ catch {
987
+ return {
988
+ ...state,
989
+ errorMessage: 'Failed to compute bumped version.',
990
+ phase: 'error',
991
+ };
992
+ }
993
+ }
994
+ case 'SUBMIT_CONFIRM': {
995
+ const trimmed = state.confirmValue.trim().toLowerCase();
996
+ if (trimmed === 'y' || trimmed === 'yes') {
997
+ return advanceToPlan(state);
998
+ }
999
+ else if (trimmed === 'n' || trimmed === 'no') {
1000
+ return { ...state, errorMessage: 'Publish cancelled.', phase: 'error' };
1001
+ }
1002
+ // Otherwise ignore (wait for valid input)
1003
+ return state;
1004
+ }
1005
+ case 'SUBMIT_DESCRIPTION': {
1006
+ const trimmed = state.descriptionValue.trim();
1007
+ if (!trimmed)
1008
+ return state; // Don't allow empty description
1009
+ return {
1010
+ ...state,
1011
+ descriptionValue: trimmed,
1012
+ metadataSubPhase: metadataAfterDescription(state),
1013
+ };
1014
+ }
1015
+ case 'SUBMIT_PROMPT_TAGS': {
1016
+ const parsedTags = splitList(state.promptTagsValue);
1017
+ // Update the target's data with the new tags
1018
+ const updatedTagsTarget = state.target
1019
+ ? {
1020
+ ...state.target,
1021
+ data: { ...state.target.data, tags: parsedTags.length > 0 ? parsedTags : undefined },
1022
+ }
1023
+ : undefined;
1024
+ // Check if tools are also missing (only for assets)
1025
+ const needsToolsPrompt = updatedTagsTarget?.type === 'asset' &&
1026
+ (!('tools' in updatedTagsTarget.data) ||
1027
+ !Array.isArray(updatedTagsTarget.data.tools) ||
1028
+ (updatedTagsTarget.data.tools?.length ?? 0) === 0);
1029
+ const nextTagsPhase = needsToolsPrompt ? 'loading-tools' : phaseAfterMetadata(updatedTagsTarget);
1030
+ return {
1031
+ ...state,
1032
+ phase: nextTagsPhase,
1033
+ target: updatedTagsTarget,
1034
+ };
1035
+ }
1036
+ case 'SUBMIT_PUBLISH_CONFIRM': {
1037
+ const trimmed = state.publishConfirmValue.trim().toLowerCase();
1038
+ if (trimmed === 'y' || trimmed === 'yes') {
1039
+ return { ...state, phase: 'publishing' };
1040
+ }
1041
+ else if (trimmed === 'n' || trimmed === 'no') {
1042
+ return { ...state, errorMessage: 'Publish cancelled.', phase: 'error' };
1043
+ }
1044
+ // Otherwise ignore (wait for valid input)
1045
+ return state;
1046
+ }
1047
+ case 'SUBMIT_REVIEWER':
1048
+ return { ...state, phase: 'checking-registry' };
1049
+ case 'SUBMIT_TAGS':
1050
+ return {
1051
+ ...state,
1052
+ metadataSubPhase: 'building-staging',
1053
+ };
1054
+ case 'SUBMIT_WITH_CONFIRM': {
1055
+ const trimmed = state.withConfirmValue.trim().toLowerCase();
1056
+ if (trimmed === 'y' || trimmed === 'yes') {
1057
+ return advanceToPlan({
1058
+ ...state,
1059
+ entries: [...state.entries, ...state.suggestedEntries],
1060
+ missingDependencies: [],
1061
+ pendingError: undefined,
1062
+ suggestedEntries: [],
1063
+ withConfirmValue: '',
1064
+ });
1065
+ }
1066
+ if ((trimmed === 'n' || trimmed === 'no') && state.pendingError) {
1067
+ return publishReducer({ ...state, suggestedEntries: [], withConfirmValue: '' }, state.pendingError);
1068
+ }
1069
+ return state;
1070
+ }
1071
+ case 'SUGGEST_ENTRIES':
1072
+ return {
1073
+ ...state,
1074
+ missingDependencies: action.missing,
1075
+ pendingError: action.error,
1076
+ phase: 'confirm-with',
1077
+ suggestedEntries: action.suggested,
1078
+ withConfirmValue: '',
1079
+ };
1080
+ case 'TOOLS_LOADED':
1081
+ return {
1082
+ ...state,
1083
+ phase: 'prompting-tools',
1084
+ toolAdapters: action.adapters,
1085
+ };
1086
+ case 'VALIDATE_DONE': {
1087
+ if (!action.validation.valid) {
1088
+ return {
1089
+ ...state,
1090
+ errorMessage: `Validation failed:\n${action.validation.errors.map((e) => ` - ${e}`).join('\n')}`,
1091
+ phase: 'error',
1092
+ validation: action.validation,
1093
+ };
1094
+ }
1095
+ // Seed the reviewers field: the --reviewer flag wins over the project's
1096
+ // lockfile-configured default (org.ts's CLI-flag > lockfile priority chain).
1097
+ const reviewerValue = state.flags.reviewerFlag ?? (action.lockfileOrgReviewers ?? []).join(', ');
1098
+ // Determine next phase based on missing tags/tools. A batch asks for neither: as for
1099
+ // a --with entry, a missing one is only a warning.
1100
+ const batch = isBatch(state.flags);
1101
+ const manifest = state.target?.data;
1102
+ const hasTags = batch || (manifest && 'tags' in manifest && Array.isArray(manifest.tags) && manifest.tags.length > 0);
1103
+ const hasTools = batch ||
1104
+ // Only assets have tools; bundles skip the tools prompt
1105
+ state.target?.type !== 'asset' ||
1106
+ (manifest && 'tools' in manifest && Array.isArray(manifest.tools) && manifest.tools.length > 0);
1107
+ const isOrgScoped = batch
1108
+ ? [state.target, ...state.entries.map((entry) => entry.target)].some((t) => t && isOrgScopedTarget(t))
1109
+ : Boolean(state.target && isOrgScopedTarget(state.target));
1110
+ // Reviewers only apply to org-scoped assets (non-org PRs always go to the registry
1111
+ // maintainer) — fail fast rather than silently ignoring the flag.
1112
+ if (!isOrgScoped && state.flags.reviewerFlag && parseReviewersInput(state.flags.reviewerFlag).length > 0) {
1113
+ return {
1114
+ ...state,
1115
+ errorMessage: 'The --reviewer flag only applies to org-scoped assets. Remove the flag to publish this asset.',
1116
+ phase: 'error',
1117
+ validation: action.validation,
1118
+ };
1119
+ }
1120
+ // A manifest without tags takes them from --tags right here, in any run: a non-interactive
1121
+ // one without the flag publishes none, an interactive one asks. Manifest tags always win.
1122
+ const tagsFromFlag = !hasTags && (state.nonInteractive || state.flags.tagsFlag !== undefined);
1123
+ let nextTarget = state.target;
1124
+ if (tagsFromFlag && nextTarget) {
1125
+ const flagTags = splitList(state.flags.tagsFlag);
1126
+ nextTarget = {
1127
+ ...nextTarget,
1128
+ data: { ...nextTarget.data, tags: flagTags.length > 0 ? flagTags : undefined },
1129
+ };
1130
+ }
1131
+ // Non-interactive path: either route to loading-tools (effect resolves tools async) or
1132
+ // directly to checking-registry. The reviewer prompt never applies here — non-interactive
1133
+ // mode always skips it.
1134
+ if (state.nonInteractive) {
1135
+ let nextPhase;
1136
+ if (!hasTools) {
1137
+ // The loading-tools effect detects whether we're non-interactive and
1138
+ // either resolves tools from the manifest/flag/inference or errors out.
1139
+ nextPhase = 'loading-tools';
1140
+ }
1141
+ else {
1142
+ nextPhase = 'checking-registry';
1143
+ }
1144
+ return {
1145
+ ...state,
1146
+ phase: nextPhase,
1147
+ reviewerValue,
1148
+ target: nextTarget,
1149
+ validation: action.validation,
1150
+ };
1151
+ }
1152
+ let nextPhase;
1153
+ if (!hasTags && !tagsFromFlag) {
1154
+ nextPhase = 'prompting-tags';
1155
+ }
1156
+ else if (!hasTools) {
1157
+ nextPhase = 'loading-tools';
1158
+ }
1159
+ else {
1160
+ nextPhase = isOrgScoped ? 'prompting-reviewer' : 'checking-registry';
1161
+ }
1162
+ return {
1163
+ ...state,
1164
+ phase: nextPhase,
1165
+ reviewerValue,
1166
+ target: nextTarget,
1167
+ validation: action.validation,
1168
+ };
1169
+ }
1170
+ default:
1171
+ return state;
1172
+ }
1173
+ }
1174
+ /**
1175
+ * The step before `building-plan`, once the root asset's own checks are done:
1176
+ * check the entries not yet checked, then settle each entry that needs a bump
1177
+ * (`--bump`; without it, the bump prompt, or an error when non-interactive), then plan.
1178
+ */
1179
+ function advanceToPlan(state) {
1180
+ if (state.entries.some((entry) => !entry.versionCheck)) {
1181
+ return { ...state, bumpEntry: undefined, phase: 'checking-entries' };
1182
+ }
1183
+ // A batch without --bump names every asset that needs one, not just the first.
1184
+ if (isBatch(state.flags) && state.nonInteractive && !state.flags.bump) {
1185
+ const pending = [
1186
+ ...(state.target && state.versionCheck?.status === 'needs-bump' && !state.resolvedVersion
1187
+ ? [{ label: state.flags.rawPath, latestVersion: state.versionCheck.latestVersion, target: state.target }]
1188
+ : []),
1189
+ ...state.entries.flatMap((entry) => entry.versionCheck?.status === 'needs-bump' && entry.resolvedVersion === undefined
1190
+ ? [{ label: entry.label, latestVersion: entry.versionCheck.latestVersion, target: entry.target }]
1191
+ : []),
1192
+ ];
1193
+ if (pending.length > 0) {
1194
+ const lines = pending.map((p) => ` ${p.label}: ${p.target.assetType}:${p.target.data.name} v${p.target.data.version}, registry has v${p.latestVersion}`);
1195
+ return {
1196
+ ...state,
1197
+ errorMessage: `--bump <patch|minor|major> required: these manifests are not newer than the registry:\n${lines.join('\n')}`,
1198
+ phase: 'error',
1199
+ };
1200
+ }
1201
+ }
1202
+ const index = state.entries.findIndex((entry) => entry.versionCheck?.status === 'needs-bump' && entry.resolvedVersion === undefined);
1203
+ const entry = state.entries[index];
1204
+ if (entry?.versionCheck?.status !== 'needs-bump') {
1205
+ return { ...state, bumpEntry: undefined, phase: 'building-plan' };
1206
+ }
1207
+ if (state.flags.bump) {
1208
+ return bumpEntryAt(state, index, state.flags.bump);
1209
+ }
1210
+ if (!state.nonInteractive) {
1211
+ return { ...state, bumpEntry: index, bumpSelection: '', phase: 'bump-prompt' };
1212
+ }
1213
+ return {
1214
+ ...state,
1215
+ errorMessage: `--bump <patch|minor|major> required: registry has v${entry.versionCheck.latestVersion} of ${entry.target.assetType}:${entry.target.data.name} (from ${entry.label}) but its manifest is not newer`,
1216
+ phase: 'error',
1217
+ };
1218
+ }
1219
+ /**
1220
+ * Bump the entry at `index` from its registry version, then go on toward the
1221
+ * plan; an error state, as for the root, when the version can't be bumped.
1222
+ */
1223
+ function bumpEntryAt(state, index, bump) {
1224
+ const entry = state.entries[index];
1225
+ if (entry?.versionCheck?.status !== 'needs-bump')
1226
+ return state;
1227
+ let version;
1228
+ try {
1229
+ version = bumpVersion(entry.versionCheck.latestVersion, bump);
1230
+ }
1231
+ catch {
1232
+ return { ...state, errorMessage: `Failed to compute bumped version for ${entry.label}.`, phase: 'error' };
1233
+ }
1234
+ return advanceToPlan({ ...state, entries: replaceEntry(state.entries, index, withVersion(entry, version)) });
1235
+ }
1236
+ /** A raw publish's step after its description: the tags prompt, unless --tags answered it. */
1237
+ function metadataAfterDescription(state) {
1238
+ return state.flags.tagsFlag === undefined ? 'tags' : 'building-staging';
1239
+ }
1240
+ /**
1241
+ * Phase to enter once the tags/tools metadata prompts are resolved: org-scoped
1242
+ * targets get the reviewer prompt, everything else proceeds to the registry check.
1243
+ */
1244
+ function phaseAfterMetadata(target) {
1245
+ return target && isOrgScopedTarget(target) ? 'prompting-reviewer' : 'checking-registry';
1246
+ }
1247
+ function replaceEntry(entries, index, entry) {
1248
+ return entries.map((current, i) => (i === index ? entry : current));
1249
+ }
1250
+ /** The entry published at `version`; a successful publish writes it to the entry's manifest. */
1251
+ function withVersion(entry, version) {
1252
+ return {
1253
+ ...entry,
1254
+ resolvedVersion: version,
1255
+ target: { ...entry.target, data: { ...entry.target.data, version } },
1256
+ };
1257
+ }
1258
+ //# sourceMappingURL=publish-flow.js.map