@lesliechan721/aw-plugin-core 0.1.0-beta.8 → 0.1.0-beta.9
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/aw-capabilities.d.ts +1 -1
- package/dist/aw-capabilities.js +3 -2
- package/dist/aw-capabilities.js.map +1 -1
- package/dist/catalog-workspace-resolution.d.ts +3 -10
- package/dist/catalog-workspace-resolution.js +4 -73
- package/dist/catalog-workspace-resolution.js.map +1 -1
- package/dist/generated/plugin-manifest-v2.d.ts +13 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest-contract.js +21 -2
- package/dist/manifest-contract.js.map +1 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +1 -1
- package/dist/plugin-authoring-capabilities.js +2 -1
- package/dist/plugin-authoring-capabilities.js.map +1 -1
- package/dist/plugin-requirements.d.ts +2 -1
- package/dist/plugin-requirements.js +132 -272
- package/dist/plugin-requirements.js.map +1 -1
- package/dist/types.d.ts +13 -9
- package/dist/workspace.d.ts +8 -16
- package/dist/workspace.js +103 -236
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/schemas/plugin-manifest-v2.json +27 -3
- package/dist/plugin-release.d.ts +0 -30
- package/dist/plugin-release.js +0 -187
- package/dist/plugin-release.js.map +0 -1
package/dist/types.d.ts
CHANGED
|
@@ -55,6 +55,11 @@ export interface BuildConfigManifestOverrides {
|
|
|
55
55
|
export interface BuildConfigRuntimeExport {
|
|
56
56
|
name: string;
|
|
57
57
|
source: string;
|
|
58
|
+
requires: {
|
|
59
|
+
aw: {
|
|
60
|
+
capabilities: string[];
|
|
61
|
+
};
|
|
62
|
+
};
|
|
58
63
|
}
|
|
59
64
|
export interface BuildConfigImportReference {
|
|
60
65
|
plugin: string;
|
|
@@ -250,6 +255,8 @@ export interface ArtifactRuntimeImportDefinition extends BuildConfigImportRefere
|
|
|
250
255
|
providerPluginId: PluginId;
|
|
251
256
|
providerVersion: PluginVersion;
|
|
252
257
|
source: string;
|
|
258
|
+
requiredCapabilities: string[];
|
|
259
|
+
capabilitySetDigest: string;
|
|
253
260
|
};
|
|
254
261
|
}
|
|
255
262
|
export interface PluginArtifactTargetDefinition {
|
|
@@ -331,7 +338,7 @@ export interface CatalogManifestEntry {
|
|
|
331
338
|
artifactDigest: string;
|
|
332
339
|
manifestDigest: string;
|
|
333
340
|
}
|
|
334
|
-
export interface
|
|
341
|
+
export interface CatalogManifestV6Entry extends CatalogManifestEntry {
|
|
335
342
|
name: PluginId;
|
|
336
343
|
contentDigest: string;
|
|
337
344
|
}
|
|
@@ -339,23 +346,20 @@ export interface CatalogMarketplaceIdentity {
|
|
|
339
346
|
owner: MarketplaceOwner;
|
|
340
347
|
interface: MarketplaceInterfaceMeta;
|
|
341
348
|
}
|
|
342
|
-
export type
|
|
349
|
+
export type CatalogManifestV6Release = {
|
|
343
350
|
state: 'development';
|
|
344
351
|
} | {
|
|
345
352
|
state: 'published';
|
|
346
|
-
targetVersion: string;
|
|
347
|
-
planId: string;
|
|
348
353
|
sourceHead: string;
|
|
349
|
-
contentDigest: string;
|
|
350
354
|
};
|
|
351
|
-
export interface
|
|
352
|
-
schemaVersion:
|
|
355
|
+
export interface CatalogManifestV6 {
|
|
356
|
+
schemaVersion: 6;
|
|
353
357
|
catalogVersion: string;
|
|
354
358
|
channel: CatalogChannel;
|
|
355
359
|
requires: PluginRequirements;
|
|
356
360
|
marketplace: CatalogMarketplaceIdentity;
|
|
357
|
-
release:
|
|
358
|
-
entries:
|
|
361
|
+
release: CatalogManifestV6Release;
|
|
362
|
+
entries: CatalogManifestV6Entry[];
|
|
359
363
|
}
|
|
360
364
|
export interface PlatformPluginManifest {
|
|
361
365
|
name: string;
|
package/dist/workspace.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type PluginReleaseLedger } from './plugin-release.js';
|
|
2
1
|
import type { ReservedImportRegistry } from './manifest-contract.js';
|
|
3
|
-
import type { CatalogChannel,
|
|
2
|
+
import type { CatalogChannel, CatalogManifestV6, MarketplaceSourceMeta, PluginMeta } from './types.js';
|
|
4
3
|
export declare const DEFAULT_CATALOG_WORKSPACE_VERSION = "0.1.0";
|
|
5
4
|
export declare const DEFAULT_CATALOG_OUTPUT_PATH: string;
|
|
6
5
|
export declare const DEFAULT_PUBLISHED_CATALOG_OUTPUT_PATH: string;
|
|
@@ -50,6 +49,10 @@ export declare function resolveCatalogWorkspaceOutputPath(options: {
|
|
|
50
49
|
workspaceRoot: string;
|
|
51
50
|
outputRoot: string;
|
|
52
51
|
}>;
|
|
52
|
+
export declare function readCatalogWorkspaceMarker(workspaceRoot: string): Promise<{
|
|
53
|
+
markerPath: string;
|
|
54
|
+
workspaceIdentity: string;
|
|
55
|
+
}>;
|
|
53
56
|
export declare function initializeCatalogWorkspace(options: CatalogWorkspaceInitOptions): Promise<{
|
|
54
57
|
workspaceRoot: string;
|
|
55
58
|
markerPath: string;
|
|
@@ -76,8 +79,7 @@ export declare function buildCatalogWorkspace(options: {
|
|
|
76
79
|
}>;
|
|
77
80
|
export declare function validatePublishedCatalogSnapshot(snapshotDirectory: string): Promise<{
|
|
78
81
|
rootDir: string;
|
|
79
|
-
manifest:
|
|
80
|
-
ledger: PluginReleaseLedger;
|
|
82
|
+
manifest: CatalogManifestV6;
|
|
81
83
|
snapshotDigest: string;
|
|
82
84
|
}>;
|
|
83
85
|
export declare function preparePublishedCatalogWorkspace(options: {
|
|
@@ -93,8 +95,6 @@ export declare function preparePublishedCatalogWorkspace(options: {
|
|
|
93
95
|
channel: CatalogChannel;
|
|
94
96
|
pluginNames: string[];
|
|
95
97
|
snapshotDigest: string;
|
|
96
|
-
contentDigest: string;
|
|
97
|
-
planId: string;
|
|
98
98
|
}>;
|
|
99
99
|
export declare function buildCatalogWorkspacePlugin(options: {
|
|
100
100
|
workspaceRoot: string;
|
|
@@ -111,20 +111,12 @@ export declare function buildCatalogWorkspacePlugin(options: {
|
|
|
111
111
|
}>;
|
|
112
112
|
export declare function validateCatalogSnapshotTransport(snapshotDirectory: string): Promise<{
|
|
113
113
|
rootDir: string;
|
|
114
|
-
manifest:
|
|
115
|
-
ledger?: PluginReleaseLedger;
|
|
116
|
-
snapshotDigest: string;
|
|
117
|
-
}>;
|
|
118
|
-
export declare function validatePublishedCatalogPublicationParent(snapshotDirectory: string): Promise<{
|
|
119
|
-
rootDir: string;
|
|
120
|
-
catalogVersion: string;
|
|
121
|
-
ledger: PluginReleaseLedger;
|
|
114
|
+
manifest: CatalogManifestV6;
|
|
122
115
|
snapshotDigest: string;
|
|
123
116
|
}>;
|
|
124
117
|
type CatalogSnapshotValidationResult = {
|
|
125
118
|
rootDir: string;
|
|
126
|
-
manifest:
|
|
127
|
-
ledger?: PluginReleaseLedger;
|
|
119
|
+
manifest: CatalogManifestV6;
|
|
128
120
|
snapshotDigest: string;
|
|
129
121
|
};
|
|
130
122
|
export declare function validateCatalogSnapshotPluginEntriesComplete(snapshotDirectory: string, pluginNames: readonly string[]): Promise<CatalogSnapshotValidationResult>;
|
package/dist/workspace.js
CHANGED
|
@@ -2,10 +2,9 @@ import crypto from 'node:crypto';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
|
-
import { assertCatalogVersion, assertStrictSemVer, compareStrictSemVer, MARKETPLACE_SOURCE_PATH, SOURCE_MANIFEST_PATH, createMarketplaceManifest, createMarketplaceManifestEntry, normalizeCatalogManifestBase, normalizeCatalogMarketplaceIdentity, normalizeCatalogManifestEntries, normalizeManifestDigestInput, readMarketplaceSourceMeta, readPluginManifest, validatePlatformManifestProjection, } from './manifest-contract.js';
|
|
5
|
+
import { assertCatalogVersion, assertStrictSemVer, compareStrictSemVer, parseStrictSemVer, 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 { createAwPluginRequirementsReceipt, normalizeAwCapabilities, parseAwPluginRequirementsReceipt, } from './aw-capabilities.js';
|
|
8
|
-
import { appendCatalogToPluginReleaseLedger, assertStablePluginVersion, PLUGIN_RELEASE_LEDGER_FILENAME, PLUGIN_RELEASE_LEDGER_SCHEMA_VERSION, resolveBetaPluginVersionOverrides, validatePluginReleaseLedgerAgainstManifest, } from './plugin-release.js';
|
|
9
8
|
export const DEFAULT_CATALOG_WORKSPACE_VERSION = '0.1.0';
|
|
10
9
|
export const DEFAULT_CATALOG_OUTPUT_PATH = path.join('.aw', 'catalog');
|
|
11
10
|
export const DEFAULT_PUBLISHED_CATALOG_OUTPUT_PATH = path.join('.aw', 'publish', 'catalog');
|
|
@@ -140,9 +139,18 @@ function validateCatalogWorkspaceMarker(value) {
|
|
|
140
139
|
}
|
|
141
140
|
return { workspaceIdentity: marker.workspaceIdentity };
|
|
142
141
|
}
|
|
143
|
-
async function readCatalogWorkspaceMarker(workspaceRoot) {
|
|
142
|
+
export async function readCatalogWorkspaceMarker(workspaceRoot) {
|
|
144
143
|
const markerPath = path.join(workspaceRoot, CATALOG_WORKSPACE_MARKER_FILENAME);
|
|
145
|
-
|
|
144
|
+
let value;
|
|
145
|
+
try {
|
|
146
|
+
value = await readRegularJson(markerPath, 'Catalog Workspace marker');
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (error instanceof SyntaxError)
|
|
150
|
+
throw new Error('Invalid Catalog Workspace marker.');
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
const marker = validateCatalogWorkspaceMarker(value);
|
|
146
154
|
return { markerPath, workspaceIdentity: marker.workspaceIdentity };
|
|
147
155
|
}
|
|
148
156
|
async function readRegularJson(filePath, label) {
|
|
@@ -400,22 +408,6 @@ function stableStringify(value) {
|
|
|
400
408
|
function sha256(value) {
|
|
401
409
|
return `sha256:${crypto.createHash('sha256').update(value).digest('hex')}`;
|
|
402
410
|
}
|
|
403
|
-
function publishedContentDigest(manifest) {
|
|
404
|
-
return sha256(stableStringify({
|
|
405
|
-
schemaIdentity: { schemaVersion: manifest.schemaVersion },
|
|
406
|
-
marketplaceMetadata: {
|
|
407
|
-
name: 'aw',
|
|
408
|
-
owner: manifest.marketplace.owner,
|
|
409
|
-
interface: manifest.marketplace.interface,
|
|
410
|
-
},
|
|
411
|
-
requires: manifest.requires,
|
|
412
|
-
plugins: manifest.entries.map((entry) => ({
|
|
413
|
-
name: entry.name,
|
|
414
|
-
targetVersion: entry.version.replace(/-beta\.[1-9]\d*$/u, ''),
|
|
415
|
-
contentDigest: entry.contentDigest,
|
|
416
|
-
})).sort((left, right) => left.name.localeCompare(right.name)),
|
|
417
|
-
}));
|
|
418
|
-
}
|
|
419
411
|
const SOURCE_IDENTITY_EXCLUDED_DIRECTORIES = new Set([
|
|
420
412
|
'.git', '.aw', 'node_modules', 'evals', 'reports',
|
|
421
413
|
]);
|
|
@@ -558,7 +550,7 @@ async function validateBundleRuntimeRequirementsReceipts(plugin, pluginRoot) {
|
|
|
558
550
|
}
|
|
559
551
|
}
|
|
560
552
|
}
|
|
561
|
-
function normalizeCatalogRelease(raw
|
|
553
|
+
function normalizeCatalogRelease(raw) {
|
|
562
554
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
563
555
|
throw new Error('Invalid catalog manifest release: expected an object.');
|
|
564
556
|
}
|
|
@@ -569,33 +561,16 @@ function normalizeCatalogRelease(raw, catalogVersion) {
|
|
|
569
561
|
}
|
|
570
562
|
return { state: 'development' };
|
|
571
563
|
}
|
|
572
|
-
const allowed = new Set(['state', '
|
|
564
|
+
const allowed = new Set(['state', 'sourceHead']);
|
|
573
565
|
if (release.state !== 'published' || Object.keys(release).some((key) => !allowed.has(key))) {
|
|
574
566
|
throw new Error('Invalid published catalog release.');
|
|
575
567
|
}
|
|
576
|
-
const targetVersion = assertStrictSemVer(release.targetVersion, 'catalog release targetVersion');
|
|
577
|
-
if (targetVersion.includes('-') || targetVersion.includes('+')) {
|
|
578
|
-
throw new Error('Invalid catalog release targetVersion: expected a stable version.');
|
|
579
|
-
}
|
|
580
|
-
if (targetVersion !== catalogVersion.replace(/-beta\.[1-9]\d*$/u, '')) {
|
|
581
|
-
throw new Error('Invalid catalog release targetVersion: must match catalogVersion.');
|
|
582
|
-
}
|
|
583
|
-
const digestPattern = /^sha256:[0-9a-f]{64}$/u;
|
|
584
|
-
if (typeof release.planId !== 'string' || !digestPattern.test(release.planId)) {
|
|
585
|
-
throw new Error('Invalid catalog release planId.');
|
|
586
|
-
}
|
|
587
|
-
if (typeof release.contentDigest !== 'string' || !digestPattern.test(release.contentDigest)) {
|
|
588
|
-
throw new Error('Invalid catalog release contentDigest.');
|
|
589
|
-
}
|
|
590
568
|
if (typeof release.sourceHead !== 'string' || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(release.sourceHead)) {
|
|
591
569
|
throw new Error('Invalid catalog release sourceHead.');
|
|
592
570
|
}
|
|
593
571
|
return {
|
|
594
572
|
state: 'published',
|
|
595
|
-
targetVersion,
|
|
596
|
-
planId: release.planId,
|
|
597
573
|
sourceHead: release.sourceHead,
|
|
598
|
-
contentDigest: release.contentDigest,
|
|
599
574
|
};
|
|
600
575
|
}
|
|
601
576
|
export async function buildCatalogWorkspace(options) {
|
|
@@ -643,10 +618,10 @@ export async function buildCatalogWorkspace(options) {
|
|
|
643
618
|
.sort((left, right) => left.manifest.name.localeCompare(right.manifest.name))
|
|
644
619
|
.map((plugin) => expectedCatalogEntry(plugin, plugin.rootDir)));
|
|
645
620
|
const manifest = {
|
|
646
|
-
schemaVersion:
|
|
621
|
+
schemaVersion: 6,
|
|
647
622
|
catalogVersion,
|
|
648
623
|
channel,
|
|
649
|
-
requires: { aw: { capabilities: ['aw.catalog-snapshot.
|
|
624
|
+
requires: { aw: { capabilities: ['aw.catalog-snapshot.v6'] } },
|
|
650
625
|
marketplace: {
|
|
651
626
|
owner: catalogWorkspace.metadata.owner,
|
|
652
627
|
interface: catalogWorkspace.metadata.interface,
|
|
@@ -692,14 +667,77 @@ async function rewritePublishedPluginVersions(snapshotRoot, overrides) {
|
|
|
692
667
|
}
|
|
693
668
|
}
|
|
694
669
|
}
|
|
670
|
+
function assertStablePluginVersion(version, fieldName) {
|
|
671
|
+
const parsed = parseStrictSemVer(version, fieldName);
|
|
672
|
+
if (parsed.prerelease.length > 0 || parsed.build.length > 0) {
|
|
673
|
+
throw new Error(`Invalid ${fieldName}: expected a stable SemVer without prerelease or build metadata.`);
|
|
674
|
+
}
|
|
675
|
+
return version;
|
|
676
|
+
}
|
|
677
|
+
function stableVersionBase(version, fieldName) {
|
|
678
|
+
const parsed = parseStrictSemVer(version, fieldName);
|
|
679
|
+
return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
|
|
680
|
+
}
|
|
681
|
+
function resolvePublishedPluginVersions(options) {
|
|
682
|
+
const previousEntries = new Map(options.previous?.entries.map((entry) => [entry.name, entry]) ?? []);
|
|
683
|
+
const versions = {};
|
|
684
|
+
for (const plugin of options.plugins) {
|
|
685
|
+
const name = plugin.manifest.name;
|
|
686
|
+
const sourceVersion = assertStablePluginVersion(plugin.manifest.version, `${name} source version`);
|
|
687
|
+
const contentDigest = options.contentDigests.get(name);
|
|
688
|
+
if (!contentDigest)
|
|
689
|
+
throw new Error(`Missing release content digest for plugin ${JSON.stringify(name)}.`);
|
|
690
|
+
const previous = previousEntries.get(name);
|
|
691
|
+
if (!previous) {
|
|
692
|
+
versions[name] = options.channel === 'beta' ? `${sourceVersion}-beta.1` : sourceVersion;
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
const previousVersion = assertStrictSemVer(previous.version, `${name} previous published version`);
|
|
696
|
+
if (options.channel === 'latest') {
|
|
697
|
+
const comparison = compareStrictSemVer(sourceVersion, previousVersion);
|
|
698
|
+
if (comparison < 0) {
|
|
699
|
+
throw new Error(`Plugin ${name} source version ${sourceVersion} must not be lower than previous ${previousVersion}.`);
|
|
700
|
+
}
|
|
701
|
+
if (comparison === 0 && previous.contentDigest !== contentDigest) {
|
|
702
|
+
throw new Error(`Plugin ${name} version ${sourceVersion} maps to different content. Increase the source plugin version.`);
|
|
703
|
+
}
|
|
704
|
+
versions[name] = sourceVersion;
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const previousBase = stableVersionBase(previousVersion, `${name} previous published version`);
|
|
708
|
+
const baseComparison = compareStrictSemVer(sourceVersion, previousBase);
|
|
709
|
+
if (baseComparison < 0) {
|
|
710
|
+
throw new Error(`Plugin ${name} source version ${sourceVersion} must not be lower than previous base ${previousBase}.`);
|
|
711
|
+
}
|
|
712
|
+
if (baseComparison > 0) {
|
|
713
|
+
versions[name] = `${sourceVersion}-beta.1`;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const parsedPrevious = parseStrictSemVer(previousVersion, `${name} previous published version`);
|
|
717
|
+
if (parsedPrevious.prerelease.length === 0) {
|
|
718
|
+
if (previous.contentDigest !== contentDigest) {
|
|
719
|
+
throw new Error(`Plugin ${name} stable version ${sourceVersion} maps to different content. Increase the source plugin version.`);
|
|
720
|
+
}
|
|
721
|
+
versions[name] = previousVersion;
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
if (parsedPrevious.prerelease.length !== 2
|
|
725
|
+
|| parsedPrevious.prerelease[0] !== 'beta'
|
|
726
|
+
|| !/^[1-9]\d*$/u.test(parsedPrevious.prerelease[1])) {
|
|
727
|
+
throw new Error(`Plugin ${name} previous version ${previousVersion} is not a stable or beta publication version.`);
|
|
728
|
+
}
|
|
729
|
+
versions[name] = previous.contentDigest === contentDigest
|
|
730
|
+
? previousVersion
|
|
731
|
+
: `${sourceVersion}-beta.${Number(parsedPrevious.prerelease[1]) + 1}`;
|
|
732
|
+
}
|
|
733
|
+
return versions;
|
|
734
|
+
}
|
|
695
735
|
export async function validatePublishedCatalogSnapshot(snapshotDirectory) {
|
|
696
736
|
const validation = await validateCatalogSnapshotComplete(snapshotDirectory);
|
|
697
737
|
if (validation.manifest.release.state !== 'published') {
|
|
698
738
|
throw new Error('Published Catalog snapshot must have release.state published.');
|
|
699
739
|
}
|
|
700
|
-
|
|
701
|
-
throw new Error('Published Catalog snapshot must include a plugin release ledger.');
|
|
702
|
-
return { ...validation, ledger: validation.ledger };
|
|
740
|
+
return validation;
|
|
703
741
|
}
|
|
704
742
|
export async function preparePublishedCatalogWorkspace(options) {
|
|
705
743
|
if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(options.sourceHead)) {
|
|
@@ -712,15 +750,11 @@ export async function preparePublishedCatalogWorkspace(options) {
|
|
|
712
750
|
const catalogVersion = catalogWorkspace.metadata.catalog.version;
|
|
713
751
|
const channel = deriveCatalogChannel(catalogVersion);
|
|
714
752
|
const previous = options.previousSnapshotRoot
|
|
715
|
-
? await
|
|
753
|
+
? await validatePublishedCatalogSnapshot(options.previousSnapshotRoot)
|
|
716
754
|
: null;
|
|
717
|
-
if (previous && compareStrictSemVer(catalogVersion, previous.catalogVersion) <= 0) {
|
|
718
|
-
throw new Error(`Catalog version ${catalogVersion} must be higher than published ${previous.catalogVersion}.`);
|
|
755
|
+
if (previous && compareStrictSemVer(catalogVersion, previous.manifest.catalogVersion) <= 0) {
|
|
756
|
+
throw new Error(`Catalog version ${catalogVersion} must be higher than published ${previous.manifest.catalogVersion}.`);
|
|
719
757
|
}
|
|
720
|
-
const previousLedger = previous?.ledger ?? {
|
|
721
|
-
schemaVersion: PLUGIN_RELEASE_LEDGER_SCHEMA_VERSION,
|
|
722
|
-
records: [],
|
|
723
|
-
};
|
|
724
758
|
const temporaryRelative = path.join('.aw', 'publish', `.catalog-preparation-${crypto.randomUUID()}`);
|
|
725
759
|
const finalRelative = options.outputPath
|
|
726
760
|
?? `${DEFAULT_PUBLISHED_CATALOG_OUTPUT_PATH}-v${catalogVersion}`;
|
|
@@ -735,13 +769,12 @@ export async function preparePublishedCatalogWorkspace(options) {
|
|
|
735
769
|
temporaryRoot = built.outputRoot;
|
|
736
770
|
const development = await validateCatalogSnapshotComplete(temporaryRoot);
|
|
737
771
|
const contentDigests = new Map(development.manifest.entries.map((entry) => [entry.name, entry.contentDigest]));
|
|
738
|
-
const overrides =
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
: Object.fromEntries(catalogWorkspace.plugins.map((plugin) => [plugin.manifest.name, plugin.manifest.version]));
|
|
772
|
+
const overrides = resolvePublishedPluginVersions({
|
|
773
|
+
channel,
|
|
774
|
+
plugins: catalogWorkspace.plugins,
|
|
775
|
+
contentDigests,
|
|
776
|
+
previous: previous?.manifest ?? null,
|
|
777
|
+
});
|
|
745
778
|
await rewritePublishedPluginVersions(temporaryRoot, overrides);
|
|
746
779
|
const bundledPlugins = await Promise.all(catalogWorkspace.plugins.map((plugin) => readPluginManifest(path.join(temporaryRoot, 'plugins', plugin.manifest.name), { mode: 'bundle' })));
|
|
747
780
|
const entries = await Promise.all(bundledPlugins.sort((left, right) => left.manifest.name.localeCompare(right.manifest.name))
|
|
@@ -752,26 +785,14 @@ export async function preparePublishedCatalogWorkspace(options) {
|
|
|
752
785
|
channel,
|
|
753
786
|
entries,
|
|
754
787
|
};
|
|
755
|
-
const contentDigest = publishedContentDigest(manifestWithoutRelease);
|
|
756
788
|
const manifest = {
|
|
757
789
|
...manifestWithoutRelease,
|
|
758
790
|
release: {
|
|
759
791
|
state: 'published',
|
|
760
|
-
targetVersion: catalogVersion.replace(/-beta\.[1-9]\d*$/u, ''),
|
|
761
|
-
planId: contentDigest,
|
|
762
792
|
sourceHead: options.sourceHead,
|
|
763
|
-
contentDigest,
|
|
764
793
|
},
|
|
765
794
|
};
|
|
766
|
-
|
|
767
|
-
previous: previousLedger,
|
|
768
|
-
manifest,
|
|
769
|
-
contentDigests,
|
|
770
|
-
});
|
|
771
|
-
await Promise.all([
|
|
772
|
-
fs.writeFile(path.join(temporaryRoot, CATALOG_MANIFEST_FILENAME), `${JSON.stringify(manifest, null, 2)}\n`),
|
|
773
|
-
fs.writeFile(path.join(temporaryRoot, PLUGIN_RELEASE_LEDGER_FILENAME), `${JSON.stringify(ledger, null, 2)}\n`),
|
|
774
|
-
]);
|
|
795
|
+
await fs.writeFile(path.join(temporaryRoot, CATALOG_MANIFEST_FILENAME), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
775
796
|
const prepared = await validatePublishedCatalogSnapshot(temporaryRoot);
|
|
776
797
|
const resolved = await resolveCatalogWorkspaceOutputPath({
|
|
777
798
|
workspaceRoot: catalogWorkspace.workspaceRoot,
|
|
@@ -795,8 +816,6 @@ export async function preparePublishedCatalogWorkspace(options) {
|
|
|
795
816
|
channel,
|
|
796
817
|
pluginNames: built.pluginNames,
|
|
797
818
|
snapshotDigest: prepared.snapshotDigest,
|
|
798
|
-
contentDigest,
|
|
799
|
-
planId: contentDigest,
|
|
800
819
|
};
|
|
801
820
|
}
|
|
802
821
|
finally {
|
|
@@ -922,7 +941,7 @@ async function digestDirectory(root) {
|
|
|
922
941
|
await walk(root);
|
|
923
942
|
return `sha256:${crypto.createHash('sha256').update(entries.join('\n')).digest('hex')}`;
|
|
924
943
|
}
|
|
925
|
-
async function validateCatalogSnapshotTransportInternal(snapshotDirectory
|
|
944
|
+
async function validateCatalogSnapshotTransportInternal(snapshotDirectory) {
|
|
926
945
|
const rootDir = await assertDirectoryWithoutSymlinks(snapshotDirectory, 'Catalog snapshot');
|
|
927
946
|
const allowed = new Set([
|
|
928
947
|
CATALOG_MANIFEST_FILENAME,
|
|
@@ -930,7 +949,6 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
|
|
|
930
949
|
'.agents',
|
|
931
950
|
'.claude-plugin',
|
|
932
951
|
'plugins',
|
|
933
|
-
'plugin-release-ledger.json',
|
|
934
952
|
]);
|
|
935
953
|
const entries = await fs.readdir(rootDir);
|
|
936
954
|
const unexpected = entries.filter((entry) => !allowed.has(entry));
|
|
@@ -970,19 +988,18 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
|
|
|
970
988
|
await readRegularJson(path.join(rootDir, '.agents', 'plugins', 'marketplace.json'), 'Codex marketplace');
|
|
971
989
|
await readRegularJson(path.join(rootDir, '.claude-plugin', 'marketplace.json'), 'Claude marketplace');
|
|
972
990
|
const snapshotDigest = await digestDirectory(rootDir);
|
|
973
|
-
const raw =
|
|
974
|
-
?? await fs.readJson(path.join(rootDir, CATALOG_MANIFEST_FILENAME));
|
|
991
|
+
const raw = await fs.readJson(path.join(rootDir, CATALOG_MANIFEST_FILENAME));
|
|
975
992
|
const allowedManifestKeys = new Set([
|
|
976
993
|
'schemaVersion', 'catalogVersion', 'channel', 'requires', 'marketplace', 'release', 'entries',
|
|
977
994
|
]);
|
|
978
995
|
for (const key of Object.keys(raw))
|
|
979
996
|
if (!allowedManifestKeys.has(key))
|
|
980
997
|
throw new Error(`Invalid catalog manifest: unknown field ${JSON.stringify(key)}.`);
|
|
981
|
-
if (raw.schemaVersion !==
|
|
982
|
-
throw new Error('Invalid catalog manifest schemaVersion: expected
|
|
983
|
-
const base = normalizeCatalogManifestBase(raw, 'catalog manifest
|
|
984
|
-
if (!(base.requires.aw.capabilities ?? []).includes('aw.catalog-snapshot.
|
|
985
|
-
throw new Error('Invalid catalog manifest requires.aw.capabilities: expected aw.catalog-snapshot.
|
|
998
|
+
if (raw.schemaVersion !== 6)
|
|
999
|
+
throw new Error('Invalid catalog manifest schemaVersion: expected 6.');
|
|
1000
|
+
const base = normalizeCatalogManifestBase(raw, 'catalog manifest v6');
|
|
1001
|
+
if (!(base.requires.aw.capabilities ?? []).includes('aw.catalog-snapshot.v6')) {
|
|
1002
|
+
throw new Error('Invalid catalog manifest requires.aw.capabilities: expected aw.catalog-snapshot.v6.');
|
|
986
1003
|
}
|
|
987
1004
|
const marketplaceIdentity = normalizeCatalogMarketplaceIdentity(raw.marketplace);
|
|
988
1005
|
if (!Array.isArray(raw.entries))
|
|
@@ -999,7 +1016,7 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
|
|
|
999
1016
|
delete copy.contentDigest;
|
|
1000
1017
|
return copy;
|
|
1001
1018
|
});
|
|
1002
|
-
const normalizedEntries = normalizeCatalogManifestEntries(entriesWithoutContentDigest, base, 'catalog manifest
|
|
1019
|
+
const normalizedEntries = normalizeCatalogManifestEntries(entriesWithoutContentDigest, base, 'catalog manifest v6')
|
|
1003
1020
|
.map((entry, index) => ({ ...entry, contentDigest: contentDigests[index] }));
|
|
1004
1021
|
const pluginNames = normalizedEntries.map((entry, index) => {
|
|
1005
1022
|
if (typeof entry.name !== 'string')
|
|
@@ -1031,10 +1048,6 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
|
|
|
1031
1048
|
contentDigest: await calculateEntryDigest(pluginRoot, true),
|
|
1032
1049
|
};
|
|
1033
1050
|
for (const field of Object.keys(expectedDigests)) {
|
|
1034
|
-
// V4 used a normalized Manifest v1 digest. The artifact digest still binds the raw manifest bytes,
|
|
1035
|
-
// and the release ledger must still match the preserved v4 manifestDigest.
|
|
1036
|
-
if (field === 'manifestDigest' && options.legacyV4PublicationParent)
|
|
1037
|
-
continue;
|
|
1038
1051
|
if (entry[field] !== expectedDigests[field]) {
|
|
1039
1052
|
throw new Error(`Catalog entry ${field} for ${JSON.stringify(name)} does not match its plugin bundle.`);
|
|
1040
1053
|
}
|
|
@@ -1083,9 +1096,9 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
|
|
|
1083
1096
|
if (!isDeepStrictEqual(normalizeMarketplaceOrder(claudeMarketplace), normalizeMarketplaceOrder(expectedClaude))) {
|
|
1084
1097
|
throw new Error('Claude marketplace metadata or plugin inventory does not match the snapshot bundles.');
|
|
1085
1098
|
}
|
|
1086
|
-
const release = normalizeCatalogRelease(raw.release
|
|
1099
|
+
const release = normalizeCatalogRelease(raw.release);
|
|
1087
1100
|
const manifest = {
|
|
1088
|
-
schemaVersion:
|
|
1101
|
+
schemaVersion: 6,
|
|
1089
1102
|
catalogVersion: base.catalogVersion,
|
|
1090
1103
|
channel: base.channel,
|
|
1091
1104
|
requires: base.requires,
|
|
@@ -1093,157 +1106,11 @@ async function validateCatalogSnapshotTransportInternal(snapshotDirectory, optio
|
|
|
1093
1106
|
release,
|
|
1094
1107
|
entries: normalizedEntries,
|
|
1095
1108
|
};
|
|
1096
|
-
|
|
1097
|
-
const contentDigest = publishedContentDigest(manifest);
|
|
1098
|
-
if (release.contentDigest !== contentDigest) {
|
|
1099
|
-
throw new Error('Published catalog release contentDigest does not match the snapshot.');
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
const hasLedger = entries.includes(PLUGIN_RELEASE_LEDGER_FILENAME);
|
|
1103
|
-
if (release.state === 'published' && !hasLedger) {
|
|
1104
|
-
throw new Error('Published Catalog snapshot must include plugin-release-ledger.json.');
|
|
1105
|
-
}
|
|
1106
|
-
if (release.state === 'development' && hasLedger) {
|
|
1107
|
-
throw new Error('Development Catalog snapshot must not include plugin-release-ledger.json.');
|
|
1108
|
-
}
|
|
1109
|
-
const ledger = hasLedger
|
|
1110
|
-
? validatePluginReleaseLedgerAgainstManifest(await readRegularJson(path.join(rootDir, PLUGIN_RELEASE_LEDGER_FILENAME), 'Plugin release ledger'), manifest)
|
|
1111
|
-
: undefined;
|
|
1112
|
-
return { rootDir, manifest, ...(ledger ? { ledger } : {}), snapshotDigest };
|
|
1109
|
+
return { rootDir, manifest, snapshotDigest };
|
|
1113
1110
|
}
|
|
1114
1111
|
export async function validateCatalogSnapshotTransport(snapshotDirectory) {
|
|
1115
1112
|
return validateCatalogSnapshotTransportInternal(snapshotDirectory);
|
|
1116
1113
|
}
|
|
1117
|
-
function normalizeLegacyCatalogCompatibility(value, fieldName) {
|
|
1118
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1119
|
-
throw new Error(`Invalid ${fieldName}: expected an object.`);
|
|
1120
|
-
}
|
|
1121
|
-
const compatibility = value;
|
|
1122
|
-
if (!isDeepStrictEqual(Object.keys(compatibility), ['aw'])) {
|
|
1123
|
-
throw new Error(`Invalid ${fieldName}: unknown field.`);
|
|
1124
|
-
}
|
|
1125
|
-
if (!compatibility.aw || typeof compatibility.aw !== 'object' || Array.isArray(compatibility.aw)) {
|
|
1126
|
-
throw new Error(`Invalid ${fieldName}.aw: expected an object.`);
|
|
1127
|
-
}
|
|
1128
|
-
const aw = compatibility.aw;
|
|
1129
|
-
if (!isDeepStrictEqual(Object.keys(aw), ['minVersion'])) {
|
|
1130
|
-
throw new Error(`Invalid ${fieldName}.aw: unknown field.`);
|
|
1131
|
-
}
|
|
1132
|
-
return { aw: { minVersion: assertStrictSemVer(aw.minVersion, `${fieldName}.aw.minVersion`) } };
|
|
1133
|
-
}
|
|
1134
|
-
function normalizeLegacyPublishedCatalogManifestV4(raw) {
|
|
1135
|
-
const allowedManifestKeys = new Set([
|
|
1136
|
-
'schemaVersion', 'catalogVersion', 'channel', 'compatibility', 'marketplace', 'release', 'entries',
|
|
1137
|
-
]);
|
|
1138
|
-
for (const key of Object.keys(raw)) {
|
|
1139
|
-
if (!allowedManifestKeys.has(key))
|
|
1140
|
-
throw new Error(`Invalid legacy catalog manifest: unknown field ${JSON.stringify(key)}.`);
|
|
1141
|
-
}
|
|
1142
|
-
if (raw.schemaVersion !== 4)
|
|
1143
|
-
throw new Error('Invalid legacy catalog manifest schemaVersion: expected 4.');
|
|
1144
|
-
const catalogVersion = assertCatalogVersion(raw.catalogVersion, 'legacy catalog manifest v4.catalogVersion');
|
|
1145
|
-
if (raw.channel !== 'latest' && raw.channel !== 'beta') {
|
|
1146
|
-
throw new Error('Invalid legacy catalog manifest v4.channel: expected "latest" or "beta".');
|
|
1147
|
-
}
|
|
1148
|
-
const channel = raw.channel;
|
|
1149
|
-
const isBetaVersion = catalogVersion.includes('-beta.');
|
|
1150
|
-
if ((channel === 'latest' && isBetaVersion) || (channel === 'beta' && !isBetaVersion)) {
|
|
1151
|
-
throw new Error('Invalid legacy catalog manifest v4 catalogVersion channel.');
|
|
1152
|
-
}
|
|
1153
|
-
const compatibility = normalizeLegacyCatalogCompatibility(raw.compatibility, 'legacy catalog manifest v4.compatibility');
|
|
1154
|
-
const marketplace = normalizeCatalogMarketplaceIdentity(raw.marketplace, 'legacy catalog manifest v4.marketplace');
|
|
1155
|
-
if (!Array.isArray(raw.entries))
|
|
1156
|
-
throw new Error('Invalid legacy catalog manifest v4.entries: expected an array.');
|
|
1157
|
-
const allowedEntryKeys = new Set([
|
|
1158
|
-
'name', 'version', 'description', 'interface', 'category', 'tags', 'strict', 'policy',
|
|
1159
|
-
'compatibility', 'source', 'artifactDigest', 'manifestDigest', 'contentDigest',
|
|
1160
|
-
]);
|
|
1161
|
-
const translatedEntryInputs = raw.entries.map((value, index) => {
|
|
1162
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
1163
|
-
throw new Error(`Invalid legacy catalog manifest v4.entries[${index}]: expected an object.`);
|
|
1164
|
-
}
|
|
1165
|
-
const entry = value;
|
|
1166
|
-
for (const key of Object.keys(entry)) {
|
|
1167
|
-
if (!allowedEntryKeys.has(key)) {
|
|
1168
|
-
throw new Error(`Invalid legacy catalog manifest v4.entries[${index}]: unknown field ${JSON.stringify(key)}.`);
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
const entryCompatibility = normalizeLegacyCatalogCompatibility(entry.compatibility, `legacy catalog manifest v4.entries[${index}].compatibility`);
|
|
1172
|
-
if (compareStrictSemVer(entryCompatibility.aw.minVersion, compatibility.aw.minVersion) < 0) {
|
|
1173
|
-
throw new Error(`Invalid legacy catalog manifest v4.entries[${index}].compatibility.aw.minVersion.`);
|
|
1174
|
-
}
|
|
1175
|
-
const translated = { ...entry };
|
|
1176
|
-
delete translated.compatibility;
|
|
1177
|
-
delete translated.contentDigest;
|
|
1178
|
-
return {
|
|
1179
|
-
...translated,
|
|
1180
|
-
dependencies: [],
|
|
1181
|
-
requires: { aw: { capabilities: [] } },
|
|
1182
|
-
};
|
|
1183
|
-
});
|
|
1184
|
-
const translatedBase = normalizeCatalogManifestBase({
|
|
1185
|
-
catalogVersion,
|
|
1186
|
-
channel,
|
|
1187
|
-
requires: { aw: { capabilities: ['aw.catalog-snapshot.v5'] } },
|
|
1188
|
-
}, 'legacy catalog manifest v4 translation');
|
|
1189
|
-
const normalizedEntries = normalizeCatalogManifestEntries(translatedEntryInputs, translatedBase, 'legacy catalog manifest v4 translation').map((entry, index) => {
|
|
1190
|
-
const contentDigest = raw.entries[index].contentDigest;
|
|
1191
|
-
if (typeof contentDigest !== 'string' || !/^sha256:[0-9a-f]{64}$/u.test(contentDigest)) {
|
|
1192
|
-
throw new Error(`Invalid legacy catalog manifest v4.entries[${index}].contentDigest.`);
|
|
1193
|
-
}
|
|
1194
|
-
return { ...entry, contentDigest };
|
|
1195
|
-
});
|
|
1196
|
-
const release = normalizeCatalogRelease(raw.release, catalogVersion);
|
|
1197
|
-
if (release.state !== 'published') {
|
|
1198
|
-
throw new Error('Legacy Catalog publication parent must have release.state published.');
|
|
1199
|
-
}
|
|
1200
|
-
const legacyContentDigest = sha256(stableStringify({
|
|
1201
|
-
schemaIdentity: { schemaVersion: 4 },
|
|
1202
|
-
marketplaceMetadata: { name: 'aw', owner: marketplace.owner, interface: marketplace.interface },
|
|
1203
|
-
compatibility,
|
|
1204
|
-
plugins: normalizedEntries.map((entry) => ({
|
|
1205
|
-
name: entry.name,
|
|
1206
|
-
targetVersion: entry.version.replace(/-beta\.[1-9]\d*$/u, ''),
|
|
1207
|
-
contentDigest: entry.contentDigest,
|
|
1208
|
-
})).sort((left, right) => left.name.localeCompare(right.name)),
|
|
1209
|
-
}));
|
|
1210
|
-
if (release.contentDigest !== legacyContentDigest) {
|
|
1211
|
-
throw new Error('Published legacy catalog release contentDigest does not match the snapshot.');
|
|
1212
|
-
}
|
|
1213
|
-
const translatedManifest = {
|
|
1214
|
-
schemaVersion: 5,
|
|
1215
|
-
catalogVersion,
|
|
1216
|
-
channel,
|
|
1217
|
-
requires: translatedBase.requires,
|
|
1218
|
-
marketplace,
|
|
1219
|
-
release,
|
|
1220
|
-
entries: normalizedEntries,
|
|
1221
|
-
};
|
|
1222
|
-
return {
|
|
1223
|
-
...translatedManifest,
|
|
1224
|
-
release: { ...release, contentDigest: publishedContentDigest(translatedManifest) },
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
export async function validatePublishedCatalogPublicationParent(snapshotDirectory) {
|
|
1228
|
-
const raw = await readRegularJson(path.join(snapshotDirectory, CATALOG_MANIFEST_FILENAME), 'Catalog publication parent manifest');
|
|
1229
|
-
const validation = raw.schemaVersion === 4
|
|
1230
|
-
? await validateCatalogSnapshotTransportInternal(snapshotDirectory, {
|
|
1231
|
-
rawManifest: normalizeLegacyPublishedCatalogManifestV4(raw),
|
|
1232
|
-
legacyV4PublicationParent: true,
|
|
1233
|
-
})
|
|
1234
|
-
: await validatePublishedCatalogSnapshot(snapshotDirectory);
|
|
1235
|
-
if (validation.manifest.release.state !== 'published') {
|
|
1236
|
-
throw new Error('Catalog publication parent must have release.state published.');
|
|
1237
|
-
}
|
|
1238
|
-
if (!validation.ledger)
|
|
1239
|
-
throw new Error('Catalog publication parent must include a plugin release ledger.');
|
|
1240
|
-
return {
|
|
1241
|
-
rootDir: validation.rootDir,
|
|
1242
|
-
catalogVersion: validation.manifest.catalogVersion,
|
|
1243
|
-
ledger: validation.ledger,
|
|
1244
|
-
snapshotDigest: validation.snapshotDigest,
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
1114
|
async function validateCatalogPluginEntriesComplete(validation, pluginNames) {
|
|
1248
1115
|
const entriesByName = new Map(validation.manifest.entries.map((entry) => [entry.name, entry]));
|
|
1249
1116
|
const plugins = [];
|