@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/manifest-contract.js +3 -8
- package/dist/manifest-contract.js.map +1 -1
- package/dist/manifest.d.ts +1 -0
- package/dist/manifest.js +1 -0
- package/dist/manifest.js.map +1 -1
- package/dist/plugin-authoring-capabilities.d.ts +21 -0
- package/dist/plugin-authoring-capabilities.js +176 -0
- package/dist/plugin-authoring-capabilities.js.map +1 -0
- package/dist/workspace.d.ts +32 -21
- package/dist/workspace.js +130 -99
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
82
|
-
const
|
|
81
|
+
function catalogWorkspaceMetadata(options, workspaceRoot) {
|
|
82
|
+
const catalogWorkspaceName = safeCatalogWorkspaceName(workspaceRoot);
|
|
83
83
|
return {
|
|
84
84
|
owner: {
|
|
85
|
-
name: options.ownerName?.trim() ||
|
|
86
|
-
url: options.ownerUrl?.trim() || `https://example.invalid/aw-workspaces/${
|
|
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() ||
|
|
89
|
+
displayName: options.displayName?.trim() || catalogWorkspaceName,
|
|
90
90
|
},
|
|
91
91
|
catalog: {
|
|
92
|
-
version: assertCatalogVersion(options.catalogVersion ??
|
|
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
|
|
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-
|
|
121
|
+
kind: 'aw-catalog-workspace',
|
|
125
122
|
workspaceIdentity: sha256(crypto.randomUUID()),
|
|
126
|
-
sourceIdentity: sha256(stableStringify(metadata)),
|
|
127
123
|
};
|
|
128
124
|
}
|
|
129
|
-
function
|
|
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', '
|
|
131
|
+
'kind', 'schemaVersion', 'workspaceIdentity',
|
|
137
132
|
])
|
|
138
133
|
|| marker.schemaVersion !== 1
|
|
139
|
-
|| marker.kind !== 'aw-
|
|
134
|
+
|| marker.kind !== 'aw-catalog-workspace'
|
|
140
135
|
|| typeof marker.workspaceIdentity !== 'string'
|
|
141
|
-
||
|
|
142
|
-
|
|
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
|
|
149
|
-
const markerPath = path.join(workspaceRoot,
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
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,
|
|
170
|
-
const metadata =
|
|
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
|
|
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
|
|
184
|
+
throw new Error(`Refusing to replace different Catalog Workspace metadata: ${metadataPath}`);
|
|
190
185
|
}
|
|
191
186
|
}
|
|
192
|
-
const expectedMarker =
|
|
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
|
-
|
|
192
|
+
workspaceIdentity = validateCatalogWorkspaceMarker(currentMarker).workspaceIdentity;
|
|
197
193
|
}
|
|
198
194
|
catch {
|
|
199
|
-
throw new Error(`Refusing to replace a
|
|
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
|
|
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
|
|
241
|
+
await readRegularJson(metadataPath, 'Catalog Workspace metadata');
|
|
245
242
|
if (!isInside(root, await fs.realpath(metadataPath)))
|
|
246
|
-
throw new Error('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
|
|
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 {
|
|
272
|
+
return {
|
|
273
|
+
workspaceRoot: root,
|
|
274
|
+
markerPath: marker.markerPath,
|
|
275
|
+
workspaceIdentity: marker.workspaceIdentity,
|
|
276
|
+
metadataPath,
|
|
277
|
+
metadata,
|
|
278
|
+
plugins,
|
|
279
|
+
};
|
|
276
280
|
}
|
|
277
|
-
export
|
|
278
|
-
const current =
|
|
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: {
|
|
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
|
|
485
|
-
const
|
|
486
|
-
const plugins = orderPlugins(
|
|
487
|
-
const resolved = await
|
|
488
|
-
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 =
|
|
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(
|
|
524
|
+
const identity = sourceIdentity(catalogWorkspace.metadata, plugins);
|
|
496
525
|
const marker = generatedOutputMarker({
|
|
497
526
|
kind: 'catalog',
|
|
498
|
-
workspaceIdentity:
|
|
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:
|
|
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(
|
|
523
|
-
await fs.outputJson(path.join(stagingRoot, '.claude-plugin', 'marketplace.json'), createMarketplaceManifest(
|
|
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:
|
|
540
|
-
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
|
|
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
|
|
586
|
-
for (const plugin of
|
|
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 =
|
|
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(
|
|
633
|
+
let temporaryRoot = path.resolve(catalogWorkspace.workspaceRoot, temporaryRelative);
|
|
605
634
|
try {
|
|
606
|
-
const built = await
|
|
607
|
-
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:
|
|
646
|
+
plugins: catalogWorkspace.plugins,
|
|
618
647
|
contentDigests,
|
|
619
648
|
ledger: previousLedger,
|
|
620
649
|
})
|
|
621
|
-
: Object.fromEntries(
|
|
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(
|
|
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
|
|
652
|
-
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
|
|
701
|
-
const
|
|
702
|
-
assertDependencyGraph(
|
|
703
|
-
const requestedPluginRoot = path.resolve(
|
|
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(
|
|
706
|
-
throw new Error('Plugin directory must stay inside the
|
|
707
|
-
const plugin =
|
|
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
|
|
740
|
+
throw new Error(`Plugin directory is not part of this Catalog Workspace source: ${pluginRoot}`);
|
|
710
741
|
}
|
|
711
|
-
const closure = dependencyClosure(plugin,
|
|
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:
|
|
746
|
+
workspaceIdentity: catalogWorkspace.workspaceIdentity,
|
|
716
747
|
sourceIdentity: identity,
|
|
717
748
|
sourceName: plugin.manifest.name,
|
|
718
749
|
});
|
|
719
|
-
const resolved = await
|
|
720
|
-
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:
|
|
759
|
+
workspaceRoot: catalogWorkspace.workspaceRoot,
|
|
729
760
|
plugin,
|
|
730
|
-
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
|
-
||
|
|
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
|
-
||
|
|
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.');
|