@detergent-software/atk 8.9.0-dev.9 → 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 (260) hide show
  1. package/CHANGELOG.md +262 -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 +14 -14
  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/lib/adapter.d.ts +10 -1
  102. package/build/lib/adapter.d.ts.map +1 -1
  103. package/build/lib/adapter.js +23 -0
  104. package/build/lib/adapter.js.map +1 -1
  105. package/build/lib/api-client.d.ts +9 -5
  106. package/build/lib/api-client.d.ts.map +1 -1
  107. package/build/lib/api-client.js +35 -8
  108. package/build/lib/api-client.js.map +1 -1
  109. package/build/lib/auth.d.ts +12 -2
  110. package/build/lib/auth.d.ts.map +1 -1
  111. package/build/lib/auth.js +38 -3
  112. package/build/lib/auth.js.map +1 -1
  113. package/build/lib/checksum.d.ts +10 -0
  114. package/build/lib/checksum.d.ts.map +1 -1
  115. package/build/lib/checksum.js +17 -0
  116. package/build/lib/checksum.js.map +1 -1
  117. package/build/lib/config.d.ts +4 -4
  118. package/build/lib/config.d.ts.map +1 -1
  119. package/build/lib/config.js +31 -8
  120. package/build/lib/config.js.map +1 -1
  121. package/build/lib/diagnostics.d.ts.map +1 -1
  122. package/build/lib/diagnostics.js +10 -7
  123. package/build/lib/diagnostics.js.map +1 -1
  124. package/build/lib/diff-command.js +1 -1
  125. package/build/lib/diff-command.js.map +1 -1
  126. package/build/lib/errors.d.ts +4 -1
  127. package/build/lib/errors.d.ts.map +1 -1
  128. package/build/lib/errors.js.map +1 -1
  129. package/build/lib/format-age.d.ts +3 -0
  130. package/build/lib/format-age.d.ts.map +1 -0
  131. package/build/lib/format-age.js +18 -0
  132. package/build/lib/format-age.js.map +1 -0
  133. package/build/lib/installer.d.ts +6 -4
  134. package/build/lib/installer.d.ts.map +1 -1
  135. package/build/lib/installer.js +53 -23
  136. package/build/lib/installer.js.map +1 -1
  137. package/build/lib/local-edits.d.ts +72 -0
  138. package/build/lib/local-edits.d.ts.map +1 -0
  139. package/build/lib/local-edits.js +145 -0
  140. package/build/lib/local-edits.js.map +1 -0
  141. package/build/lib/lockfile.d.ts +2 -2
  142. package/build/lib/lockfile.d.ts.map +1 -1
  143. package/build/lib/lockfile.js +28 -19
  144. package/build/lib/lockfile.js.map +1 -1
  145. package/build/lib/mover.d.ts.map +1 -1
  146. package/build/lib/mover.js +11 -12
  147. package/build/lib/mover.js.map +1 -1
  148. package/build/lib/multi-tool-install.d.ts +4 -2
  149. package/build/lib/multi-tool-install.d.ts.map +1 -1
  150. package/build/lib/multi-tool-install.js +12 -2
  151. package/build/lib/multi-tool-install.js.map +1 -1
  152. package/build/lib/offline.d.ts +19 -0
  153. package/build/lib/offline.d.ts.map +1 -0
  154. package/build/lib/offline.js +26 -0
  155. package/build/lib/offline.js.map +1 -0
  156. package/build/lib/owned-paths.d.ts +3 -2
  157. package/build/lib/owned-paths.d.ts.map +1 -1
  158. package/build/lib/owned-paths.js +4 -2
  159. package/build/lib/owned-paths.js.map +1 -1
  160. package/build/lib/parse-json.d.ts +12 -0
  161. package/build/lib/parse-json.d.ts.map +1 -1
  162. package/build/lib/parse-json.js +15 -2
  163. package/build/lib/parse-json.js.map +1 -1
  164. package/build/lib/pastel/commander-specs.d.ts +7 -0
  165. package/build/lib/pastel/commander-specs.d.ts.map +1 -0
  166. package/build/lib/pastel/commander-specs.js +50 -0
  167. package/build/lib/pastel/commander-specs.js.map +1 -0
  168. package/build/lib/pastel/generate-arguments.d.ts +3 -3
  169. package/build/lib/pastel/generate-arguments.d.ts.map +1 -1
  170. package/build/lib/pastel/generate-arguments.js +25 -22
  171. package/build/lib/pastel/generate-arguments.js.map +1 -1
  172. package/build/lib/pastel/generate-command.d.ts +7 -5
  173. package/build/lib/pastel/generate-command.d.ts.map +1 -1
  174. package/build/lib/pastel/generate-command.js +32 -67
  175. package/build/lib/pastel/generate-command.js.map +1 -1
  176. package/build/lib/pastel/generate-commands.d.ts +3 -5
  177. package/build/lib/pastel/generate-commands.d.ts.map +1 -1
  178. package/build/lib/pastel/generate-commands.js +6 -10
  179. package/build/lib/pastel/generate-commands.js.map +1 -1
  180. package/build/lib/pastel/generate-options.d.ts +3 -3
  181. package/build/lib/pastel/generate-options.d.ts.map +1 -1
  182. package/build/lib/pastel/generate-options.js +16 -13
  183. package/build/lib/pastel/generate-options.js.map +1 -1
  184. package/build/lib/pastel/index.d.ts +26 -4
  185. package/build/lib/pastel/index.d.ts.map +1 -1
  186. package/build/lib/pastel/index.js +104 -18
  187. package/build/lib/pastel/index.js.map +1 -1
  188. package/build/lib/pastel/internal-types.d.ts +39 -8
  189. package/build/lib/pastel/internal-types.d.ts.map +1 -1
  190. package/build/lib/pastel/manifest.d.ts +8 -0
  191. package/build/lib/pastel/manifest.d.ts.map +1 -0
  192. package/build/lib/pastel/manifest.js +7 -0
  193. package/build/lib/pastel/manifest.js.map +1 -0
  194. package/build/lib/pastel/read-commands.d.ts +7 -2
  195. package/build/lib/pastel/read-commands.d.ts.map +1 -1
  196. package/build/lib/pastel/read-commands.js +51 -30
  197. package/build/lib/pastel/read-commands.js.map +1 -1
  198. package/build/lib/pastel/render-command.d.ts +13 -0
  199. package/build/lib/pastel/render-command.d.ts.map +1 -0
  200. package/build/lib/pastel/render-command.js +56 -0
  201. package/build/lib/pastel/render-command.js.map +1 -0
  202. package/build/lib/process-alive.d.ts +11 -0
  203. package/build/lib/process-alive.d.ts.map +1 -0
  204. package/build/lib/process-alive.js +22 -0
  205. package/build/lib/process-alive.js.map +1 -0
  206. package/build/lib/pruner.d.ts +26 -0
  207. package/build/lib/pruner.d.ts.map +1 -0
  208. package/build/lib/pruner.js +42 -0
  209. package/build/lib/pruner.js.map +1 -0
  210. package/build/lib/publish-flow.d.ts +3 -6
  211. package/build/lib/publish-flow.d.ts.map +1 -1
  212. package/build/lib/publish-flow.js +199 -246
  213. package/build/lib/publish-flow.js.map +1 -1
  214. package/build/lib/publish-run.d.ts +2 -2
  215. package/build/lib/publish-run.js +11 -6
  216. package/build/lib/publish-run.js.map +1 -1
  217. package/build/lib/publisher.d.ts +23 -16
  218. package/build/lib/publisher.d.ts.map +1 -1
  219. package/build/lib/publisher.js +34 -35
  220. package/build/lib/publisher.js.map +1 -1
  221. package/build/lib/registrar.d.ts.map +1 -1
  222. package/build/lib/registrar.js +2 -2
  223. package/build/lib/registrar.js.map +1 -1
  224. package/build/lib/registry.d.ts +18 -7
  225. package/build/lib/registry.d.ts.map +1 -1
  226. package/build/lib/registry.js +113 -32
  227. package/build/lib/registry.js.map +1 -1
  228. package/build/lib/schemas/config.d.ts +3 -1
  229. package/build/lib/schemas/config.d.ts.map +1 -1
  230. package/build/lib/schemas/config.js +1 -1
  231. package/build/lib/schemas/config.js.map +1 -1
  232. package/build/lib/setup.d.ts.map +1 -1
  233. package/build/lib/setup.js +53 -48
  234. package/build/lib/setup.js.map +1 -1
  235. package/build/lib/sync.d.ts +22 -6
  236. package/build/lib/sync.d.ts.map +1 -1
  237. package/build/lib/sync.js +31 -7
  238. package/build/lib/sync.js.map +1 -1
  239. package/build/lib/uninstaller.d.ts.map +1 -1
  240. package/build/lib/uninstaller.js +6 -3
  241. package/build/lib/uninstaller.js.map +1 -1
  242. package/build/lib/update-check.d.ts +4 -0
  243. package/build/lib/update-check.d.ts.map +1 -1
  244. package/build/lib/update-check.js +55 -56
  245. package/build/lib/update-check.js.map +1 -1
  246. package/build/lib/updater.d.ts +31 -1
  247. package/build/lib/updater.d.ts.map +1 -1
  248. package/build/lib/updater.js +143 -4
  249. package/build/lib/updater.js.map +1 -1
  250. package/build/lib/usage-error.d.ts +20 -0
  251. package/build/lib/usage-error.d.ts.map +1 -0
  252. package/build/lib/usage-error.js +41 -0
  253. package/build/lib/usage-error.js.map +1 -0
  254. package/build/lib/user-setup.d.ts.map +1 -1
  255. package/build/lib/user-setup.js +62 -59
  256. package/build/lib/user-setup.js.map +1 -1
  257. package/build/lib/write-file.d.ts.map +1 -1
  258. package/build/lib/write-file.js +63 -11
  259. package/build/lib/write-file.js.map +1 -1
  260. package/package.json +3 -3
@@ -1,17 +1,17 @@
1
1
  import { writeFile } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
+ import { compare as semverCompare, valid as semverValid } from 'semver';
3
4
  import { loadAllAdapters, resolveInstalledPaths } from './adapter.js';
4
5
  import { isApiError } from './api-client.js';
5
6
  import { getCredential } from './auth.js';
6
- import { hashFile } from './checksum.js';
7
7
  import { errorGuidance, errorMessage } from './errors.js';
8
- import { detectCompatibleTools, getSignedInUserName } from './init.js';
9
- import { addAssetToLockfile, findInstalledAsset, readLockfile, withLockfileLock, writeLockfile } from './lockfile.js';
8
+ import { detectCompatibleTools, getEntrypointName, getSignedInUserName } from './init.js';
9
+ import { findInstalledAsset, readLockfile } from './lockfile.js';
10
10
  import { diffManifests, fetchExistingManifest } from './manifest-diff.js';
11
11
  import { parseOrgFromName } from './org.js';
12
12
  import { findProjectRoot } from './paths.js';
13
- import { buildPublishPlan, buildStagingArea, bumpVersion, checkRegistryVersion, detectAssetFromPath, detectPublishType, executePublish, findSiblingDependencies, isOrgScopedTarget, loadPublishBatch, loadPublishEntries, mapPublishError, missingDependencies, resolvePlanDependencies, unknownToolsError, updateManifestField, updateManifestVersion, validatePublishTarget, } from './publisher.js';
14
- import { clearCache, fetchAssetManifest, fetchRegistry, findAsset, findBundle } from './registry.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
15
  import { parseReviewersInput, validateGithubUsername } from './reviewers.js';
16
16
  import { stageInstalledFiles } from './staging.js';
17
17
  import { resolveTools } from './tool-resolver.js';
@@ -162,6 +162,64 @@ async function firstFailure(edits) {
162
162
  }
163
163
  return undefined;
164
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
+ }
165
223
  function stepFor(state) {
166
224
  switch (state.phase) {
167
225
  case 'building-plan':
@@ -174,8 +232,6 @@ function stepFor(state) {
174
232
  return detecting;
175
233
  case 'gathering-metadata':
176
234
  return state.metadataSubPhase === 'building-staging' ? buildingStaging : undefined;
177
- case 'installing':
178
- return installing;
179
235
  case 'loading-tools':
180
236
  return loadingTools;
181
237
  case 'publishing':
@@ -228,10 +284,10 @@ function toErrorAction(err, override) {
228
284
  }
229
285
  /**
230
286
  * Write what the run changed in memory to the manifests on disk: the root's bump and each
231
- * entry's, and the tags and tools the prompts asked for. Only once the publish has opened its
232
- * PR, so a cancelled or failed one leaves every manifest as it was. A file whose write fails
233
- * takes none of its later edits and comes back as one warning naming the fields it lacks; the
234
- * other files are still written.
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.
235
291
  */
236
292
  async function writeManifestEdits(state) {
237
293
  const target = state.target;
@@ -243,19 +299,14 @@ async function writeManifestEdits(state) {
243
299
  files.set(path, [...(files.get(path) ?? []), { field, value, write }]);
244
300
  };
245
301
  const version = (dir, file, value) => edit(dir, file, 'version', value, () => updateManifestVersion(dir, file, value));
246
- const field = (name, value) => edit(target.sourceDir, 'manifest.json', name, value, () => updateManifestField(target.sourceDir, 'manifest.json', name, value));
302
+ const manifestFile = target.type === 'asset' ? 'manifest.json' : 'bundle.json';
247
303
  if (state.versionCheck?.status === 'needs-bump' && state.resolvedVersion) {
248
- version(target.sourceDir, target.type === 'asset' ? 'manifest.json' : 'bundle.json', state.resolvedVersion);
304
+ version(target.sourceDir, manifestFile, state.resolvedVersion);
249
305
  }
250
- // Only an asset's manifest.json takes the prompted tags and tools, as the target (and so the plan) has them.
251
- if (target.type === 'asset') {
252
- const { tags, tools } = target.data;
253
- if (state.promptTagsValue && tags && tags.length > 0) {
254
- field('tags', tags);
255
- }
256
- if (state.selectedPromptTools.length > 0) {
257
- field('tools', tools);
258
- }
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));
259
310
  }
260
311
  for (const entry of state.entries) {
261
312
  if (entry.versionCheck?.status === 'needs-bump' && entry.resolvedVersion) {
@@ -268,7 +319,7 @@ async function writeManifestEdits(state) {
268
319
  if (!failed)
269
320
  continue;
270
321
  const missing = edits.slice(failed.index);
271
- // Only the version matters to the next publish: it asks for missing tags and tools again.
322
+ // Only the version matters to the next publish: it fills missing tags and tools again.
272
323
  const again = missing.some((e) => e.field === 'version') ? ' before you publish it again' : '';
273
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}.`);
274
325
  }
@@ -277,29 +328,45 @@ async function writeManifestEdits(state) {
277
328
  // ---------------------------------------------------------------------------
278
329
  // Steps, one per phase that waits on work
279
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
+ */
280
335
  const resolvingInstalled = {
281
336
  async run(state, ctx) {
282
337
  const source = await findInstalledSource(state, ctx);
283
338
  if (!source)
284
339
  return;
285
340
  const { adapter, installed, projectRoot } = source;
286
- // Copy the installed files into a temp directory, preserving their source paths
287
- const tempDir = await stageInstalledFiles(installed, adapter, projectRoot);
288
- ctx.stage(tempDir);
289
- if (ctx.cancelled())
290
- return;
291
- // Generate manifest.json in the temp dir
292
- const manifest = {
293
- author: 'unknown',
294
- description: `Published from installed asset ${installed.name}`,
295
- entrypoint: installed.files[0]?.sourcePath ?? 'index.md',
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
+ })),
296
358
  name: installed.name,
297
- ...(installed.org ? { org: installed.org } : {}),
298
- type: installed.type,
359
+ sourceDir: projectRoot,
299
360
  version: installed.version,
300
361
  };
301
- await writeFile(join(tempDir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
302
- ctx.dispatch({ resolvedPath: tempDir, type: 'RESOLVED_INSTALLED' });
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 });
303
370
  },
304
371
  };
305
372
  const resolvingUpdate = {
@@ -309,21 +376,35 @@ const resolvingUpdate = {
309
376
  return;
310
377
  const { adapter, installed, org, projectRoot } = source;
311
378
  const assetName = state.flags.rawPath;
312
- // Fetch the registry to find the latest version
379
+ // Fetch the registry to find the latest version. An installed entry is always an asset.
313
380
  const registry = await fetchRegistry({ force: state.flags.refresh });
314
381
  if (ctx.cancelled())
315
382
  return;
316
- // Look up the asset in the registry: try findAsset first, then findBundle
317
383
  const resolvedAsset = findAsset(registry, installed.name, installed.type, undefined, installed.org ?? org);
318
- const resolvedBundle = resolvedAsset ? undefined : findBundle(registry, installed.name, installed.org ?? org);
319
- if (!resolvedAsset && !resolvedBundle) {
384
+ if (!resolvedAsset) {
320
385
  ctx.dispatch({ message: `Asset '${assetName}' not found in the registry.`, type: 'ERROR' });
321
386
  return;
322
387
  }
323
- const latestVersion = resolvedAsset ? resolvedAsset.version : resolvedBundle.version;
324
- const assetType = resolvedAsset ? resolvedAsset.asset.type : 'bundle';
325
- // Fetch the authoritative manifest (and optional README) from the registry
326
- const fetched = await fetchAssetManifest({ name: installed.name, org: installed.org ?? org, type: assetType }, latestVersion);
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
+ ]);
327
408
  if (ctx.cancelled())
328
409
  return;
329
410
  // Copy the user's locally modified files into a temp staging directory
@@ -331,13 +412,9 @@ const resolvingUpdate = {
331
412
  ctx.stage(tempDir);
332
413
  if (ctx.cancelled())
333
414
  return;
334
- // Write the fetched manifest into the staging directory. It is the manifest as this
335
- // CLI's schema parsed it: a field the schema does not know (from a newer publisher)
336
- // is dropped here and so is missing from the re-published version.
337
- await writeFile(join(tempDir, fetched.manifestFileName), fetched.manifestContent, 'utf-8');
338
- // Write README if present
339
- if (fetched.readmeContent) {
340
- await writeFile(join(tempDir, 'README.md'), fetched.readmeContent, 'utf-8');
415
+ await writeFile(join(tempDir, 'manifest.json'), manifest);
416
+ if (readme) {
417
+ await writeFile(join(tempDir, 'README.md'), readme, 'utf-8');
341
418
  }
342
419
  ctx.dispatch({ resolvedPath: tempDir, type: 'RESOLVED_UPDATE' });
343
420
  },
@@ -388,66 +465,11 @@ const detecting = {
388
465
  const detection = await detectAssetFromPath(state.resolvedPath, adapter, projectRoot);
389
466
  if (ctx.cancelled())
390
467
  return;
391
- // Pre-fill metadata
392
- const author = (await getSignedInUserName()) ?? '';
393
- if (ctx.cancelled())
394
- return;
468
+ // No manifest exists yet, so the project's org and the flags and prompts give the metadata.
395
469
  const lockfile = await readLockfile(projectRoot);
396
470
  if (ctx.cancelled())
397
471
  return;
398
- const tools = await detectCompatibleTools(detection.assetType);
399
- if (ctx.cancelled())
400
- return;
401
- // Non-interactive mode (raw publish — no manifest exists yet, so the only
402
- // sources for description are --description flag or exit-1; tags come from
403
- // --tags or default to empty). Tool detection runs for both modes above.
404
- if (!state.nonInteractive) {
405
- // Interactive mode — gather metadata from the user
406
- ctx.dispatch({
407
- authorValue: author,
408
- orgValue: lockfile.org ?? '',
409
- rawDetection: detection,
410
- toolsDetected: tools,
411
- type: 'RAW_DETECT_DONE',
412
- });
413
- return;
414
- }
415
- if (!state.flags.descriptionFlag) {
416
- ctx.dispatch({
417
- message: 'description required: provide --description or include in manifest.json',
418
- type: 'ERROR',
419
- });
420
- return;
421
- }
422
- // Tools resolution: --tools flag -> detected (already populated above)
423
- let toolsList;
424
- if (state.flags.toolsFlag) {
425
- toolsList = splitList(state.flags.toolsFlag);
426
- }
427
- else if (tools.length > 0) {
428
- toolsList = tools;
429
- }
430
- else {
431
- ctx.dispatch({
432
- message: `--tools <comma-separated> required: no tools detected for type ${detection.assetType}`,
433
- type: 'ERROR',
434
- });
435
- return;
436
- }
437
- const stagingDir = await buildStagingArea(detection, {
438
- author,
439
- description: state.flags.descriptionFlag,
440
- ...(lockfile.org ? { org: lockfile.org } : {}),
441
- tags: splitList(state.flags.tagsFlag),
442
- tools: toolsList,
443
- });
444
- ctx.stage(stagingDir);
445
- if (ctx.cancelled())
446
- return;
447
- const detected = await detectPublishType(stagingDir);
448
- if (ctx.cancelled())
449
- return;
450
- ctx.dispatch({ rawDetection: detection, target: detected, type: 'RAW_DETECT_DONE_NON_INTERACTIVE' });
472
+ await gatherRawMetadata(state, ctx, detection, { org: lockfile.org });
451
473
  },
452
474
  };
453
475
  /** The `building-staging` sub-phase of `gathering-metadata`: the manifest from the prompts' answers. */
@@ -528,14 +550,13 @@ const validating = {
528
550
  },
529
551
  };
530
552
  /**
531
- * In interactive mode, loads adapters for the multi-select prompt. In non-interactive
532
- * mode, resolves tools via manifest -> --tools flag -> detectCompatibleTools(assetType)
533
- * -> exit 1, then dispatches RESOLVE_TOOLS_NONINTERACTIVE to advance directly to
534
- * checking-registry.
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.
535
556
  */
536
557
  const loadingTools = {
537
558
  async run(state, ctx) {
538
- if (!state.nonInteractive) {
559
+ if (!state.nonInteractive && !state.flags.toolsFlag) {
539
560
  const adapters = await loadAllAdapters();
540
561
  if (ctx.cancelled())
541
562
  return;
@@ -689,8 +710,8 @@ const buildingPlan = {
689
710
  const localPlan = await buildPublishPlan(currentTarget, currentVersion, state.isUpdate, state.flags.message, reviewers, entries, ...(isBatch(state.flags) ? [state.flags.rawPath] : []));
690
711
  if (ctx.cancelled())
691
712
  return;
692
- // Resolve declared dependencies against the registry now, so the preview and
693
- // --dry-run show them and an unresolvable one stops before the confirm prompt.
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.
694
715
  const publishPlan = await resolvePlanDependencies(localPlan);
695
716
  if (ctx.cancelled())
696
717
  return;
@@ -727,77 +748,14 @@ const publishing = {
727
748
  catch {
728
749
  // Best-effort — don't fail publish over cache
729
750
  }
730
- // Update lockfile in-place when publishing from an installed asset
731
- if (state.flags.fromInstalled || state.flags.update) {
732
- try {
733
- const projectRoot = findProjectRoot(state.flags.projectRoot);
734
- const pubTools = await resolveTools(state.flags.tool);
735
- const pubAdapter = pubTools[0].adapter;
736
- await withLockfileLock(projectRoot, async () => {
737
- const lockfile = await readLockfile(projectRoot);
738
- const { name, org } = parseOrgFromName(state.flags.rawPath);
739
- const installed = findInstalledAsset(lockfile, name, undefined, org);
740
- if (installed) {
741
- const resolved = resolveInstalledPaths(installed, pubAdapter, projectRoot);
742
- const updatedFiles = await Promise.all(resolved.files.map(async (file) => ({
743
- checksum: await hashFile(join(projectRoot, file.installedPath)),
744
- sourcePath: file.sourcePath,
745
- })));
746
- const updatedLockfile = addAssetToLockfile(lockfile, {
747
- ...installed,
748
- files: updatedFiles,
749
- version: state.resolvedVersion,
750
- });
751
- await writeLockfile(projectRoot, updatedLockfile);
752
- }
753
- });
754
- }
755
- catch {
756
- // Best-effort — don't fail publish over lockfile update
757
- }
758
- }
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.
759
754
  if (ctx.cancelled())
760
755
  return;
761
756
  ctx.dispatch({ result, type: 'PUBLISH_DONE' });
762
757
  },
763
758
  };
764
- /** A raw publish records the asset it published in the lockfile, as installed from the project's own files. */
765
- const installing = {
766
- async run(state, ctx) {
767
- try {
768
- const projectRoot = findProjectRoot(state.flags.projectRoot);
769
- await withLockfileLock(projectRoot, async () => {
770
- // Read current lockfile
771
- const lockfile = await readLockfile(projectRoot);
772
- // Compute checksums from actual project files (not staging copies)
773
- const files = [];
774
- for (const file of state.rawDetection.files) {
775
- const checksum = await hashFile(file.absolutePath);
776
- files.push({
777
- checksum,
778
- sourcePath: file.relativePath,
779
- });
780
- }
781
- // Build InstalledAsset entry
782
- const installedAsset = {
783
- files,
784
- installReason: 'direct',
785
- name: state.rawDetection.name,
786
- org: state.orgValue || undefined,
787
- type: state.rawDetection.assetType,
788
- version: state.resolvedVersion || state.target?.data.version || '1.0.0',
789
- };
790
- // Add to lockfile and write
791
- const updatedLockfile = addAssetToLockfile(lockfile, installedAsset);
792
- await writeLockfile(projectRoot, updatedLockfile);
793
- });
794
- }
795
- catch {
796
- // Publish already succeeded, so just warn and move to done
797
- }
798
- ctx.dispatch({ type: 'INSTALL_DONE' });
799
- },
800
- };
801
759
  // ---------------------------------------------------------------------------
802
760
  // Reducer
803
761
  // ---------------------------------------------------------------------------
@@ -855,8 +813,6 @@ export function publishReducer(state, action) {
855
813
  errorMessage: action.message,
856
814
  phase: 'error',
857
815
  };
858
- case 'INSTALL_DONE':
859
- return { ...state, phase: 'done' };
860
816
  case 'PLAN_DONE': {
861
817
  // dry-run always shows the dry-run result; --yes / non-TTY skip the confirm prompt
862
818
  // and proceed directly to publishing.
@@ -877,30 +833,28 @@ export function publishReducer(state, action) {
877
833
  plan: action.plan,
878
834
  };
879
835
  }
880
- case 'PUBLISH_DONE': {
881
- const nextPhase = state.isRawPublish ? 'installing' : 'done';
882
- return {
883
- ...state,
884
- phase: nextPhase,
885
- publishResult: action.result,
886
- };
887
- }
836
+ case 'PUBLISH_DONE':
837
+ return { ...state, phase: 'done', publishResult: action.result };
888
838
  case 'PUBLISH_PROGRESS':
889
839
  return { ...state, progressMessage: action.message };
890
840
  case 'RAW_DETECT_DONE':
841
+ // Ask only for what neither a flag nor the installed asset gave.
891
842
  return {
892
843
  ...state,
893
844
  authorValue: action.authorValue,
894
- isRawPublish: true,
845
+ descriptionValue: action.description ?? '',
846
+ isRawPublish: !state.flags.fromInstalled,
847
+ metadataSubPhase: action.description === undefined ? 'description' : metadataAfterDescription(state),
895
848
  orgValue: action.orgValue,
896
849
  phase: 'gathering-metadata',
897
850
  rawDetection: action.rawDetection,
851
+ tagsValue: state.flags.tagsFlag ?? '',
898
852
  toolsDetected: action.toolsDetected,
899
853
  };
900
854
  case 'RAW_DETECT_DONE_NON_INTERACTIVE':
901
855
  return {
902
856
  ...state,
903
- isRawPublish: true,
857
+ isRawPublish: !state.flags.fromInstalled,
904
858
  phase: 'validating',
905
859
  rawDetection: action.rawDetection,
906
860
  target: action.target,
@@ -920,34 +874,32 @@ export function publishReducer(state, action) {
920
874
  }
921
875
  else {
922
876
  // needs-bump
923
- if (state.nonInteractive) {
924
- if (state.flags.bump && state.target) {
925
- // Auto-bump using the flag value; a successful publish writes it to the manifest
926
- try {
927
- bumpedVersion = bumpVersion(versionCheck.latestVersion, state.flags.bump);
928
- bumpedTarget = {
929
- ...state.target,
930
- data: { ...state.target.data, version: bumpedVersion },
931
- };
932
- nextPhase = 'building-plan';
933
- }
934
- catch {
935
- errorMessage = 'Failed to compute bumped version.';
936
- nextPhase = 'error';
937
- }
938
- }
939
- else if (isBatch(state.flags)) {
940
- // advanceToPlan lists every asset of the batch that needs --bump, once the entries are checked.
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
+ };
941
885
  nextPhase = 'building-plan';
942
886
  }
943
- else {
944
- errorMessage = `--bump <patch|minor|major> required: registry has v${versionCheck.latestVersion} but manifest is not newer`;
887
+ catch {
888
+ errorMessage = 'Failed to compute bumped version.';
945
889
  nextPhase = 'error';
946
890
  }
947
891
  }
948
- else {
892
+ else if (!state.nonInteractive) {
949
893
  nextPhase = 'bump-prompt';
950
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
+ }
951
903
  }
952
904
  const checked = {
953
905
  ...state,
@@ -970,16 +922,11 @@ export function publishReducer(state, action) {
970
922
  : undefined;
971
923
  return {
972
924
  ...state,
973
- phase: 'checking-registry',
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),
974
927
  target: updatedTarget,
975
928
  };
976
929
  }
977
- case 'RESOLVED_INSTALLED':
978
- return {
979
- ...state,
980
- phase: 'detecting',
981
- resolvedPath: action.resolvedPath,
982
- };
983
930
  case 'RESOLVED_UPDATE':
984
931
  return {
985
932
  ...state,
@@ -1062,7 +1009,7 @@ export function publishReducer(state, action) {
1062
1009
  return {
1063
1010
  ...state,
1064
1011
  descriptionValue: trimmed,
1065
- metadataSubPhase: 'tags',
1012
+ metadataSubPhase: metadataAfterDescription(state),
1066
1013
  };
1067
1014
  }
1068
1015
  case 'SUBMIT_PROMPT_TAGS': {
@@ -1170,19 +1117,21 @@ export function publishReducer(state, action) {
1170
1117
  validation: action.validation,
1171
1118
  };
1172
1119
  }
1173
- // Non-interactive path: resolve tags from --tags flag (or empty) right here, then
1174
- // either route to loading-tools (effect resolves tools async) or directly to
1175
- // checking-registry. Manifest tags always win when present. The reviewer prompt
1176
- // never applies here — non-interactive mode always skips it.
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.
1177
1134
  if (state.nonInteractive) {
1178
- let nextTarget = state.target;
1179
- if (!hasTags && nextTarget) {
1180
- const flagTags = splitList(state.flags.tagsFlag);
1181
- nextTarget = {
1182
- ...nextTarget,
1183
- data: { ...nextTarget.data, tags: flagTags.length > 0 ? flagTags : undefined },
1184
- };
1185
- }
1186
1135
  let nextPhase;
1187
1136
  if (!hasTools) {
1188
1137
  // The loading-tools effect detects whether we're non-interactive and
@@ -1201,7 +1150,7 @@ export function publishReducer(state, action) {
1201
1150
  };
1202
1151
  }
1203
1152
  let nextPhase;
1204
- if (!hasTags) {
1153
+ if (!hasTags && !tagsFromFlag) {
1205
1154
  nextPhase = 'prompting-tags';
1206
1155
  }
1207
1156
  else if (!hasTools) {
@@ -1214,6 +1163,7 @@ export function publishReducer(state, action) {
1214
1163
  ...state,
1215
1164
  phase: nextPhase,
1216
1165
  reviewerValue,
1166
+ target: nextTarget,
1217
1167
  validation: action.validation,
1218
1168
  };
1219
1169
  }
@@ -1224,7 +1174,7 @@ export function publishReducer(state, action) {
1224
1174
  /**
1225
1175
  * The step before `building-plan`, once the root asset's own checks are done:
1226
1176
  * check the entries not yet checked, then settle each entry that needs a bump
1227
- * (the bump prompt; `--bump` or an error when non-interactive), then plan.
1177
+ * (`--bump`; without it, the bump prompt, or an error when non-interactive), then plan.
1228
1178
  */
1229
1179
  function advanceToPlan(state) {
1230
1180
  if (state.entries.some((entry) => !entry.versionCheck)) {
@@ -1254,18 +1204,17 @@ function advanceToPlan(state) {
1254
1204
  if (entry?.versionCheck?.status !== 'needs-bump') {
1255
1205
  return { ...state, bumpEntry: undefined, phase: 'building-plan' };
1256
1206
  }
1207
+ if (state.flags.bump) {
1208
+ return bumpEntryAt(state, index, state.flags.bump);
1209
+ }
1257
1210
  if (!state.nonInteractive) {
1258
1211
  return { ...state, bumpEntry: index, bumpSelection: '', phase: 'bump-prompt' };
1259
1212
  }
1260
- const { latestVersion } = entry.versionCheck;
1261
- if (!state.flags.bump) {
1262
- return {
1263
- ...state,
1264
- errorMessage: `--bump <patch|minor|major> required: registry has v${latestVersion} of ${entry.target.assetType}:${entry.target.data.name} (from ${entry.label}) but its manifest is not newer`,
1265
- phase: 'error',
1266
- };
1267
- }
1268
- return bumpEntryAt(state, index, state.flags.bump);
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
+ };
1269
1218
  }
1270
1219
  /**
1271
1220
  * Bump the entry at `index` from its registry version, then go on toward the
@@ -1284,6 +1233,10 @@ function bumpEntryAt(state, index, bump) {
1284
1233
  }
1285
1234
  return advanceToPlan({ ...state, entries: replaceEntry(state.entries, index, withVersion(entry, version)) });
1286
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
+ }
1287
1240
  /**
1288
1241
  * Phase to enter once the tags/tools metadata prompts are resolved: org-scoped
1289
1242
  * targets get the reviewer prompt, everything else proceeds to the registry check.