@lesliechan721/aw-plugin-core 0.1.0-beta.4 → 0.1.0-beta.6

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/workspace.js CHANGED
@@ -5,13 +5,13 @@ import fs from 'fs-extra';
5
5
  import { assertCatalogVersion, assertStrictSemVer, compareStrictSemVer, MARKETPLACE_SOURCE_PATH, SOURCE_MANIFEST_PATH, createMarketplaceManifest, createMarketplaceManifestEntry, normalizeCatalogManifestBase, normalizeCatalogMarketplaceIdentity, normalizeCatalogManifestEntries, normalizeManifestDigestInput, readMarketplaceSourceMeta, readPluginManifest, validatePlatformManifestProjection, } from './manifest-contract.js';
6
6
  import { resolveSupportedPlatforms } from './platform-support.js';
7
7
  import { appendCatalogToPluginReleaseLedger, assertStablePluginVersion, PLUGIN_RELEASE_LEDGER_FILENAME, PLUGIN_RELEASE_LEDGER_SCHEMA_VERSION, resolveBetaPluginVersionOverrides, validatePluginReleaseLedgerAgainstManifest, } from './plugin-release.js';
8
- export const DEFAULT_WORKSPACE_CATALOG_VERSION = '0.1.0';
8
+ export const DEFAULT_CATALOG_WORKSPACE_VERSION = '0.1.0';
9
9
  export const DEFAULT_CATALOG_OUTPUT_PATH = path.join('.aw', 'catalog');
10
10
  export const DEFAULT_PUBLISHED_CATALOG_OUTPUT_PATH = path.join('.aw', 'publish', 'catalog');
11
11
  export const DEFAULT_PLUGIN_OUTPUT_PATH = path.join('.aw', 'plugins');
12
12
  export const CATALOG_MANIFEST_FILENAME = 'catalog-manifest.json';
13
13
  export const GENERATED_OUTPUT_MARKER_FILENAME = '.aw-generated.json';
14
- export const WORKSPACE_MARKER_FILENAME = '.aw-workspace.json';
14
+ export const CATALOG_WORKSPACE_MARKER_FILENAME = '.aw-workspace.json';
15
15
  function isInside(root, candidate) {
16
16
  const relative = path.relative(root, candidate);
17
17
  return relative === '' || (relative !== '..'
@@ -47,11 +47,11 @@ async function assertNoSymlinkPath(root, target) {
47
47
  }
48
48
  }
49
49
  }
50
- export async function resolveWorkspaceOutputPath(options) {
51
- const workspaceRoot = await assertDirectoryWithoutSymlinks(options.workspaceRoot, 'Workspace root');
50
+ export async function resolveCatalogWorkspaceOutputPath(options) {
51
+ const workspaceRoot = await assertDirectoryWithoutSymlinks(options.workspaceRoot, 'Catalog Workspace root');
52
52
  const outputRoot = path.resolve(workspaceRoot, options.outputPath);
53
53
  if (!isInside(workspaceRoot, outputRoot) || outputRoot === workspaceRoot) {
54
- throw new Error(`Output must be inside the workspace: ${outputRoot}`);
54
+ throw new Error(`Output must be inside the Catalog Workspace: ${outputRoot}`);
55
55
  }
56
56
  await assertNoSymlinkPath(workspaceRoot, outputRoot);
57
57
  const protectedRoots = [
@@ -63,7 +63,7 @@ export async function resolveWorkspaceOutputPath(options) {
63
63
  if (protectedRoot.endsWith(`${path.sep}.aw`) && outputRoot !== protectedRoot)
64
64
  continue;
65
65
  if (isInside(protectedRoot, outputRoot) || isInside(outputRoot, protectedRoot)) {
66
- throw new Error(`Output overlaps a protected workspace path: ${outputRoot}`);
66
+ throw new Error(`Output overlaps a protected Catalog Workspace path: ${outputRoot}`);
67
67
  }
68
68
  }
69
69
  const otherGeneratedRoot = path.join(workspaceRoot, options.kind === 'catalog' ? DEFAULT_PLUGIN_OUTPUT_PATH : DEFAULT_CATALOG_OUTPUT_PATH);
@@ -72,24 +72,24 @@ export async function resolveWorkspaceOutputPath(options) {
72
72
  }
73
73
  return { workspaceRoot, outputRoot };
74
74
  }
75
- function safeWorkspaceName(workspaceRoot) {
75
+ function safeCatalogWorkspaceName(workspaceRoot) {
76
76
  const normalized = path.basename(workspaceRoot).toLowerCase()
77
77
  .replace(/[^a-z0-9]+/gu, '-')
78
78
  .replace(/^-+|-+$/gu, '');
79
79
  return normalized || 'aw-workspace';
80
80
  }
81
- function workspaceMetadata(options, workspaceRoot) {
82
- const workspaceName = safeWorkspaceName(workspaceRoot);
81
+ function catalogWorkspaceMetadata(options, workspaceRoot) {
82
+ const catalogWorkspaceName = safeCatalogWorkspaceName(workspaceRoot);
83
83
  return {
84
84
  owner: {
85
- name: options.ownerName?.trim() || workspaceName,
86
- url: options.ownerUrl?.trim() || `https://example.invalid/aw-workspaces/${workspaceName}`,
85
+ name: options.ownerName?.trim() || catalogWorkspaceName,
86
+ url: options.ownerUrl?.trim() || `https://example.invalid/aw-workspaces/${catalogWorkspaceName}`,
87
87
  },
88
88
  interface: {
89
- displayName: options.displayName?.trim() || workspaceName,
89
+ displayName: options.displayName?.trim() || catalogWorkspaceName,
90
90
  },
91
91
  catalog: {
92
- version: assertCatalogVersion(options.catalogVersion ?? DEFAULT_WORKSPACE_CATALOG_VERSION, 'catalog version'),
92
+ version: assertCatalogVersion(options.catalogVersion ?? DEFAULT_CATALOG_WORKSPACE_VERSION, 'catalog version'),
93
93
  },
94
94
  };
95
95
  }
@@ -115,41 +115,33 @@ async function writeTextAtomic(filePath, value) {
115
115
  throw error;
116
116
  }
117
117
  }
118
- function workspaceIdentity(workspaceRoot) {
119
- return sha256(`aw-plugin-workspace-v1\0${path.resolve(workspaceRoot)}`);
120
- }
121
- function workspaceScaffoldMarker(metadata) {
118
+ function catalogWorkspaceMarker() {
122
119
  return {
123
120
  schemaVersion: 1,
124
- kind: 'aw-plugin-workspace',
121
+ kind: 'aw-catalog-workspace',
125
122
  workspaceIdentity: sha256(crypto.randomUUID()),
126
- sourceIdentity: sha256(stableStringify(metadata)),
127
123
  };
128
124
  }
129
- function validateWorkspaceScaffoldMarker(value) {
125
+ function validateCatalogWorkspaceMarker(value) {
130
126
  const marker = value;
131
- const digestPattern = /^sha256:[0-9a-f]{64}$/u;
132
127
  if (!marker
133
128
  || typeof marker !== 'object'
134
129
  || Array.isArray(marker)
135
130
  || !isDeepStrictEqual(Object.keys(marker).sort(), [
136
- 'kind', 'schemaVersion', 'sourceIdentity', 'workspaceIdentity',
131
+ 'kind', 'schemaVersion', 'workspaceIdentity',
137
132
  ])
138
133
  || marker.schemaVersion !== 1
139
- || marker.kind !== 'aw-plugin-workspace'
134
+ || marker.kind !== 'aw-catalog-workspace'
140
135
  || typeof marker.workspaceIdentity !== 'string'
141
- || !digestPattern.test(marker.workspaceIdentity)
142
- || typeof marker.sourceIdentity !== 'string'
143
- || !digestPattern.test(marker.sourceIdentity)) {
144
- throw new Error('Invalid workspace scaffold marker.');
136
+ || marker.workspaceIdentity.trim().length === 0) {
137
+ throw new Error('Invalid Catalog Workspace marker.');
145
138
  }
146
139
  return { workspaceIdentity: marker.workspaceIdentity };
147
140
  }
148
- async function readWorkspaceOwnershipIdentity(workspaceRoot) {
149
- const markerPath = path.join(workspaceRoot, WORKSPACE_MARKER_FILENAME);
150
- if (!await fs.pathExists(markerPath))
151
- return workspaceIdentity(workspaceRoot);
152
- return validateWorkspaceScaffoldMarker(await readRegularJson(markerPath, 'Workspace marker')).workspaceIdentity;
141
+ async function readCatalogWorkspaceMarker(workspaceRoot) {
142
+ const markerPath = path.join(workspaceRoot, CATALOG_WORKSPACE_MARKER_FILENAME);
143
+ const marker = validateCatalogWorkspaceMarker(await readRegularJson(markerPath, 'Catalog Workspace marker'));
144
+ return { markerPath, workspaceIdentity: marker.workspaceIdentity };
153
145
  }
154
146
  async function readRegularJson(filePath, label) {
155
147
  const stat = await fs.lstat(filePath).catch((error) => {
@@ -162,21 +154,24 @@ async function readRegularJson(filePath, label) {
162
154
  }
163
155
  return fs.readJson(filePath);
164
156
  }
165
- export async function initializePluginWorkspace(options) {
166
- const workspaceRoot = await assertDirectoryWithoutSymlinks(options.workspaceRoot, 'Workspace root');
157
+ export async function initializeCatalogWorkspace(options) {
158
+ const workspaceRoot = await assertDirectoryWithoutSymlinks(options.workspaceRoot, 'Catalog Workspace root');
167
159
  const pluginsRoot = path.join(workspaceRoot, 'plugins');
168
160
  const metadataPath = path.join(workspaceRoot, MARKETPLACE_SOURCE_PATH);
169
- const markerPath = path.join(workspaceRoot, WORKSPACE_MARKER_FILENAME);
170
- const metadata = workspaceMetadata(options, workspaceRoot);
161
+ const markerPath = path.join(workspaceRoot, CATALOG_WORKSPACE_MARKER_FILENAME);
162
+ const metadata = catalogWorkspaceMetadata(options, workspaceRoot);
171
163
  const existingMetadata = await fs.pathExists(metadataPath);
172
164
  const existingMarker = await fs.pathExists(markerPath);
173
165
  if ((existingMetadata || existingMarker) && !options.force) {
174
- throw new Error(`Workspace catalog scaffold already exists: ${workspaceRoot}`);
166
+ throw new Error(`Catalog Workspace scaffold already exists: ${workspaceRoot}`);
167
+ }
168
+ if (existingMetadata && !existingMarker) {
169
+ throw new Error(`Refusing to adopt Catalog Workspace metadata without a valid marker: ${metadataPath}`);
175
170
  }
176
171
  if (await fs.pathExists(pluginsRoot)) {
177
172
  const stat = await fs.lstat(pluginsRoot);
178
173
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
179
- throw new Error(`Workspace plugins path must be a regular directory: ${pluginsRoot}`);
174
+ throw new Error(`Catalog Workspace plugins path must be a regular directory: ${pluginsRoot}`);
180
175
  }
181
176
  const entries = (await fs.readdir(pluginsRoot)).filter((entry) => entry !== path.basename(metadataPath));
182
177
  if (entries.length > 0 && !existingMarker) {
@@ -186,17 +181,18 @@ export async function initializePluginWorkspace(options) {
186
181
  if (existingMetadata) {
187
182
  const current = await readMarketplaceSourceMeta(workspaceRoot);
188
183
  if (!isDeepStrictEqual(current, metadata)) {
189
- throw new Error(`Refusing to replace different workspace catalog metadata: ${metadataPath}`);
184
+ throw new Error(`Refusing to replace different Catalog Workspace metadata: ${metadataPath}`);
190
185
  }
191
186
  }
192
- const expectedMarker = workspaceScaffoldMarker(metadata);
187
+ const expectedMarker = catalogWorkspaceMarker();
188
+ let workspaceIdentity = expectedMarker.workspaceIdentity;
193
189
  if (existingMarker) {
194
- const currentMarker = await readRegularJson(markerPath, 'Workspace marker');
190
+ const currentMarker = await readRegularJson(markerPath, 'Catalog Workspace marker');
195
191
  try {
196
- validateWorkspaceScaffoldMarker(currentMarker);
192
+ workspaceIdentity = validateCatalogWorkspaceMarker(currentMarker).workspaceIdentity;
197
193
  }
198
194
  catch {
199
- throw new Error(`Refusing to replace a workspace scaffold with a different identity: ${workspaceRoot}`);
195
+ throw new Error(`Refusing to replace a Catalog Workspace marker with a different identity: ${workspaceRoot}`);
200
196
  }
201
197
  }
202
198
  const gitignorePath = path.join(workspaceRoot, '.gitignore');
@@ -205,7 +201,7 @@ export async function initializePluginWorkspace(options) {
205
201
  if (gitignoreExists) {
206
202
  const stat = await fs.lstat(gitignorePath);
207
203
  if (!stat.isFile() || stat.isSymbolicLink()) {
208
- throw new Error(`Workspace .gitignore must be a regular file: ${gitignorePath}`);
204
+ throw new Error(`Catalog Workspace .gitignore must be a regular file: ${gitignorePath}`);
209
205
  }
210
206
  previousGitignore = await fs.readFile(gitignorePath, 'utf8');
211
207
  }
@@ -236,49 +232,82 @@ export async function initializePluginWorkspace(options) {
236
232
  await fs.remove(pluginsRoot);
237
233
  throw error;
238
234
  }
239
- return { workspaceRoot, metadataPath, metadata };
235
+ return { workspaceRoot, markerPath, workspaceIdentity, metadataPath, metadata };
240
236
  }
241
- export async function readPluginWorkspace(workspaceRoot, options = {}) {
242
- const root = await assertDirectoryWithoutSymlinks(workspaceRoot, 'Workspace root');
237
+ export async function readCatalogWorkspace(workspaceRoot, options = {}) {
238
+ const root = await assertDirectoryWithoutSymlinks(workspaceRoot, 'Catalog Workspace root');
239
+ const marker = await readCatalogWorkspaceMarker(root);
243
240
  const metadataPath = path.join(root, MARKETPLACE_SOURCE_PATH);
244
- await readRegularJson(metadataPath, 'Workspace catalog metadata');
241
+ await readRegularJson(metadataPath, 'Catalog Workspace metadata');
245
242
  if (!isInside(root, await fs.realpath(metadataPath)))
246
- throw new Error('Workspace catalog metadata resolves outside the workspace.');
243
+ throw new Error('Catalog Workspace metadata resolves outside the workspace.');
247
244
  const metadata = await readMarketplaceSourceMeta(root);
248
- const pluginsRoot = await assertDirectoryWithoutSymlinks(path.join(root, 'plugins'), 'Workspace plugins directory');
245
+ const pluginsRoot = await assertDirectoryWithoutSymlinks(path.join(root, 'plugins'), 'Catalog Workspace plugins directory');
249
246
  if (!isInside(root, pluginsRoot))
250
- throw new Error('Workspace plugins directory resolves outside the workspace.');
247
+ throw new Error('Catalog Workspace plugins directory resolves outside the workspace.');
251
248
  const plugins = [];
252
249
  for (const entry of (await fs.readdir(pluginsRoot, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name))) {
253
250
  if (entry.name === path.basename(metadataPath) || !entry.isDirectory()) {
254
251
  if (entry.isSymbolicLink())
255
- throw new Error(`Workspace plugin source must not be a symbolic link: ${entry.name}`);
252
+ throw new Error(`Catalog Workspace plugin source must not be a symbolic link: ${entry.name}`);
256
253
  continue;
257
254
  }
258
255
  const pluginRoot = path.join(pluginsRoot, entry.name);
259
256
  const pluginStat = await fs.lstat(pluginRoot);
260
257
  if (!pluginStat.isDirectory() || pluginStat.isSymbolicLink() || !isInside(root, await fs.realpath(pluginRoot))) {
261
- throw new Error(`Workspace plugin source must be a regular in-workspace directory: ${pluginRoot}`);
258
+ throw new Error(`Catalog Workspace plugin source must be a regular in-workspace directory: ${pluginRoot}`);
262
259
  }
263
260
  const manifestDir = path.join(pluginRoot, path.dirname(SOURCE_MANIFEST_PATH));
264
261
  const manifestPath = path.join(pluginRoot, SOURCE_MANIFEST_PATH);
265
262
  await assertDirectoryWithoutSymlinks(manifestDir, 'Plugin manifest directory');
266
263
  await readRegularJson(manifestPath, 'Plugin manifest');
267
264
  if (!isInside(root, await fs.realpath(manifestPath)))
268
- throw new Error(`Plugin manifest resolves outside the workspace: ${manifestPath}`);
265
+ throw new Error(`Plugin manifest resolves outside the Catalog Workspace: ${manifestPath}`);
269
266
  const plugin = await readPluginManifest(pluginRoot, { mode: 'source', reservedImports: options.reservedImports });
270
267
  if (plugin.manifest.name !== entry.name) {
271
268
  throw new Error(`Plugin directory name ${JSON.stringify(entry.name)} must match manifest name ${JSON.stringify(plugin.manifest.name)}.`);
272
269
  }
273
270
  plugins.push(plugin);
274
271
  }
275
- return { workspaceRoot: root, metadataPath, metadata, plugins };
272
+ return {
273
+ workspaceRoot: root,
274
+ markerPath: marker.markerPath,
275
+ workspaceIdentity: marker.workspaceIdentity,
276
+ metadataPath,
277
+ metadata,
278
+ plugins,
279
+ };
276
280
  }
277
- export async function setWorkspaceCatalogVersion(workspaceRoot, version, options = {}) {
278
- const current = await readPluginWorkspace(workspaceRoot, options);
281
+ export function resolveCatalogVersionMutation(currentVersion, mutation) {
282
+ const current = assertCatalogVersion(currentVersion, 'current catalog version');
283
+ const hasVersion = mutation.version !== undefined;
284
+ const hasBump = mutation.bump !== undefined;
285
+ if (hasVersion === hasBump) {
286
+ throw new Error('Catalog version mutation requires exactly one of version or bump.');
287
+ }
288
+ if (mutation.version !== undefined) {
289
+ return assertCatalogVersion(mutation.version, 'catalog version');
290
+ }
291
+ if (!['major', 'minor', 'patch'].includes(mutation.bump)) {
292
+ throw new Error('Catalog version bump must be major, minor, or patch.');
293
+ }
294
+ const [major, minor, patch] = current.split('-', 1)[0].split('.').map(BigInt);
295
+ if (mutation.bump === 'major')
296
+ return `${major + 1n}.0.0`;
297
+ if (mutation.bump === 'minor')
298
+ return `${major}.${minor + 1n}.0`;
299
+ return `${major}.${minor}.${patch + 1n}`;
300
+ }
301
+ export async function mutateCatalogWorkspaceVersion(workspaceRoot, mutation, options = {}) {
302
+ const current = await readCatalogWorkspace(workspaceRoot, options);
303
+ const version = resolveCatalogVersionMutation(current.metadata.catalog.version, mutation);
304
+ if (version === current.metadata.catalog.version)
305
+ return current.metadata;
279
306
  const metadata = {
280
307
  ...current.metadata,
281
- catalog: { version: assertCatalogVersion(version, 'catalog version') },
308
+ catalog: {
309
+ version,
310
+ },
282
311
  };
283
312
  await writeJsonAtomic(current.metadataPath, metadata);
284
313
  return metadata;
@@ -481,21 +510,21 @@ function normalizeCatalogRelease(raw, catalogVersion) {
481
510
  contentDigest: release.contentDigest,
482
511
  };
483
512
  }
484
- export async function buildWorkspaceCatalog(options) {
485
- const workspace = await readPluginWorkspace(options.workspaceRoot, { reservedImports: options.reservedImports });
486
- const plugins = orderPlugins(workspace.plugins);
487
- const resolved = await resolveWorkspaceOutputPath({
488
- workspaceRoot: workspace.workspaceRoot,
513
+ export async function buildCatalogWorkspace(options) {
514
+ const catalogWorkspace = await readCatalogWorkspace(options.workspaceRoot, { reservedImports: options.reservedImports });
515
+ const plugins = orderPlugins(catalogWorkspace.plugins);
516
+ const resolved = await resolveCatalogWorkspaceOutputPath({
517
+ workspaceRoot: catalogWorkspace.workspaceRoot,
489
518
  outputPath: options.outputPath ?? DEFAULT_CATALOG_OUTPUT_PATH,
490
519
  kind: 'catalog',
491
520
  });
492
- const catalogVersion = workspace.metadata.catalog.version;
521
+ const catalogVersion = catalogWorkspace.metadata.catalog.version;
493
522
  const channel = deriveCatalogChannel(catalogVersion);
494
523
  const pluginNames = plugins.map((plugin) => plugin.manifest.name).sort();
495
- const identity = sourceIdentity(workspace.metadata, plugins);
524
+ const identity = sourceIdentity(catalogWorkspace.metadata, plugins);
496
525
  const marker = generatedOutputMarker({
497
526
  kind: 'catalog',
498
- workspaceIdentity: await readWorkspaceOwnershipIdentity(workspace.workspaceRoot),
527
+ workspaceIdentity: catalogWorkspace.workspaceIdentity,
499
528
  sourceIdentity: identity,
500
529
  });
501
530
  await replaceGeneratedOutput({
@@ -508,7 +537,7 @@ export async function buildWorkspaceCatalog(options) {
508
537
  const outputRoot = path.join(stagingRoot, 'plugins', plugin.manifest.name);
509
538
  await fs.ensureDir(outputRoot);
510
539
  await options.adapter.build({
511
- workspaceRoot: workspace.workspaceRoot,
540
+ workspaceRoot: catalogWorkspace.workspaceRoot,
512
541
  plugin,
513
542
  plugins,
514
543
  dependencies: closure.slice(0, -1),
@@ -519,8 +548,8 @@ export async function buildWorkspaceCatalog(options) {
519
548
  const byPlatform = (platform) => bundledPlugins
520
549
  .filter((plugin) => resolveSupportedPlatforms(plugin.manifest).has(platform))
521
550
  .sort((left, right) => left.manifest.name.localeCompare(right.manifest.name));
522
- await fs.outputJson(path.join(stagingRoot, '.agents', 'plugins', 'marketplace.json'), createMarketplaceManifest(workspace.metadata, byPlatform('codex')), { spaces: 2 });
523
- await fs.outputJson(path.join(stagingRoot, '.claude-plugin', 'marketplace.json'), createMarketplaceManifest(workspace.metadata, byPlatform('claude')), { spaces: 2 });
551
+ await fs.outputJson(path.join(stagingRoot, '.agents', 'plugins', 'marketplace.json'), createMarketplaceManifest(catalogWorkspace.metadata, byPlatform('codex')), { spaces: 2 });
552
+ await fs.outputJson(path.join(stagingRoot, '.claude-plugin', 'marketplace.json'), createMarketplaceManifest(catalogWorkspace.metadata, byPlatform('claude')), { spaces: 2 });
524
553
  let minVersion = '0.0.0';
525
554
  for (const plugin of bundledPlugins) {
526
555
  const candidate = plugin.manifest.compatibility.aw.minVersion;
@@ -536,8 +565,8 @@ export async function buildWorkspaceCatalog(options) {
536
565
  channel,
537
566
  compatibility: { aw: { minVersion } },
538
567
  marketplace: {
539
- owner: workspace.metadata.owner,
540
- interface: workspace.metadata.interface,
568
+ owner: catalogWorkspace.metadata.owner,
569
+ interface: catalogWorkspace.metadata.interface,
541
570
  },
542
571
  release: { state: 'development' },
543
572
  entries,
@@ -578,15 +607,15 @@ export async function validatePublishedCatalogSnapshot(snapshotDirectory) {
578
607
  throw new Error('Published Catalog snapshot must include a plugin release ledger.');
579
608
  return { ...validation, ledger: validation.ledger };
580
609
  }
581
- export async function preparePublishedWorkspaceCatalog(options) {
610
+ export async function preparePublishedCatalogWorkspace(options) {
582
611
  if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(options.sourceHead)) {
583
612
  throw new Error('Published Catalog sourceHead must be a committed Git object ID.');
584
613
  }
585
- const workspace = await readPluginWorkspace(options.workspaceRoot, { reservedImports: options.reservedImports });
586
- for (const plugin of workspace.plugins) {
614
+ const catalogWorkspace = await readCatalogWorkspace(options.workspaceRoot, { reservedImports: options.reservedImports });
615
+ for (const plugin of catalogWorkspace.plugins) {
587
616
  assertStablePluginVersion(plugin.manifest.version, `${plugin.manifest.name} source version`);
588
617
  }
589
- const catalogVersion = workspace.metadata.catalog.version;
618
+ const catalogVersion = catalogWorkspace.metadata.catalog.version;
590
619
  const channel = deriveCatalogChannel(catalogVersion);
591
620
  const previous = options.previousSnapshotRoot
592
621
  ? await validatePublishedCatalogSnapshot(options.previousSnapshotRoot)
@@ -601,10 +630,10 @@ export async function preparePublishedWorkspaceCatalog(options) {
601
630
  const temporaryRelative = path.join('.aw', 'publish', `.catalog-preparation-${crypto.randomUUID()}`);
602
631
  const finalRelative = options.outputPath
603
632
  ?? `${DEFAULT_PUBLISHED_CATALOG_OUTPUT_PATH}-v${catalogVersion}`;
604
- let temporaryRoot = path.resolve(workspace.workspaceRoot, temporaryRelative);
633
+ let temporaryRoot = path.resolve(catalogWorkspace.workspaceRoot, temporaryRelative);
605
634
  try {
606
- const built = await buildWorkspaceCatalog({
607
- workspaceRoot: workspace.workspaceRoot,
635
+ const built = await buildCatalogWorkspace({
636
+ workspaceRoot: catalogWorkspace.workspaceRoot,
608
637
  outputPath: temporaryRelative,
609
638
  adapter: options.adapter,
610
639
  reservedImports: options.reservedImports,
@@ -614,17 +643,19 @@ export async function preparePublishedWorkspaceCatalog(options) {
614
643
  const contentDigests = new Map(development.manifest.entries.map((entry) => [entry.name, entry.contentDigest]));
615
644
  const overrides = channel === 'beta'
616
645
  ? resolveBetaPluginVersionOverrides({
617
- plugins: workspace.plugins,
646
+ plugins: catalogWorkspace.plugins,
618
647
  contentDigests,
619
648
  ledger: previousLedger,
620
649
  })
621
- : Object.fromEntries(workspace.plugins.map((plugin) => [plugin.manifest.name, plugin.manifest.version]));
650
+ : Object.fromEntries(catalogWorkspace.plugins.map((plugin) => [plugin.manifest.name, plugin.manifest.version]));
622
651
  await rewritePublishedPluginVersions(temporaryRoot, overrides);
623
- const bundledPlugins = await Promise.all(workspace.plugins.map((plugin) => readPluginManifest(path.join(temporaryRoot, 'plugins', plugin.manifest.name), { mode: 'bundle' })));
652
+ const bundledPlugins = await Promise.all(catalogWorkspace.plugins.map((plugin) => readPluginManifest(path.join(temporaryRoot, 'plugins', plugin.manifest.name), { mode: 'bundle' })));
624
653
  const entries = await Promise.all(bundledPlugins.sort((left, right) => left.manifest.name.localeCompare(right.manifest.name))
625
654
  .map((plugin) => expectedCatalogEntry(plugin, plugin.rootDir)));
626
655
  const manifestWithoutRelease = {
627
656
  ...development.manifest,
657
+ catalogVersion,
658
+ channel,
628
659
  entries,
629
660
  };
630
661
  const contentDigest = publishedContentDigest(manifestWithoutRelease);
@@ -648,8 +679,8 @@ export async function preparePublishedWorkspaceCatalog(options) {
648
679
  fs.writeFile(path.join(temporaryRoot, PLUGIN_RELEASE_LEDGER_FILENAME), `${JSON.stringify(ledger, null, 2)}\n`),
649
680
  ]);
650
681
  const prepared = await validatePublishedCatalogSnapshot(temporaryRoot);
651
- const resolved = await resolveWorkspaceOutputPath({
652
- workspaceRoot: workspace.workspaceRoot,
682
+ const resolved = await resolveCatalogWorkspaceOutputPath({
683
+ workspaceRoot: catalogWorkspace.workspaceRoot,
653
684
  outputPath: finalRelative,
654
685
  kind: 'catalog',
655
686
  });
@@ -697,27 +728,27 @@ function dependencyClosure(plugin, all) {
697
728
  visit(plugin);
698
729
  return result;
699
730
  }
700
- export async function buildWorkspacePlugin(options) {
701
- const workspace = await readPluginWorkspace(options.workspaceRoot, { reservedImports: options.reservedImports });
702
- assertDependencyGraph(workspace.plugins);
703
- const requestedPluginRoot = path.resolve(workspace.workspaceRoot, options.pluginDirectory);
731
+ export async function buildCatalogWorkspacePlugin(options) {
732
+ const catalogWorkspace = await readCatalogWorkspace(options.workspaceRoot, { reservedImports: options.reservedImports });
733
+ assertDependencyGraph(catalogWorkspace.plugins);
734
+ const requestedPluginRoot = path.resolve(catalogWorkspace.workspaceRoot, options.pluginDirectory);
704
735
  const pluginRoot = await fs.realpath(requestedPluginRoot);
705
- if (!isInside(workspace.workspaceRoot, pluginRoot))
706
- throw new Error('Plugin directory must stay inside the workspace.');
707
- const plugin = workspace.plugins.find((entry) => entry.rootDir === pluginRoot);
736
+ if (!isInside(catalogWorkspace.workspaceRoot, pluginRoot))
737
+ throw new Error('Plugin directory must stay inside the Catalog Workspace.');
738
+ const plugin = catalogWorkspace.plugins.find((entry) => entry.rootDir === pluginRoot);
708
739
  if (!plugin) {
709
- throw new Error(`Plugin directory is not part of this workspace source: ${pluginRoot}`);
740
+ throw new Error(`Plugin directory is not part of this Catalog Workspace source: ${pluginRoot}`);
710
741
  }
711
- const closure = dependencyClosure(plugin, workspace.plugins);
742
+ const closure = dependencyClosure(plugin, catalogWorkspace.plugins);
712
743
  const identity = sha256(normalizeManifestDigestInput(plugin.manifest));
713
744
  const marker = generatedOutputMarker({
714
745
  kind: 'plugin',
715
- workspaceIdentity: await readWorkspaceOwnershipIdentity(workspace.workspaceRoot),
746
+ workspaceIdentity: catalogWorkspace.workspaceIdentity,
716
747
  sourceIdentity: identity,
717
748
  sourceName: plugin.manifest.name,
718
749
  });
719
- const resolved = await resolveWorkspaceOutputPath({
720
- workspaceRoot: workspace.workspaceRoot,
750
+ const resolved = await resolveCatalogWorkspaceOutputPath({
751
+ workspaceRoot: catalogWorkspace.workspaceRoot,
721
752
  outputPath: options.outputPath ?? path.join(DEFAULT_PLUGIN_OUTPUT_PATH, plugin.manifest.name),
722
753
  kind: 'plugin',
723
754
  });
@@ -725,9 +756,9 @@ export async function buildWorkspacePlugin(options) {
725
756
  outputRoot: resolved.outputRoot,
726
757
  force: options.force === true,
727
758
  build: (stagingRoot) => options.adapter.build({
728
- workspaceRoot: workspace.workspaceRoot,
759
+ workspaceRoot: catalogWorkspace.workspaceRoot,
729
760
  plugin,
730
- plugins: workspace.plugins,
761
+ plugins: catalogWorkspace.plugins,
731
762
  dependencies: closure.slice(0, -1),
732
763
  outputRoot: stagingRoot,
733
764
  }).then(() => fs.writeJson(path.join(stagingRoot, GENERATED_OUTPUT_MARKER_FILENAME), marker, { spaces: 2 })),
@@ -765,7 +796,7 @@ async function assertGeneratedOutput(root, expected) {
765
796
  if (!isDeepStrictEqual(Object.keys(marker).sort(), expectedKeys)
766
797
  || marker.schemaVersion !== 1
767
798
  || typeof marker.workspaceIdentity !== 'string'
768
- || !digestPattern.test(marker.workspaceIdentity)
799
+ || marker.workspaceIdentity.trim().length === 0
769
800
  || typeof marker.sourceIdentity !== 'string'
770
801
  || !digestPattern.test(marker.sourceIdentity)) {
771
802
  throw new Error(`Refusing to replace foreign plugin output: ${root}`);
@@ -823,7 +854,7 @@ export async function validateCatalogSnapshot(snapshotDirectory) {
823
854
  || marker.schemaVersion !== 1
824
855
  || marker.kind !== 'catalog'
825
856
  || typeof marker.workspaceIdentity !== 'string'
826
- || !digestPattern.test(marker.workspaceIdentity)
857
+ || marker.workspaceIdentity.trim().length === 0
827
858
  || typeof marker.sourceIdentity !== 'string'
828
859
  || !digestPattern.test(marker.sourceIdentity)) {
829
860
  throw new Error('Invalid catalog generated output marker.');