@lesliechan721/aw 0.1.0-beta.12 → 0.1.0-beta.14
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/dist/src/commands/catalog-dev-source.d.ts +31 -0
- package/dist/src/commands/catalog-dev-source.js +165 -5
- package/dist/src/commands/catalog-dev-source.js.map +1 -1
- package/dist/src/commands/catalog-generation.d.ts +5 -0
- package/dist/src/commands/catalog-generation.js.map +1 -1
- package/dist/src/commands/catalog-publication-journal.d.ts +12 -0
- package/dist/src/commands/catalog-publication-journal.js +77 -0
- package/dist/src/commands/catalog-publication-journal.js.map +1 -1
- package/dist/src/commands/catalog-store.d.ts +5 -0
- package/dist/src/commands/catalog-store.js +19 -1
- package/dist/src/commands/catalog-store.js.map +1 -1
- package/dist/src/commands/dependency-operation-plan.d.ts +7 -0
- package/dist/src/commands/dependency-operation-plan.js +63 -27
- package/dist/src/commands/dependency-operation-plan.js.map +1 -1
- package/dist/src/commands/rebuild-command.d.ts +22 -8
- package/dist/src/commands/rebuild-command.js +562 -250
- package/dist/src/commands/rebuild-command.js.map +1 -1
- package/dist/src/commands/rebuild-integration.js +188 -67
- package/dist/src/commands/rebuild-integration.js.map +1 -1
- package/dist/src/commands/reconcile-operation-plan.d.ts +11 -9
- package/dist/src/commands/reconcile-operation-plan.js +25 -6
- package/dist/src/commands/reconcile-operation-plan.js.map +1 -1
- package/dist/src/commands/status-v6.js +2 -2
- package/dist/src/commands/status-v6.js.map +1 -1
- package/dist/src/commands/write-commands.d.ts +2 -1
- package/dist/src/commands/write-commands.js +24 -1
- package/dist/src/commands/write-commands.js.map +1 -1
- package/dist/src/tui/catalog-management-screens.d.ts +45 -0
- package/dist/src/tui/catalog-management-screens.js +25 -0
- package/dist/src/tui/catalog-management-screens.js.map +1 -0
- package/dist/src/tui/catalog-management.d.ts +240 -0
- package/dist/src/tui/catalog-management.js +856 -0
- package/dist/src/tui/catalog-management.js.map +1 -0
- package/dist/src/tui/catalog-sources/session.d.ts +5 -0
- package/dist/src/tui/catalog-sources/session.js +49 -1
- package/dist/src/tui/catalog-sources/session.js.map +1 -1
- package/dist/src/tui/catalog-sources/types.d.ts +11 -0
- package/dist/src/tui/console-app-v4.d.ts +1 -1
- package/dist/src/tui/console-app-v4.js +2184 -564
- package/dist/src/tui/console-app-v4.js.map +1 -1
- package/dist/src/tui/controller-runtime.js +609 -13
- package/dist/src/tui/controller-runtime.js.map +1 -1
- package/dist/src/tui/controller.d.ts +121 -6
- package/dist/src/tui/controller.js +667 -17
- package/dist/src/tui/controller.js.map +1 -1
- package/dist/src/tui/dependency-graph.d.ts +67 -0
- package/dist/src/tui/dependency-graph.js +399 -0
- package/dist/src/tui/dependency-graph.js.map +1 -0
- package/dist/src/tui/lifecycle-action-view.js +4 -2
- package/dist/src/tui/lifecycle-action-view.js.map +1 -1
- package/dist/src/tui/model.d.ts +37 -0
- package/dist/src/tui/model.js +179 -17
- package/dist/src/tui/model.js.map +1 -1
- package/dist/src/tui/operation-review-planner.js +244 -8
- package/dist/src/tui/operation-review-planner.js.map +1 -1
- package/dist/src/tui/plugin-write-task.d.ts +66 -0
- package/dist/src/tui/plugin-write-task.js +265 -0
- package/dist/src/tui/plugin-write-task.js.map +1 -0
- package/dist/src/tui/router.d.ts +38 -2
- package/dist/src/tui/router.js.map +1 -1
- package/dist/src/tui/screen-registry.d.ts +105 -6
- package/dist/src/tui/screen-registry.js +31 -4
- package/dist/src/tui/screen-registry.js.map +1 -1
- package/dist/src/utils/catalog-generation-service.d.ts +4 -0
- package/dist/src/utils/catalog-generation-service.js.map +1 -1
- package/dist/src/utils/codex-shared-native.d.ts +4 -0
- package/dist/src/utils/codex-shared-native.js +13 -0
- package/dist/src/utils/codex-shared-native.js.map +1 -1
- package/dist/src/utils/local-marketplace.d.ts +14 -1
- package/dist/src/utils/local-marketplace.js +242 -14
- package/dist/src/utils/local-marketplace.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { assertCatalogVersion, calculateCatalogWorkspaceSourceIdentity, CATALOG_WORKSPACE_MARKER_FILENAME, DEFAULT_CATALOG_OUTPUT_PATH, readCatalogWorkspace, } from '@lesliechan721/aw-plugin-core';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { runCatalogBuildCommand, runCatalogInitCommand, runCatalogValidateCommand, runCatalogVersionCommand, } from '../authoring/workspace-commands.js';
|
|
6
|
+
import { activateCatalogDevSource, deactivateCatalogDevSource, getCatalogDevSourceStatus, preflightCatalogDevSourceActivation, } from '../commands/catalog-dev-source.js';
|
|
7
|
+
import { preflightCatalogSourceMutation } from '../commands/catalog-source.js';
|
|
8
|
+
import { applyCatalogPublicationPlan, prepareCatalogPublicationPlan, safeRemoteIdentity, } from '../commands/catalog-publish.js';
|
|
9
|
+
import { discoverCatalogPublicationJournals, findCatalogPublicationJournalByPlanDigest, } from '../commands/catalog-publication-journal.js';
|
|
10
|
+
import { inspectCatalogPublicationApplyContext } from '../commands/catalog-publication-inspection.js';
|
|
11
|
+
import { catalogStorePrune, catalogStoreStatus, } from '../commands/catalog-store.js';
|
|
12
|
+
import { getCatalogStatus } from '../commands/catalog-status.js';
|
|
13
|
+
import { resolveCatalogPublicationTarget } from '../commands/catalog-publication-target.js';
|
|
14
|
+
import { resolveCatalogWorkspaceRoot, } from '../commands/catalog-workspace-resolution.js';
|
|
15
|
+
import { readCliPackageInfo } from '../utils/local-marketplace.js';
|
|
16
|
+
import { CLI_RESERVED_IMPORTS } from '../utils/manifest.js';
|
|
17
|
+
import { CATALOG_SOURCE_ID_PATTERN, isDefaultOfficialCatalogSource, readCatalogSourcesConfigSnapshot, } from '../utils/catalog-sources.js';
|
|
18
|
+
import { sanitizeDiagnosticText } from './diagnostics.js';
|
|
19
|
+
export class CatalogManagementError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = 'CatalogManagementError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function digest(value) {
|
|
28
|
+
return `sha256:${crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex')}`;
|
|
29
|
+
}
|
|
30
|
+
function storeItemKey(item) {
|
|
31
|
+
return digest([item.category, item.storeKey]).slice('sha256:'.length, 'sha256:'.length + 16);
|
|
32
|
+
}
|
|
33
|
+
function safeIssue(issue, sensitiveRoots, sensitiveValues = []) {
|
|
34
|
+
return {
|
|
35
|
+
code: typeof issue.code === 'string' && /^[A-Za-z][A-Za-z0-9._-]{0,79}$/u.test(issue.code)
|
|
36
|
+
? issue.code
|
|
37
|
+
: 'catalogManagementFailed',
|
|
38
|
+
message: sanitizeDiagnosticText(issue.message, sensitiveRoots, sensitiveValues),
|
|
39
|
+
blocking: issue.blocking !== false,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function thrownIssue(error, sensitiveRoots, sensitiveValues = []) {
|
|
43
|
+
const candidate = error;
|
|
44
|
+
return safeIssue({
|
|
45
|
+
code: candidate?.code,
|
|
46
|
+
message: typeof candidate?.message === 'string' ? candidate.message : String(error),
|
|
47
|
+
}, sensitiveRoots, sensitiveValues);
|
|
48
|
+
}
|
|
49
|
+
function safeWorkspaceLabel(workspaceRoot) {
|
|
50
|
+
return path.basename(workspaceRoot) || 'Catalog Workspace';
|
|
51
|
+
}
|
|
52
|
+
function safeRemoteLabel(remote, remoteUrl) {
|
|
53
|
+
if (remoteUrl !== undefined)
|
|
54
|
+
return sanitizeDiagnosticText(safeRemoteIdentity(remoteUrl));
|
|
55
|
+
return remote === undefined ? null : sanitizeDiagnosticText(remote);
|
|
56
|
+
}
|
|
57
|
+
function intentSensitiveValues(intent) {
|
|
58
|
+
return [
|
|
59
|
+
'ownerName' in intent ? intent.ownerName : undefined,
|
|
60
|
+
'ownerUrl' in intent ? intent.ownerUrl : undefined,
|
|
61
|
+
'displayName' in intent ? intent.displayName : undefined,
|
|
62
|
+
'remoteUrl' in intent ? intent.remoteUrl : undefined,
|
|
63
|
+
].filter((value) => value !== undefined && value !== '');
|
|
64
|
+
}
|
|
65
|
+
function projectPublicationRecord(entry, sensitiveRoot) {
|
|
66
|
+
if (entry.state === 'damaged') {
|
|
67
|
+
return {
|
|
68
|
+
catalogVersion: entry.catalogVersion,
|
|
69
|
+
state: 'damaged',
|
|
70
|
+
channel: null,
|
|
71
|
+
pluginCount: null,
|
|
72
|
+
planDigest: null,
|
|
73
|
+
remote: null,
|
|
74
|
+
branch: null,
|
|
75
|
+
canApply: false,
|
|
76
|
+
requiresRemoteInput: false,
|
|
77
|
+
mustIncreaseVersion: true,
|
|
78
|
+
localRepositoryState: 'absent',
|
|
79
|
+
issue: safeIssue({
|
|
80
|
+
code: 'catalogPublicationStateDamaged',
|
|
81
|
+
message: `Catalog ${entry.catalogVersion} publication state is damaged. Increase the Catalog version.`,
|
|
82
|
+
}, [sensitiveRoot]),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
catalogVersion: entry.journal.display.catalogVersion,
|
|
87
|
+
state: entry.journal.state,
|
|
88
|
+
channel: entry.journal.display.channel,
|
|
89
|
+
pluginCount: entry.journal.display.pluginCount,
|
|
90
|
+
planDigest: entry.journal.planDigest,
|
|
91
|
+
remote: entry.journal.descriptor.remote.name === null
|
|
92
|
+
? null
|
|
93
|
+
: sanitizeDiagnosticText(entry.journal.descriptor.remote.name),
|
|
94
|
+
branch: sanitizeDiagnosticText(entry.journal.descriptor.branch),
|
|
95
|
+
canApply: true,
|
|
96
|
+
requiresRemoteInput: entry.journal.descriptor.remote.name === null,
|
|
97
|
+
mustIncreaseVersion: false,
|
|
98
|
+
localRepositoryState: entry.journal.state === 'published'
|
|
99
|
+
? entry.localRepositoryState === 'present' ? 'unexpected' : 'notRequired'
|
|
100
|
+
: 'present',
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
class DefaultCatalogManagementService {
|
|
104
|
+
context;
|
|
105
|
+
#selectedWorkspaceRoot;
|
|
106
|
+
#devActivationReviews = new Map();
|
|
107
|
+
constructor(context) {
|
|
108
|
+
this.context = context;
|
|
109
|
+
}
|
|
110
|
+
sanitizeError(error, sensitiveValues = []) {
|
|
111
|
+
const issue = thrownIssue(error, [
|
|
112
|
+
this.context.cwd,
|
|
113
|
+
this.#requestedWorkspaceRoot(),
|
|
114
|
+
...(this.#selectedWorkspaceRoot ? [this.#selectedWorkspaceRoot] : []),
|
|
115
|
+
], sensitiveValues);
|
|
116
|
+
return new CatalogManagementError(issue.code, issue.message);
|
|
117
|
+
}
|
|
118
|
+
async #resolveWorkspace(mode) {
|
|
119
|
+
return resolveCatalogWorkspaceRoot({
|
|
120
|
+
cwd: this.context.cwd,
|
|
121
|
+
...(this.#selectedWorkspaceRoot === undefined
|
|
122
|
+
? {}
|
|
123
|
+
: { explicitRoot: this.#selectedWorkspaceRoot }),
|
|
124
|
+
mode,
|
|
125
|
+
env: this.context.env,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
#requestedWorkspaceRoot() {
|
|
129
|
+
return this.#selectedWorkspaceRoot ?? this.context.cwd;
|
|
130
|
+
}
|
|
131
|
+
#workspaceBinding() {
|
|
132
|
+
return digest(path.resolve(this.#requestedWorkspaceRoot()));
|
|
133
|
+
}
|
|
134
|
+
async #scaffoldStateDigest(workspaceRoot) {
|
|
135
|
+
const fileState = async (filePath) => {
|
|
136
|
+
const stat = await fs.lstat(filePath).catch((error) => {
|
|
137
|
+
if (error.code === 'ENOENT')
|
|
138
|
+
return null;
|
|
139
|
+
throw error;
|
|
140
|
+
});
|
|
141
|
+
if (!stat)
|
|
142
|
+
return { kind: 'absent' };
|
|
143
|
+
if (stat.isSymbolicLink())
|
|
144
|
+
return { kind: 'symlink' };
|
|
145
|
+
if (stat.isFile())
|
|
146
|
+
return { kind: 'file', digest: digest(await fs.readFile(filePath)) };
|
|
147
|
+
if (stat.isDirectory()) {
|
|
148
|
+
return { kind: 'directory', entries: (await fs.readdir(filePath)).sort() };
|
|
149
|
+
}
|
|
150
|
+
return { kind: 'special' };
|
|
151
|
+
};
|
|
152
|
+
const rootStat = await fs.lstat(workspaceRoot);
|
|
153
|
+
return digest({
|
|
154
|
+
root: {
|
|
155
|
+
kind: rootStat.isDirectory() && !rootStat.isSymbolicLink() ? 'directory' : 'invalid',
|
|
156
|
+
device: rootStat.dev,
|
|
157
|
+
inode: rootStat.ino,
|
|
158
|
+
},
|
|
159
|
+
marker: await fileState(path.join(workspaceRoot, CATALOG_WORKSPACE_MARKER_FILENAME)),
|
|
160
|
+
metadata: await fileState(path.join(workspaceRoot, 'plugins', 'marketplace-source.json')),
|
|
161
|
+
plugins: await fileState(path.join(workspaceRoot, 'plugins')),
|
|
162
|
+
gitignore: await fileState(path.join(workspaceRoot, '.gitignore')),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async #workspaceStateDigest() {
|
|
166
|
+
const status = await getCatalogStatus({
|
|
167
|
+
cwd: this.context.cwd,
|
|
168
|
+
...(this.#selectedWorkspaceRoot === undefined
|
|
169
|
+
? {}
|
|
170
|
+
: { workspaceRoot: this.#selectedWorkspaceRoot }),
|
|
171
|
+
env: this.context.env,
|
|
172
|
+
});
|
|
173
|
+
const sourceIdentity = status.workspace.valid
|
|
174
|
+
? await calculateCatalogWorkspaceSourceIdentity(await readCatalogWorkspace(status.workspaceRoot, { reservedImports: CLI_RESERVED_IMPORTS }))
|
|
175
|
+
: await this.#scaffoldStateDigest(status.workspaceRoot);
|
|
176
|
+
return digest({
|
|
177
|
+
sourceIdentity,
|
|
178
|
+
workspace: {
|
|
179
|
+
valid: status.workspace.valid,
|
|
180
|
+
identity: status.workspace.identity,
|
|
181
|
+
},
|
|
182
|
+
catalog: status.catalog,
|
|
183
|
+
plugins: status.plugins,
|
|
184
|
+
dependencyGraph: status.dependencyGraph,
|
|
185
|
+
git: status.git,
|
|
186
|
+
snapshot: {
|
|
187
|
+
state: status.snapshot.state,
|
|
188
|
+
valid: status.snapshot.valid,
|
|
189
|
+
catalogVersion: status.snapshot.catalogVersion,
|
|
190
|
+
snapshotDigest: status.snapshot.snapshotDigest,
|
|
191
|
+
sourceIdentityMatches: status.snapshot.sourceIdentityMatches,
|
|
192
|
+
},
|
|
193
|
+
issues: status.issues.map((issue) => ({ code: issue.code, blocking: issue.blocking })),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async #devDeactivationImpact(dev, replacementPrimary) {
|
|
197
|
+
if (!dev.source.exists || dev.source.id === null || dev.workspaceIdentity === null)
|
|
198
|
+
return undefined;
|
|
199
|
+
const config = await readCatalogSourcesConfigSnapshot(this.context.env);
|
|
200
|
+
const source = config.config.sources.find((entry) => entry.id === dev.source.id);
|
|
201
|
+
if (!source) {
|
|
202
|
+
throw new CatalogManagementError('operationReviewStale', 'Catalog development source changed before Review.');
|
|
203
|
+
}
|
|
204
|
+
let replacementPrimarySourceId;
|
|
205
|
+
if (source.primary) {
|
|
206
|
+
const others = config.config.sources.filter((entry) => entry.id !== source.id);
|
|
207
|
+
const replacement = replacementPrimary === undefined
|
|
208
|
+
? others.find(isDefaultOfficialCatalogSource) ?? (others.length === 1 ? others[0] : undefined)
|
|
209
|
+
: others.find((entry) => entry.id === replacementPrimary);
|
|
210
|
+
if (!replacement) {
|
|
211
|
+
throw new CatalogManagementError('replacementPrimaryUnavailable', 'Select an existing replacement primary source.');
|
|
212
|
+
}
|
|
213
|
+
replacementPrimarySourceId = replacement.id;
|
|
214
|
+
}
|
|
215
|
+
else if (replacementPrimary !== undefined) {
|
|
216
|
+
throw new CatalogManagementError('replacementPrimaryInvalid', 'A replacement primary source is only valid for a primary development source.');
|
|
217
|
+
}
|
|
218
|
+
const resolved = await this.#resolveWorkspace('existing');
|
|
219
|
+
const preview = await preflightCatalogSourceMutation({
|
|
220
|
+
request: {
|
|
221
|
+
action: 'remove',
|
|
222
|
+
sourceId: source.id,
|
|
223
|
+
...(replacementPrimarySourceId ? { replacementPrimarySourceId } : {}),
|
|
224
|
+
},
|
|
225
|
+
cwd: resolved.workspaceRoot,
|
|
226
|
+
env: this.context.env,
|
|
227
|
+
managedByWorkspaceIdentity: dev.workspaceIdentity,
|
|
228
|
+
});
|
|
229
|
+
const references = {
|
|
230
|
+
installation: preview.references.references.filter((entry) => entry.category === 'installation').length,
|
|
231
|
+
config: preview.references.references.filter((entry) => entry.category === 'config').length,
|
|
232
|
+
lifecycle: preview.references.references.filter((entry) => entry.category === 'lifecycle').length,
|
|
233
|
+
marketplace: preview.references.references.filter((entry) => entry.category === 'marketplace').length,
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
evidenceDigest: digest({
|
|
237
|
+
configDigest: preview.configDigest,
|
|
238
|
+
proposedConfigDigest: preview.proposedConfigDigest,
|
|
239
|
+
impactDigest: preview.impactDigest,
|
|
240
|
+
}),
|
|
241
|
+
inventoryComplete: preview.references.inventoryComplete,
|
|
242
|
+
primaryChange: preview.references.primaryChange !== null,
|
|
243
|
+
references,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
async #devActivationEvidence(intent) {
|
|
247
|
+
const resolved = await this.#resolveWorkspace('existing');
|
|
248
|
+
const packageInfo = await readCliPackageInfo();
|
|
249
|
+
const preflight = await preflightCatalogDevSourceActivation({
|
|
250
|
+
cwd: this.context.cwd,
|
|
251
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
252
|
+
sourceId: intent.sourceId,
|
|
253
|
+
primary: intent.primary,
|
|
254
|
+
forceBuild: intent.forceBuild,
|
|
255
|
+
cliVersion: packageInfo.version,
|
|
256
|
+
env: this.context.env,
|
|
257
|
+
});
|
|
258
|
+
const evidenceDigest = digest(preflight.review);
|
|
259
|
+
this.#devActivationReviews.set(evidenceDigest, preflight.review);
|
|
260
|
+
const affectedReferences = new Map([
|
|
261
|
+
...(preflight.sourceImpact?.references ?? []),
|
|
262
|
+
...preflight.impacts.flatMap((impact) => impact.affectedReferences),
|
|
263
|
+
].map((reference) => [JSON.stringify(reference), reference]));
|
|
264
|
+
const references = {
|
|
265
|
+
installation: 0,
|
|
266
|
+
config: 0,
|
|
267
|
+
lifecycle: 0,
|
|
268
|
+
marketplace: 0,
|
|
269
|
+
};
|
|
270
|
+
for (const reference of affectedReferences.values())
|
|
271
|
+
references[reference.category] += 1;
|
|
272
|
+
const changeNames = {
|
|
273
|
+
added: new Set(),
|
|
274
|
+
removed: new Set(),
|
|
275
|
+
changed: new Set(),
|
|
276
|
+
newlyIncompatible: new Set(),
|
|
277
|
+
dependencyChanged: new Set(),
|
|
278
|
+
platformSupportChanged: new Set(),
|
|
279
|
+
};
|
|
280
|
+
for (const impact of preflight.impacts) {
|
|
281
|
+
for (const key of [
|
|
282
|
+
'added', 'removed', 'changed', 'newlyIncompatible', 'dependencyChanged',
|
|
283
|
+
]) {
|
|
284
|
+
for (const pluginId of impact.changes[key])
|
|
285
|
+
changeNames[key].add(pluginId);
|
|
286
|
+
}
|
|
287
|
+
for (const change of impact.changes.platformSupportChanged) {
|
|
288
|
+
changeNames.platformSupportChanged.add(change.pluginId);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
evidenceDigest,
|
|
293
|
+
sourceId: preflight.review.sourceId,
|
|
294
|
+
catalogVersion: preflight.review.candidate.catalogVersion,
|
|
295
|
+
inventoryComplete: preflight.sourceImpact?.inventoryComplete !== false
|
|
296
|
+
&& preflight.impacts.every((impact) => impact.inventoryComplete),
|
|
297
|
+
primaryChange: preflight.impacts.some((impact) => impact.primaryChange !== null),
|
|
298
|
+
references,
|
|
299
|
+
changes: {
|
|
300
|
+
added: changeNames.added.size,
|
|
301
|
+
removed: changeNames.removed.size,
|
|
302
|
+
changed: changeNames.changed.size,
|
|
303
|
+
newlyIncompatible: changeNames.newlyIncompatible.size,
|
|
304
|
+
dependencyChanged: changeNames.dependencyChanged.size,
|
|
305
|
+
platformSupportChanged: changeNames.platformSupportChanged.size,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async selectWorkspace(workspaceRoot) {
|
|
310
|
+
this.#selectedWorkspaceRoot = workspaceRoot === undefined
|
|
311
|
+
? undefined
|
|
312
|
+
: path.resolve(this.context.cwd, workspaceRoot);
|
|
313
|
+
return this.getWorkspace();
|
|
314
|
+
}
|
|
315
|
+
async getWorkspace() {
|
|
316
|
+
const requestedRoot = this.#requestedWorkspaceRoot();
|
|
317
|
+
const status = await getCatalogStatus({
|
|
318
|
+
cwd: this.context.cwd,
|
|
319
|
+
...(this.#selectedWorkspaceRoot === undefined
|
|
320
|
+
? {}
|
|
321
|
+
: { workspaceRoot: this.#selectedWorkspaceRoot }),
|
|
322
|
+
env: this.context.env,
|
|
323
|
+
});
|
|
324
|
+
const markerExists = await fs.pathExists(path.join(status.workspaceRoot, CATALOG_WORKSPACE_MARKER_FILENAME));
|
|
325
|
+
return {
|
|
326
|
+
state: status.workspace.valid ? 'ready' : markerExists ? 'invalid' : 'missing',
|
|
327
|
+
selected: this.#selectedWorkspaceRoot !== undefined,
|
|
328
|
+
label: safeWorkspaceLabel(status.workspaceRoot),
|
|
329
|
+
resolvedFrom: status.workspace.resolvedFrom,
|
|
330
|
+
identity: status.workspace.identity === null
|
|
331
|
+
? null
|
|
332
|
+
: sanitizeDiagnosticText(status.workspace.identity, [requestedRoot, status.workspaceRoot]),
|
|
333
|
+
catalog: {
|
|
334
|
+
...structuredClone(status.catalog),
|
|
335
|
+
ownerName: status.catalog.ownerName === null
|
|
336
|
+
? null
|
|
337
|
+
: sanitizeDiagnosticText(status.catalog.ownerName, [requestedRoot, status.workspaceRoot]),
|
|
338
|
+
ownerUrl: status.catalog.ownerUrl === null
|
|
339
|
+
? null
|
|
340
|
+
: sanitizeDiagnosticText(safeRemoteIdentity(status.catalog.ownerUrl), [requestedRoot, status.workspaceRoot]),
|
|
341
|
+
displayName: status.catalog.displayName === null
|
|
342
|
+
? null
|
|
343
|
+
: sanitizeDiagnosticText(status.catalog.displayName, [requestedRoot, status.workspaceRoot]),
|
|
344
|
+
},
|
|
345
|
+
plugins: status.plugins.map((plugin) => ({
|
|
346
|
+
name: plugin.name,
|
|
347
|
+
version: plugin.version,
|
|
348
|
+
dependencies: [...plugin.dependencies],
|
|
349
|
+
})),
|
|
350
|
+
dependencyGraphValid: status.dependencyGraph.valid,
|
|
351
|
+
git: {
|
|
352
|
+
checked: status.git.checked,
|
|
353
|
+
clean: status.git.clean,
|
|
354
|
+
hasHead: status.git.sourceHead !== null,
|
|
355
|
+
},
|
|
356
|
+
snapshot: {
|
|
357
|
+
state: status.snapshot.state,
|
|
358
|
+
valid: status.snapshot.valid,
|
|
359
|
+
catalogVersion: status.snapshot.catalogVersion,
|
|
360
|
+
hasDigest: status.snapshot.snapshotDigest !== null,
|
|
361
|
+
sourceIdentityMatches: status.snapshot.sourceIdentityMatches,
|
|
362
|
+
},
|
|
363
|
+
issues: status.issues.map((issue) => safeIssue(issue, [requestedRoot, status.workspaceRoot])),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
async validateWorkspace() {
|
|
367
|
+
const workspace = await this.getWorkspace();
|
|
368
|
+
if (workspace.state !== 'ready' || workspace.snapshot.state === 'absent') {
|
|
369
|
+
return {
|
|
370
|
+
state: workspace.snapshot.state === 'absent' ? 'absent' : 'invalid',
|
|
371
|
+
catalogVersion: workspace.snapshot.catalogVersion,
|
|
372
|
+
channel: workspace.catalog.channel,
|
|
373
|
+
hasSnapshotDigest: workspace.snapshot.hasDigest,
|
|
374
|
+
issues: workspace.issues,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
const resolved = await this.#resolveWorkspace('existing');
|
|
378
|
+
try {
|
|
379
|
+
await runCatalogValidateCommand({
|
|
380
|
+
snapshotDirectory: path.join(resolved.workspaceRoot, DEFAULT_CATALOG_OUTPUT_PATH),
|
|
381
|
+
io: { stdout: () => undefined, stderr: () => undefined },
|
|
382
|
+
});
|
|
383
|
+
const after = await this.getWorkspace();
|
|
384
|
+
return {
|
|
385
|
+
state: 'valid',
|
|
386
|
+
catalogVersion: after.snapshot.catalogVersion,
|
|
387
|
+
channel: after.catalog.channel,
|
|
388
|
+
hasSnapshotDigest: after.snapshot.hasDigest,
|
|
389
|
+
issues: [],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
return {
|
|
394
|
+
state: 'invalid',
|
|
395
|
+
catalogVersion: workspace.snapshot.catalogVersion,
|
|
396
|
+
channel: workspace.catalog.channel,
|
|
397
|
+
hasSnapshotDigest: workspace.snapshot.hasDigest,
|
|
398
|
+
issues: [thrownIssue(error, [resolved.workspaceRoot])],
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async getDev() {
|
|
403
|
+
const workspace = await this.getWorkspace();
|
|
404
|
+
if (workspace.state !== 'ready') {
|
|
405
|
+
return {
|
|
406
|
+
state: 'workspaceUnavailable',
|
|
407
|
+
ready: false,
|
|
408
|
+
workspaceIdentity: workspace.identity,
|
|
409
|
+
snapshot: {
|
|
410
|
+
state: workspace.snapshot.state,
|
|
411
|
+
valid: workspace.snapshot.valid,
|
|
412
|
+
hasDigest: workspace.snapshot.hasDigest,
|
|
413
|
+
},
|
|
414
|
+
source: { exists: false, id: null, pathMatches: null, primary: null },
|
|
415
|
+
active: {
|
|
416
|
+
exists: false,
|
|
417
|
+
ready: false,
|
|
418
|
+
digestMatchesSnapshot: false,
|
|
419
|
+
compatibility: null,
|
|
420
|
+
},
|
|
421
|
+
issues: workspace.issues,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const resolved = await this.#resolveWorkspace('existing');
|
|
425
|
+
const packageInfo = await readCliPackageInfo();
|
|
426
|
+
const status = await getCatalogDevSourceStatus({
|
|
427
|
+
cwd: this.context.cwd,
|
|
428
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
429
|
+
cliVersion: packageInfo.version,
|
|
430
|
+
env: this.context.env,
|
|
431
|
+
});
|
|
432
|
+
return {
|
|
433
|
+
state: status.ready ? 'ready'
|
|
434
|
+
: !status.source.exists ? 'inactive'
|
|
435
|
+
: 'needsAttention',
|
|
436
|
+
ready: status.ready,
|
|
437
|
+
workspaceIdentity: status.workspaceIdentity,
|
|
438
|
+
snapshot: {
|
|
439
|
+
state: status.snapshot.state,
|
|
440
|
+
valid: status.snapshot.valid,
|
|
441
|
+
hasDigest: status.snapshot.digest !== null,
|
|
442
|
+
},
|
|
443
|
+
source: structuredClone(status.source),
|
|
444
|
+
active: {
|
|
445
|
+
exists: status.active.exists,
|
|
446
|
+
ready: status.active.ready,
|
|
447
|
+
digestMatchesSnapshot: status.active.digestMatchesSnapshot,
|
|
448
|
+
compatibility: status.active.compatibility?.state ?? null,
|
|
449
|
+
},
|
|
450
|
+
issues: status.issues.map((issue) => safeIssue(issue, [resolved.workspaceRoot])),
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
async getStore() {
|
|
454
|
+
const status = await catalogStoreStatus({ env: this.context.env });
|
|
455
|
+
const entries = status.entries.map((entry) => ({
|
|
456
|
+
item: { storeKey: entry.storeKey, category: entry.category },
|
|
457
|
+
itemKey: storeItemKey(entry),
|
|
458
|
+
category: entry.category,
|
|
459
|
+
status: entry.status,
|
|
460
|
+
generationReference: entry.generationReference,
|
|
461
|
+
ageMs: entry.ageMs,
|
|
462
|
+
reclaimableBytes: entry.reclaimableBytes,
|
|
463
|
+
reason: entry.reason,
|
|
464
|
+
}));
|
|
465
|
+
return {
|
|
466
|
+
state: status.ok ? 'ready' : 'needsAttention',
|
|
467
|
+
entries,
|
|
468
|
+
totals: {
|
|
469
|
+
...status.totals,
|
|
470
|
+
retainedEntries: entries.filter((entry) => entry.status !== 'reclaimable').length,
|
|
471
|
+
},
|
|
472
|
+
issues: status.errors.map((error) => safeIssue(error, [])),
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
async getPublication() {
|
|
476
|
+
const workspace = await this.getWorkspace();
|
|
477
|
+
if (workspace.state !== 'ready') {
|
|
478
|
+
return { state: 'none', records: [], issues: [] };
|
|
479
|
+
}
|
|
480
|
+
const resolved = await this.#resolveWorkspace('existing');
|
|
481
|
+
const discoveries = await discoverCatalogPublicationJournals(resolved.workspaceRoot);
|
|
482
|
+
const records = discoveries.map((entry) => projectPublicationRecord(entry, resolved.workspaceRoot));
|
|
483
|
+
const issues = records.flatMap((entry) => entry.issue ? [entry.issue] : []);
|
|
484
|
+
return {
|
|
485
|
+
state: issues.length > 0 ? 'needsAttention'
|
|
486
|
+
: records.some((entry) => entry.state === 'prepared' || entry.state === 'uncertain')
|
|
487
|
+
? 'pending'
|
|
488
|
+
: records.some((entry) => entry.state === 'published') ? 'published' : 'none',
|
|
489
|
+
records,
|
|
490
|
+
issues,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
async captureEvidence(intent) {
|
|
494
|
+
const workspaceBinding = intent.action === 'storePrune' ? null : this.#workspaceBinding();
|
|
495
|
+
let evidence;
|
|
496
|
+
const requestedVersion = intent.action === 'workspaceInit'
|
|
497
|
+
? intent.catalogVersion
|
|
498
|
+
: intent.action === 'workspaceVersion' ? intent.version : undefined;
|
|
499
|
+
if (requestedVersion !== undefined) {
|
|
500
|
+
try {
|
|
501
|
+
assertCatalogVersion(requestedVersion, 'Catalog version');
|
|
502
|
+
}
|
|
503
|
+
catch {
|
|
504
|
+
throw new CatalogManagementError('invalidCatalogVersion', 'Catalog version must be valid strict SemVer.');
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (intent.action === 'devActivate' && intent.sourceId !== undefined
|
|
508
|
+
&& !CATALOG_SOURCE_ID_PATTERN.test(intent.sourceId)) {
|
|
509
|
+
throw new CatalogManagementError('invalidSourceId', 'Catalog development source ID is invalid.');
|
|
510
|
+
}
|
|
511
|
+
if (intent.action === 'devDeactivate' && intent.replacementPrimary !== undefined
|
|
512
|
+
&& !CATALOG_SOURCE_ID_PATTERN.test(intent.replacementPrimary)) {
|
|
513
|
+
throw new CatalogManagementError('invalidSourceId', 'Replacement primary source ID is invalid.');
|
|
514
|
+
}
|
|
515
|
+
if (intent.action === 'storePrune') {
|
|
516
|
+
const store = await this.getStore();
|
|
517
|
+
const byKey = new Map(store.entries.map((entry) => [entry.itemKey, entry]));
|
|
518
|
+
const requested = intent.items.map((item) => byKey.get(storeItemKey(item)));
|
|
519
|
+
if (requested.length === 0 || requested.some((entry) => entry?.status !== 'reclaimable')) {
|
|
520
|
+
throw new CatalogManagementError('catalogStoreReviewStale', 'Catalog store items are no longer reclaimable.');
|
|
521
|
+
}
|
|
522
|
+
evidence = {
|
|
523
|
+
action: intent.action,
|
|
524
|
+
workspaceBinding,
|
|
525
|
+
store,
|
|
526
|
+
authorizedStoreItems: intent.items.map((item) => ({ ...item })),
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
else if (intent.action === 'publishApply') {
|
|
530
|
+
const publication = await this.getPublication();
|
|
531
|
+
const record = publication.records.find((entry) => entry.planDigest === intent.expectedPlanDigest);
|
|
532
|
+
if (!record || record.state === 'damaged' || !record.canApply) {
|
|
533
|
+
throw new CatalogManagementError('catalogPublicationPlanUnavailable', 'The authorized Catalog publication plan is missing or damaged. Increase the Catalog version.');
|
|
534
|
+
}
|
|
535
|
+
if (record.remote === null && intent.remote === undefined && intent.remoteUrl === undefined) {
|
|
536
|
+
throw new CatalogManagementError('catalogPublicationTargetRequired', 'Re-enter the direct remote URL for this Catalog publication plan.');
|
|
537
|
+
}
|
|
538
|
+
const resolved = await this.#resolveWorkspace('existing');
|
|
539
|
+
const located = await findCatalogPublicationJournalByPlanDigest(resolved.workspaceRoot, intent.expectedPlanDigest);
|
|
540
|
+
const applyContext = await inspectCatalogPublicationApplyContext({
|
|
541
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
542
|
+
remote: intent.remote ?? record.remote ?? undefined,
|
|
543
|
+
remoteUrl: intent.remoteUrl,
|
|
544
|
+
branch: intent.branch ?? record.branch ?? undefined,
|
|
545
|
+
env: this.context.env,
|
|
546
|
+
});
|
|
547
|
+
if (applyContext.workspaceIdentity !== located.journal.descriptor.workspaceIdentity
|
|
548
|
+
|| applyContext.remoteIdentityDigest !== located.journal.descriptor.remote.identityDigest
|
|
549
|
+
|| applyContext.branch !== located.journal.descriptor.branch) {
|
|
550
|
+
throw new CatalogManagementError('catalogPublicationTargetMismatch', 'The selected Workspace, remote, or branch does not match this Catalog publication plan.');
|
|
551
|
+
}
|
|
552
|
+
evidence = {
|
|
553
|
+
action: intent.action,
|
|
554
|
+
workspaceBinding,
|
|
555
|
+
publication: record,
|
|
556
|
+
target: {
|
|
557
|
+
remote: safeRemoteLabel(intent.remote, intent.remoteUrl) ?? record.remote,
|
|
558
|
+
branch: intent.branch ?? record.branch ?? 'catalog',
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
const workspace = await this.getWorkspace();
|
|
564
|
+
if (intent.action !== 'workspaceInit' && workspace.state !== 'ready') {
|
|
565
|
+
throw new CatalogManagementError('catalogWorkspaceUnavailable', 'Catalog Workspace is not available.');
|
|
566
|
+
}
|
|
567
|
+
const workspaceStateDigest = await this.#workspaceStateDigest();
|
|
568
|
+
const dev = intent.action === 'devActivate' || intent.action === 'devDeactivate'
|
|
569
|
+
? await this.getDev()
|
|
570
|
+
: undefined;
|
|
571
|
+
const devActivation = intent.action === 'devActivate'
|
|
572
|
+
? await this.#devActivationEvidence(intent)
|
|
573
|
+
: undefined;
|
|
574
|
+
const devImpact = intent.action === 'devDeactivate' && dev !== undefined
|
|
575
|
+
? await this.#devDeactivationImpact(dev, intent.replacementPrimary)
|
|
576
|
+
: undefined;
|
|
577
|
+
let target;
|
|
578
|
+
if (intent.action === 'publishPlan') {
|
|
579
|
+
const resolved = await this.#resolveWorkspace('existing');
|
|
580
|
+
const publicationTarget = await resolveCatalogPublicationTarget({
|
|
581
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
582
|
+
remote: intent.remote,
|
|
583
|
+
branch: intent.branch,
|
|
584
|
+
env: this.context.env,
|
|
585
|
+
requireRemote: intent.remoteUrl === undefined,
|
|
586
|
+
});
|
|
587
|
+
if (!publicationTarget.branchValid) {
|
|
588
|
+
throw new CatalogManagementError('catalogPublicationBranchInvalid', 'Catalog publication branch is not a safe Git branch name.');
|
|
589
|
+
}
|
|
590
|
+
target = {
|
|
591
|
+
remote: safeRemoteLabel(intent.remote, intent.remoteUrl)
|
|
592
|
+
?? publicationTarget.remote,
|
|
593
|
+
branch: publicationTarget.branch,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
evidence = {
|
|
597
|
+
action: intent.action,
|
|
598
|
+
workspaceBinding,
|
|
599
|
+
workspace,
|
|
600
|
+
workspaceStateDigest,
|
|
601
|
+
...(dev ? { dev } : {}),
|
|
602
|
+
...(devActivation ? { devActivation } : {}),
|
|
603
|
+
...(devImpact ? { devImpact } : {}),
|
|
604
|
+
...(target ? { target } : {}),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
return { ...evidence, evidenceDigest: digest(evidence) };
|
|
608
|
+
}
|
|
609
|
+
async #assertExpected(intent, expected) {
|
|
610
|
+
if (expected.action !== intent.action
|
|
611
|
+
|| expected.workspaceBinding !== (intent.action === 'storePrune' ? null : this.#workspaceBinding())) {
|
|
612
|
+
throw new CatalogManagementError('operationReviewStale', 'Catalog management selection changed after Review.');
|
|
613
|
+
}
|
|
614
|
+
if (intent.action === 'storePrune') {
|
|
615
|
+
const expectedItems = expected.authorizedStoreItems ?? [];
|
|
616
|
+
if (JSON.stringify(expectedItems) !== JSON.stringify(intent.items)) {
|
|
617
|
+
throw new CatalogManagementError('operationReviewStale', 'Catalog store selection changed after Review.');
|
|
618
|
+
}
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (intent.action === 'publishApply') {
|
|
622
|
+
const current = await this.captureEvidence(intent);
|
|
623
|
+
if (current.publication?.planDigest !== expected.publication?.planDigest
|
|
624
|
+
|| current.publication?.state !== expected.publication?.state
|
|
625
|
+
|| current.target?.remote !== expected.target?.remote
|
|
626
|
+
|| current.target?.branch !== expected.target?.branch) {
|
|
627
|
+
throw new CatalogManagementError('operationReviewStale', 'Catalog publication authorization changed after Review.');
|
|
628
|
+
}
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
const current = await this.captureEvidence(intent);
|
|
632
|
+
if (current.evidenceDigest !== expected.evidenceDigest) {
|
|
633
|
+
throw new CatalogManagementError('operationReviewStale', 'Catalog management state changed after Review.');
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
async execute(intent, expected, onProgress = () => undefined) {
|
|
637
|
+
await this.#assertExpected(intent, expected);
|
|
638
|
+
if (intent.action === 'storePrune') {
|
|
639
|
+
await onProgress('prune');
|
|
640
|
+
const result = await catalogStorePrune({
|
|
641
|
+
dryRun: false,
|
|
642
|
+
authorizedItems: intent.items,
|
|
643
|
+
env: this.context.env,
|
|
644
|
+
});
|
|
645
|
+
return {
|
|
646
|
+
action: intent.action,
|
|
647
|
+
state: result.overallState === 'partial' ? 'partial' : 'succeeded',
|
|
648
|
+
message: result.overallState === 'partial'
|
|
649
|
+
? 'Catalog store cleanup completed partially.'
|
|
650
|
+
: 'Catalog store cleanup completed.',
|
|
651
|
+
messages: result.errors.map((error) => sanitizeDiagnosticText(error.message, [], [error.storeKey])),
|
|
652
|
+
store: {
|
|
653
|
+
deletedEntries: result.deleted.length,
|
|
654
|
+
deletedBytes: result.deletedBytes,
|
|
655
|
+
retainedEntries: Math.max(0, intent.items.length - result.deleted.length),
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
const mode = intent.action === 'workspaceInit' ? 'initialize' : 'existing';
|
|
660
|
+
const resolved = await this.#resolveWorkspace(mode);
|
|
661
|
+
if (intent.action === 'workspaceInit') {
|
|
662
|
+
await onProgress('validate');
|
|
663
|
+
await runCatalogInitCommand({
|
|
664
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
665
|
+
workspaceRootResolvedFrom: resolved.workspaceRootResolvedFrom,
|
|
666
|
+
ownerName: intent.ownerName,
|
|
667
|
+
ownerUrl: intent.ownerUrl,
|
|
668
|
+
displayName: intent.displayName,
|
|
669
|
+
catalogVersion: intent.catalogVersion,
|
|
670
|
+
force: intent.force,
|
|
671
|
+
io: { stdout: () => undefined, stderr: () => undefined },
|
|
672
|
+
});
|
|
673
|
+
const status = await this.getWorkspace();
|
|
674
|
+
return {
|
|
675
|
+
action: intent.action,
|
|
676
|
+
state: 'succeeded',
|
|
677
|
+
message: 'Catalog Workspace initialized.',
|
|
678
|
+
messages: [],
|
|
679
|
+
...(status.catalog.version ? { catalogVersion: status.catalog.version } : {}),
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
if (intent.action === 'workspaceVersion') {
|
|
683
|
+
await onProgress('validate');
|
|
684
|
+
const result = await runCatalogVersionCommand({
|
|
685
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
686
|
+
workspaceRootResolvedFrom: resolved.workspaceRootResolvedFrom,
|
|
687
|
+
version: intent.version,
|
|
688
|
+
bump: intent.bump,
|
|
689
|
+
io: { stdout: () => undefined, stderr: () => undefined },
|
|
690
|
+
});
|
|
691
|
+
return {
|
|
692
|
+
action: intent.action,
|
|
693
|
+
state: 'succeeded',
|
|
694
|
+
message: result.changed ? 'Catalog version updated.' : 'Catalog version is unchanged.',
|
|
695
|
+
messages: [],
|
|
696
|
+
catalogVersion: result.catalogVersion,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
if (intent.action === 'workspaceBuild') {
|
|
700
|
+
await onProgress('build');
|
|
701
|
+
const result = await runCatalogBuildCommand({
|
|
702
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
703
|
+
workspaceRootResolvedFrom: resolved.workspaceRootResolvedFrom,
|
|
704
|
+
force: intent.force,
|
|
705
|
+
io: { stdout: () => undefined, stderr: () => undefined },
|
|
706
|
+
});
|
|
707
|
+
await onProgress('verify');
|
|
708
|
+
return {
|
|
709
|
+
action: intent.action,
|
|
710
|
+
state: 'succeeded',
|
|
711
|
+
message: 'Catalog snapshot built and validated.',
|
|
712
|
+
messages: [`${result.pluginNames.length} plugins`],
|
|
713
|
+
catalogVersion: result.catalogVersion,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
const packageInfo = await readCliPackageInfo();
|
|
717
|
+
if (intent.action === 'devActivate') {
|
|
718
|
+
const activationEvidence = expected.devActivation;
|
|
719
|
+
const activationReview = activationEvidence
|
|
720
|
+
? this.#devActivationReviews.get(activationEvidence.evidenceDigest)
|
|
721
|
+
: undefined;
|
|
722
|
+
if (!activationEvidence || !activationReview) {
|
|
723
|
+
throw new CatalogManagementError('operationReviewStale', 'Catalog development activation evidence is unavailable. Review it again.');
|
|
724
|
+
}
|
|
725
|
+
await onProgress('validate');
|
|
726
|
+
const result = await activateCatalogDevSource({
|
|
727
|
+
cwd: this.context.cwd,
|
|
728
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
729
|
+
sourceId: intent.sourceId,
|
|
730
|
+
primary: intent.primary,
|
|
731
|
+
forceBuild: intent.forceBuild,
|
|
732
|
+
yes: true,
|
|
733
|
+
cliVersion: packageInfo.version,
|
|
734
|
+
env: this.context.env,
|
|
735
|
+
expectedReview: activationReview,
|
|
736
|
+
runtime: {
|
|
737
|
+
sourceRuntime: {
|
|
738
|
+
onProgress: () => onProgress('source'),
|
|
739
|
+
},
|
|
740
|
+
beforeSync: () => onProgress('sync'),
|
|
741
|
+
afterSync: () => onProgress('verify'),
|
|
742
|
+
},
|
|
743
|
+
});
|
|
744
|
+
if (result.state === 'failed') {
|
|
745
|
+
throw new CatalogManagementError(result.errors[0]?.code ?? 'catalogDevActivationFailed', result.errors[0]?.message ?? 'Catalog development source activation failed.');
|
|
746
|
+
}
|
|
747
|
+
return {
|
|
748
|
+
action: intent.action,
|
|
749
|
+
state: result.state === 'completed' ? 'succeeded' : 'partial',
|
|
750
|
+
message: result.state === 'completed'
|
|
751
|
+
? 'Catalog development source activated.'
|
|
752
|
+
: 'Catalog development source activation completed partially.',
|
|
753
|
+
messages: [
|
|
754
|
+
...result.errors.map((error) => sanitizeDiagnosticText(error.message, [resolved.workspaceRoot])),
|
|
755
|
+
...result.recovery.map((message) => sanitizeDiagnosticText(message, [resolved.workspaceRoot])),
|
|
756
|
+
],
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
if (intent.action === 'devDeactivate') {
|
|
760
|
+
await onProgress('source');
|
|
761
|
+
const result = await deactivateCatalogDevSource({
|
|
762
|
+
cwd: this.context.cwd,
|
|
763
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
764
|
+
replacementPrimary: intent.replacementPrimary,
|
|
765
|
+
yes: true,
|
|
766
|
+
env: this.context.env,
|
|
767
|
+
});
|
|
768
|
+
return {
|
|
769
|
+
action: intent.action,
|
|
770
|
+
state: result.state === 'completed' ? 'succeeded' : 'partial',
|
|
771
|
+
message: result.state === 'completed'
|
|
772
|
+
? 'Catalog development source deactivated.'
|
|
773
|
+
: 'Catalog development source deactivation completed partially.',
|
|
774
|
+
messages: [
|
|
775
|
+
...result.errors.map((error) => sanitizeDiagnosticText(error.message, [resolved.workspaceRoot])),
|
|
776
|
+
...result.recovery.map((message) => sanitizeDiagnosticText(message, [resolved.workspaceRoot])),
|
|
777
|
+
],
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
if (intent.action === 'publishPlan') {
|
|
781
|
+
await onProgress('prepare');
|
|
782
|
+
const result = await prepareCatalogPublicationPlan({
|
|
783
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
784
|
+
workspaceRootResolvedFrom: resolved.workspaceRootResolvedFrom,
|
|
785
|
+
remote: intent.remote,
|
|
786
|
+
remoteUrl: intent.remoteUrl,
|
|
787
|
+
branch: intent.branch,
|
|
788
|
+
env: this.context.env,
|
|
789
|
+
});
|
|
790
|
+
await onProgress('verify');
|
|
791
|
+
return {
|
|
792
|
+
action: intent.action,
|
|
793
|
+
state: 'succeeded',
|
|
794
|
+
message: 'Catalog publication plan prepared.',
|
|
795
|
+
messages: ['Review the prepared plan before publication.'],
|
|
796
|
+
catalogVersion: result.catalogVersion,
|
|
797
|
+
planDigest: result.planDigest,
|
|
798
|
+
publication: {
|
|
799
|
+
catalogVersion: result.catalogVersion,
|
|
800
|
+
planDigest: result.planDigest,
|
|
801
|
+
state: result.state,
|
|
802
|
+
remote: result.remote,
|
|
803
|
+
branch: result.branch,
|
|
804
|
+
},
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
await onProgress('remote');
|
|
808
|
+
const result = await applyCatalogPublicationPlan({
|
|
809
|
+
workspaceRoot: resolved.workspaceRoot,
|
|
810
|
+
workspaceRootResolvedFrom: resolved.workspaceRootResolvedFrom,
|
|
811
|
+
remote: intent.remote ?? expected.publication?.remote ?? undefined,
|
|
812
|
+
remoteUrl: intent.remoteUrl,
|
|
813
|
+
branch: intent.branch ?? expected.publication?.branch ?? undefined,
|
|
814
|
+
expectedPlanDigest: intent.expectedPlanDigest,
|
|
815
|
+
env: this.context.env,
|
|
816
|
+
});
|
|
817
|
+
await onProgress('verify');
|
|
818
|
+
return {
|
|
819
|
+
action: intent.action,
|
|
820
|
+
state: 'succeeded',
|
|
821
|
+
message: 'Catalog publication completed.',
|
|
822
|
+
messages: result.warnings.map((warning) => sanitizeDiagnosticText(warning, [resolved.workspaceRoot])),
|
|
823
|
+
catalogVersion: result.catalogVersion,
|
|
824
|
+
planDigest: result.planDigest,
|
|
825
|
+
publication: {
|
|
826
|
+
catalogVersion: result.catalogVersion,
|
|
827
|
+
planDigest: result.planDigest,
|
|
828
|
+
state: result.state,
|
|
829
|
+
remote: result.remote,
|
|
830
|
+
branch: result.branch,
|
|
831
|
+
},
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
export function createCatalogManagementService(context) {
|
|
836
|
+
const service = new DefaultCatalogManagementService(context);
|
|
837
|
+
const protect = async (operation, sensitiveValues = []) => {
|
|
838
|
+
try {
|
|
839
|
+
return await operation();
|
|
840
|
+
}
|
|
841
|
+
catch (error) {
|
|
842
|
+
throw service.sanitizeError(error, sensitiveValues);
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
return {
|
|
846
|
+
selectWorkspace: (workspaceRoot) => protect(() => service.selectWorkspace(workspaceRoot), workspaceRoot ? [workspaceRoot] : []),
|
|
847
|
+
getWorkspace: () => protect(() => service.getWorkspace()),
|
|
848
|
+
validateWorkspace: () => protect(() => service.validateWorkspace()),
|
|
849
|
+
getDev: () => protect(() => service.getDev()),
|
|
850
|
+
getStore: () => protect(() => service.getStore()),
|
|
851
|
+
getPublication: () => protect(() => service.getPublication()),
|
|
852
|
+
captureEvidence: (intent) => protect(() => service.captureEvidence(intent), intentSensitiveValues(intent)),
|
|
853
|
+
execute: (intent, expected, onProgress) => protect(() => service.execute(intent, expected, onProgress), intentSensitiveValues(intent)),
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
//# sourceMappingURL=catalog-management.js.map
|