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