@lesliechan721/aw 0.1.0-beta.20 → 0.1.0-beta.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/src/commands/status-v6.js +49 -32
  2. package/dist/src/commands/status-v6.js.map +1 -1
  3. package/dist/src/tui/components.d.ts +1 -0
  4. package/dist/src/tui/components.js +3 -1
  5. package/dist/src/tui/components.js.map +1 -1
  6. package/dist/src/tui/console-app-v4.d.ts +1 -1
  7. package/dist/src/tui/console-app-v4.js +756 -1127
  8. package/dist/src/tui/console-app-v4.js.map +1 -1
  9. package/dist/src/tui/controller-runtime.js +65 -42
  10. package/dist/src/tui/controller-runtime.js.map +1 -1
  11. package/dist/src/tui/controller.d.ts +4 -30
  12. package/dist/src/tui/controller.js +7 -219
  13. package/dist/src/tui/controller.js.map +1 -1
  14. package/dist/src/tui/dependency-graph.d.ts +3 -3
  15. package/dist/src/tui/dependency-graph.js +15 -14
  16. package/dist/src/tui/dependency-graph.js.map +1 -1
  17. package/dist/src/tui/glossary.d.ts +1 -1
  18. package/dist/src/tui/glossary.js +1 -1
  19. package/dist/src/tui/glossary.js.map +1 -1
  20. package/dist/src/tui/model.d.ts +2 -0
  21. package/dist/src/tui/model.js +67 -30
  22. package/dist/src/tui/model.js.map +1 -1
  23. package/dist/src/tui/operation-review-planner.js +8 -195
  24. package/dist/src/tui/operation-review-planner.js.map +1 -1
  25. package/dist/src/tui/plugin-write-task.js +4 -0
  26. package/dist/src/tui/plugin-write-task.js.map +1 -1
  27. package/dist/src/tui/router.d.ts +4 -16
  28. package/dist/src/tui/router.js.map +1 -1
  29. package/dist/src/tui/screen-registry.d.ts +10 -57
  30. package/dist/src/tui/screen-registry.js +1 -15
  31. package/dist/src/tui/screen-registry.js.map +1 -1
  32. package/package.json +3 -3
  33. package/dist/src/tui/catalog-management-screens.d.ts +0 -45
  34. package/dist/src/tui/catalog-management-screens.js +0 -25
  35. package/dist/src/tui/catalog-management-screens.js.map +0 -1
  36. package/dist/src/tui/catalog-management.d.ts +0 -240
  37. package/dist/src/tui/catalog-management.js +0 -856
  38. package/dist/src/tui/catalog-management.js.map +0 -1
@@ -1,856 +0,0 @@
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