@kb-labs/marketplace-core 0.1.1
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/README.md +75 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +458 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# @product-name/package-name
|
|
2
|
+
|
|
3
|
+
Baseline library package inside KB Labs Product Template.
|
|
4
|
+
|
|
5
|
+
## Vision & Purpose
|
|
6
|
+
|
|
7
|
+
**@product-name/package-name** is a minimal example library shipped with `kb-labs-product-template`.
|
|
8
|
+
It shows how a typical package is structured (source, tests, types) and is intended to be **renamed or removed** when creating a real product.
|
|
9
|
+
|
|
10
|
+
### Core Goals
|
|
11
|
+
|
|
12
|
+
- Demonstrate the standard KB Labs package layout (src/types/tests/build tooling)
|
|
13
|
+
- Provide a simple, testable function (`hello`) as a starting point
|
|
14
|
+
- Act as a safe playground for verifying DevKit configs (tsup, Vitest, ESLint)
|
|
15
|
+
|
|
16
|
+
## Package Status
|
|
17
|
+
|
|
18
|
+
- **Version**: 0.1.0
|
|
19
|
+
- **Stage**: Template / Example
|
|
20
|
+
- **Status**: Not for Production ⚠️
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
### Structure
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
packages/package-name/
|
|
28
|
+
├── src/
|
|
29
|
+
│ ├── index.ts # Public entrypoint
|
|
30
|
+
│ └── types/ # Shared types and re-exports
|
|
31
|
+
│ ├── types.ts
|
|
32
|
+
│ └── index.ts
|
|
33
|
+
├── index.test.ts # Vitest example
|
|
34
|
+
└── tsup.config.ts # Build configuration
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The default implementation is intentionally tiny:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
export const hello = (name = 'KB Labs') => `Hello, ${name}!`;
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Dependencies
|
|
44
|
+
|
|
45
|
+
### Runtime
|
|
46
|
+
|
|
47
|
+
None by default — this is a pure TypeScript example.
|
|
48
|
+
|
|
49
|
+
### Development
|
|
50
|
+
|
|
51
|
+
- `@kb-labs/devkit`: shared TS/ESLint/Vitest/TSUP presets
|
|
52
|
+
- `tsup`, `vitest`, `tsx`, `typescript`
|
|
53
|
+
|
|
54
|
+
## Scripts
|
|
55
|
+
|
|
56
|
+
From the `kb-labs-product-template` root:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pnpm --filter @product-name/package-name build
|
|
60
|
+
pnpm --filter @product-name/package-name test
|
|
61
|
+
pnpm --filter @product-name/package-name lint
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## How to Adapt for a Real Package
|
|
65
|
+
|
|
66
|
+
When using the product template for your own project:
|
|
67
|
+
|
|
68
|
+
1. Rename the package in `package.json` (e.g. `@kb-labs/my-product-core`).
|
|
69
|
+
2. Replace the `hello` function with your actual public API.
|
|
70
|
+
3. Update tests in `index.test.ts` to cover real behaviour.
|
|
71
|
+
4. Adjust `types/` to expose the correct public types.
|
|
72
|
+
|
|
73
|
+
This package is meant as a scaffold only; don’t ship it as-is to production.
|
|
74
|
+
|
|
75
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { EntityKind, MarketplaceEntry } from '@kb-labs/core-discovery';
|
|
2
|
+
import { MarketplaceServiceAPI, PackageSource, EntityKindStrategy, InstallResult, InstallResultEntry, MarketplaceEntryWithId, SyncResult, DoctorReport, ManifestCache, ManifestCacheEntry } from '@kb-labs/marketplace-contracts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @module @kb-labs/marketplace-core/marketplace-service
|
|
6
|
+
* Unified marketplace service — install/uninstall/enable/disable for all entity types.
|
|
7
|
+
* Works through PackageSource abstraction — never calls pnpm directly.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface MarketplaceServiceOptions {
|
|
11
|
+
/** Workspace root directory */
|
|
12
|
+
root: string;
|
|
13
|
+
/** Package source (npm, registry, etc.) */
|
|
14
|
+
source: PackageSource;
|
|
15
|
+
/** Additional strategies beyond built-in plugin/adapter */
|
|
16
|
+
strategies?: EntityKindStrategy[];
|
|
17
|
+
}
|
|
18
|
+
declare class MarketplaceService implements MarketplaceServiceAPI {
|
|
19
|
+
private readonly root;
|
|
20
|
+
private readonly source;
|
|
21
|
+
private readonly strategies;
|
|
22
|
+
constructor(opts: MarketplaceServiceOptions);
|
|
23
|
+
registerStrategy(strategy: EntityKindStrategy): void;
|
|
24
|
+
install(specs: string[], opts?: {
|
|
25
|
+
dev?: boolean;
|
|
26
|
+
}): Promise<InstallResult>;
|
|
27
|
+
uninstall(packageIds: string[]): Promise<void>;
|
|
28
|
+
link(packagePath: string): Promise<InstallResultEntry>;
|
|
29
|
+
unlink(packageId: string): Promise<void>;
|
|
30
|
+
update(packageIds?: string[]): Promise<InstallResult>;
|
|
31
|
+
enable(packageId: string): Promise<void>;
|
|
32
|
+
disable(packageId: string): Promise<void>;
|
|
33
|
+
list(filter?: {
|
|
34
|
+
kind?: EntityKind;
|
|
35
|
+
}): Promise<MarketplaceEntryWithId[]>;
|
|
36
|
+
getEntry(packageId: string): Promise<MarketplaceEntry | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Scan workspace for plugins and adapters using glob patterns.
|
|
39
|
+
* Existing entries are preserved (not overwritten).
|
|
40
|
+
* Patterns come from kb.config.json marketplace.sync.include.
|
|
41
|
+
*/
|
|
42
|
+
sync(opts: {
|
|
43
|
+
include: string[];
|
|
44
|
+
exclude?: string[];
|
|
45
|
+
autoEnable?: boolean;
|
|
46
|
+
}): Promise<SyncResult>;
|
|
47
|
+
private _syncPackage;
|
|
48
|
+
doctor(): Promise<DoctorReport>;
|
|
49
|
+
private detectKind;
|
|
50
|
+
private cacheManifest;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @module @kb-labs/marketplace-core/manifest-cache
|
|
55
|
+
* Read/write .kb/marketplace.manifests.json — cached manifests for fast discovery.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read the manifest cache from disk. Returns null if missing or invalid.
|
|
60
|
+
*/
|
|
61
|
+
declare function readManifestCache(root: string): Promise<ManifestCache | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Write the manifest cache atomically (tmp → rename).
|
|
64
|
+
*/
|
|
65
|
+
declare function writeManifestCache(root: string, cache: ManifestCache): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Create an empty manifest cache.
|
|
68
|
+
*/
|
|
69
|
+
declare function createEmptyManifestCache(): ManifestCache;
|
|
70
|
+
/**
|
|
71
|
+
* Set a single entry in the manifest cache and persist.
|
|
72
|
+
*/
|
|
73
|
+
declare function setCacheEntry(root: string, packageId: string, entry: ManifestCacheEntry): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Remove a single entry from the manifest cache and persist.
|
|
76
|
+
*/
|
|
77
|
+
declare function removeCacheEntry(root: string, packageId: string): Promise<void>;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @module @kb-labs/marketplace-core/strategies/plugin-strategy
|
|
81
|
+
* Strategy for plugins — detects ManifestV3 and extracts entity kinds.
|
|
82
|
+
*/
|
|
83
|
+
|
|
84
|
+
declare class PluginStrategy implements EntityKindStrategy {
|
|
85
|
+
kind: EntityKind;
|
|
86
|
+
detectKind(packageRoot: string): Promise<EntityKind | null>;
|
|
87
|
+
extractProvides(packageRoot: string): Promise<EntityKind[]>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @module @kb-labs/marketplace-core/strategies/adapter-strategy
|
|
92
|
+
* Strategy for adapters — detects AdapterManifest, validates dependencies.
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
declare class AdapterStrategy implements EntityKindStrategy {
|
|
96
|
+
kind: EntityKind;
|
|
97
|
+
detectKind(packageRoot: string): Promise<EntityKind | null>;
|
|
98
|
+
extractProvides(_packageRoot: string): Promise<EntityKind[]>;
|
|
99
|
+
afterInstall(packageId: string, packageRoot: string, service: MarketplaceServiceAPI): Promise<void>;
|
|
100
|
+
beforeUninstall(packageId: string, service: MarketplaceServiceAPI): Promise<void>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { AdapterStrategy, MarketplaceService, type MarketplaceServiceOptions, PluginStrategy, createEmptyManifestCache, readManifestCache, removeCacheEntry, setCacheEntry, writeManifestCache };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path3 from 'path';
|
|
3
|
+
import * as crypto from 'crypto';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
import { pathToFileURL } from 'url';
|
|
6
|
+
import { glob } from 'glob';
|
|
7
|
+
import { DiagnosticCollector, loadManifest, extractEntityKinds, createMarketplaceEntry, addToMarketplaceLock, removeFromMarketplaceLock, readMarketplaceLock, enablePlugin, disablePlugin, createEmptyLock, writeMarketplaceLock } from '@kb-labs/core-discovery';
|
|
8
|
+
|
|
9
|
+
// src/marketplace-service.ts
|
|
10
|
+
var CACHE_FILE = ".kb/marketplace.manifests.json";
|
|
11
|
+
var SCHEMA_VERSION = "kb.marketplace.manifests/1";
|
|
12
|
+
async function readManifestCache(root) {
|
|
13
|
+
const cachePath = path3.join(root, CACHE_FILE);
|
|
14
|
+
try {
|
|
15
|
+
const raw = await fs.readFile(cachePath, "utf-8");
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
if (parsed?.schema !== SCHEMA_VERSION) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return parsed;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function writeManifestCache(root, cache) {
|
|
26
|
+
const cachePath = path3.join(root, CACHE_FILE);
|
|
27
|
+
const dir = path3.dirname(cachePath);
|
|
28
|
+
await fs.mkdir(dir, { recursive: true });
|
|
29
|
+
const tmpPath = `${cachePath}.tmp.${randomUUID()}`;
|
|
30
|
+
await fs.writeFile(tmpPath, JSON.stringify(cache, null, 2) + "\n", "utf-8");
|
|
31
|
+
await fs.rename(tmpPath, cachePath);
|
|
32
|
+
}
|
|
33
|
+
function createEmptyManifestCache() {
|
|
34
|
+
return { schema: SCHEMA_VERSION, entries: {} };
|
|
35
|
+
}
|
|
36
|
+
async function setCacheEntry(root, packageId, entry) {
|
|
37
|
+
const existing = await readManifestCache(root) ?? createEmptyManifestCache();
|
|
38
|
+
existing.entries[packageId] = entry;
|
|
39
|
+
await writeManifestCache(root, existing);
|
|
40
|
+
}
|
|
41
|
+
async function removeCacheEntry(root, packageId) {
|
|
42
|
+
const existing = await readManifestCache(root);
|
|
43
|
+
if (!existing || !(packageId in existing.entries)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
delete existing.entries[packageId];
|
|
47
|
+
await writeManifestCache(root, existing);
|
|
48
|
+
}
|
|
49
|
+
var PluginStrategy = class {
|
|
50
|
+
kind = "plugin";
|
|
51
|
+
async detectKind(packageRoot) {
|
|
52
|
+
const diag = new DiagnosticCollector();
|
|
53
|
+
const manifest = await loadManifest(packageRoot, diag);
|
|
54
|
+
return manifest ? "plugin" : null;
|
|
55
|
+
}
|
|
56
|
+
async extractProvides(packageRoot) {
|
|
57
|
+
const diag = new DiagnosticCollector();
|
|
58
|
+
const manifest = await loadManifest(packageRoot, diag);
|
|
59
|
+
if (!manifest) {
|
|
60
|
+
return ["plugin"];
|
|
61
|
+
}
|
|
62
|
+
return extractEntityKinds(manifest);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var AdapterStrategy = class {
|
|
66
|
+
kind = "adapter";
|
|
67
|
+
async detectKind(packageRoot) {
|
|
68
|
+
const manifest = await loadAdapterManifest(packageRoot);
|
|
69
|
+
return manifest ? "adapter" : null;
|
|
70
|
+
}
|
|
71
|
+
async extractProvides(_packageRoot) {
|
|
72
|
+
return ["adapter"];
|
|
73
|
+
}
|
|
74
|
+
async afterInstall(packageId, packageRoot, service) {
|
|
75
|
+
const manifest = await loadAdapterManifest(packageRoot);
|
|
76
|
+
if (!manifest?.requires?.adapters) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const installed = await service.list({ kind: "adapter" });
|
|
80
|
+
const installedIds = new Set(installed.map((e) => e.id));
|
|
81
|
+
for (const dep of manifest.requires.adapters) {
|
|
82
|
+
const depId = typeof dep === "string" ? dep : dep.id;
|
|
83
|
+
const found = installedIds.has(depId) || installed.some((e) => e.id?.includes(`adapters-${depId}`));
|
|
84
|
+
if (!found) {
|
|
85
|
+
console.warn(
|
|
86
|
+
`[marketplace] Adapter "${packageId}" requires adapter "${depId}" which is not installed. Run: kb marketplace link <adapter-package-that-provides-${depId}>`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async beforeUninstall(packageId, service) {
|
|
92
|
+
const installed = await service.list({ kind: "adapter" });
|
|
93
|
+
if (installed.length > 1) {
|
|
94
|
+
console.warn(
|
|
95
|
+
`[marketplace] Removing adapter "${packageId}" \u2014 verify no other adapters depend on it`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
async function loadAdapterManifest(packageRoot) {
|
|
101
|
+
try {
|
|
102
|
+
const distPath = path3.join(packageRoot, "dist", "index.js");
|
|
103
|
+
await fs.access(distPath);
|
|
104
|
+
const mod = await import(pathToFileURL(distPath).href);
|
|
105
|
+
if (mod.manifest && typeof mod.manifest === "object" && mod.manifest.implements) {
|
|
106
|
+
return mod.manifest;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/marketplace-service.ts
|
|
115
|
+
var MarketplaceService = class {
|
|
116
|
+
root;
|
|
117
|
+
source;
|
|
118
|
+
strategies = /* @__PURE__ */ new Map();
|
|
119
|
+
constructor(opts) {
|
|
120
|
+
this.root = opts.root;
|
|
121
|
+
this.source = opts.source;
|
|
122
|
+
this.registerStrategy(new PluginStrategy());
|
|
123
|
+
this.registerStrategy(new AdapterStrategy());
|
|
124
|
+
if (opts.strategies) {
|
|
125
|
+
for (const s of opts.strategies) {
|
|
126
|
+
this.registerStrategy(s);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
registerStrategy(strategy) {
|
|
131
|
+
this.strategies.set(strategy.kind, strategy);
|
|
132
|
+
}
|
|
133
|
+
// -------------------------------------------------------------------------
|
|
134
|
+
// Install
|
|
135
|
+
// -------------------------------------------------------------------------
|
|
136
|
+
async install(specs, opts) {
|
|
137
|
+
const installed = [];
|
|
138
|
+
const warnings = [];
|
|
139
|
+
for (const spec of specs) {
|
|
140
|
+
const resolved = await this.source.resolve(spec);
|
|
141
|
+
const result = await this.source.install(resolved, this.root, opts);
|
|
142
|
+
const primaryKind = await this.detectKind(result.packageRoot);
|
|
143
|
+
const strategy = this.strategies.get(primaryKind);
|
|
144
|
+
const provides = strategy ? await strategy.extractProvides(result.packageRoot) : [primaryKind];
|
|
145
|
+
const entry = createMarketplaceEntry({
|
|
146
|
+
version: result.version,
|
|
147
|
+
integrity: result.integrity,
|
|
148
|
+
resolvedPath: relativeToRoot(this.root, result.packageRoot),
|
|
149
|
+
source: resolved.source,
|
|
150
|
+
primaryKind,
|
|
151
|
+
provides
|
|
152
|
+
});
|
|
153
|
+
await addToMarketplaceLock(this.root, result.id, entry);
|
|
154
|
+
await this.cacheManifest(result.id, result.packageRoot, primaryKind, result.integrity);
|
|
155
|
+
if (strategy?.afterInstall) {
|
|
156
|
+
try {
|
|
157
|
+
await strategy.afterInstall(result.id, result.packageRoot, this);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
warnings.push(`afterInstall for ${result.id}: ${err.message}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
installed.push({
|
|
163
|
+
id: result.id,
|
|
164
|
+
version: result.version,
|
|
165
|
+
primaryKind,
|
|
166
|
+
provides,
|
|
167
|
+
packageRoot: result.packageRoot
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return { installed, warnings };
|
|
171
|
+
}
|
|
172
|
+
// -------------------------------------------------------------------------
|
|
173
|
+
// Uninstall
|
|
174
|
+
// -------------------------------------------------------------------------
|
|
175
|
+
async uninstall(packageIds) {
|
|
176
|
+
for (const id of packageIds) {
|
|
177
|
+
const entry = await this.getEntry(id);
|
|
178
|
+
if (entry) {
|
|
179
|
+
const strategy = this.strategies.get(entry.primaryKind);
|
|
180
|
+
if (strategy?.beforeUninstall) {
|
|
181
|
+
await strategy.beforeUninstall(id, this);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
await removeFromMarketplaceLock(this.root, id);
|
|
185
|
+
await removeCacheEntry(this.root, id);
|
|
186
|
+
await this.source.remove(id, this.root);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// -------------------------------------------------------------------------
|
|
190
|
+
// Link / Unlink
|
|
191
|
+
// -------------------------------------------------------------------------
|
|
192
|
+
async link(packagePath) {
|
|
193
|
+
const absPath = path3.resolve(this.root, packagePath);
|
|
194
|
+
if (!absPath.startsWith(this.root)) {
|
|
195
|
+
throw new Error(`Path "${packagePath}" is outside workspace root \u2014 refusing to link`);
|
|
196
|
+
}
|
|
197
|
+
const pkgJson = JSON.parse(
|
|
198
|
+
await fs.readFile(
|
|
199
|
+
path3.join(absPath, "package.json"),
|
|
200
|
+
"utf-8"
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
const id = pkgJson.name;
|
|
204
|
+
const version = pkgJson.version ?? "0.0.0";
|
|
205
|
+
const primaryKind = await this.detectKind(absPath);
|
|
206
|
+
const strategy = this.strategies.get(primaryKind);
|
|
207
|
+
const provides = strategy ? await strategy.extractProvides(absPath) : [primaryKind];
|
|
208
|
+
const integrity = await computeIntegrity(absPath);
|
|
209
|
+
const entry = createMarketplaceEntry({
|
|
210
|
+
version,
|
|
211
|
+
integrity,
|
|
212
|
+
resolvedPath: relativeToRoot(this.root, absPath),
|
|
213
|
+
source: "local",
|
|
214
|
+
primaryKind,
|
|
215
|
+
provides
|
|
216
|
+
});
|
|
217
|
+
await addToMarketplaceLock(this.root, id, entry);
|
|
218
|
+
await this.cacheManifest(id, absPath, primaryKind, integrity);
|
|
219
|
+
if (strategy?.afterInstall) {
|
|
220
|
+
await strategy.afterInstall(id, absPath, this);
|
|
221
|
+
}
|
|
222
|
+
return { id, version, primaryKind, provides, packageRoot: absPath };
|
|
223
|
+
}
|
|
224
|
+
async unlink(packageId) {
|
|
225
|
+
await removeFromMarketplaceLock(this.root, packageId);
|
|
226
|
+
await removeCacheEntry(this.root, packageId);
|
|
227
|
+
}
|
|
228
|
+
// -------------------------------------------------------------------------
|
|
229
|
+
// Update
|
|
230
|
+
// -------------------------------------------------------------------------
|
|
231
|
+
async update(packageIds) {
|
|
232
|
+
const diag = new DiagnosticCollector();
|
|
233
|
+
const lock = await readMarketplaceLock(this.root, diag);
|
|
234
|
+
if (!lock) {
|
|
235
|
+
return { installed: [], warnings: ["No marketplace.lock found"] };
|
|
236
|
+
}
|
|
237
|
+
const ids = packageIds ?? Object.keys(lock.installed);
|
|
238
|
+
const specs = ids.filter((id) => id in lock.installed);
|
|
239
|
+
return this.install(specs);
|
|
240
|
+
}
|
|
241
|
+
// -------------------------------------------------------------------------
|
|
242
|
+
// Enable / Disable
|
|
243
|
+
// -------------------------------------------------------------------------
|
|
244
|
+
async enable(packageId) {
|
|
245
|
+
const ok = await enablePlugin(this.root, packageId);
|
|
246
|
+
if (!ok) {
|
|
247
|
+
throw new Error(`Package "${packageId}" not found in marketplace.lock`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async disable(packageId) {
|
|
251
|
+
const ok = await disablePlugin(this.root, packageId);
|
|
252
|
+
if (!ok) {
|
|
253
|
+
throw new Error(`Package "${packageId}" not found in marketplace.lock`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// -------------------------------------------------------------------------
|
|
257
|
+
// List / GetEntry (MarketplaceServiceAPI)
|
|
258
|
+
// -------------------------------------------------------------------------
|
|
259
|
+
async list(filter) {
|
|
260
|
+
const diag = new DiagnosticCollector();
|
|
261
|
+
const lock = await readMarketplaceLock(this.root, diag);
|
|
262
|
+
if (!lock) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
let entries = Object.entries(lock.installed).map(([id, entry]) => ({ ...entry, id }));
|
|
266
|
+
if (filter?.kind) {
|
|
267
|
+
entries = entries.filter((e) => e.primaryKind === filter.kind);
|
|
268
|
+
}
|
|
269
|
+
return entries;
|
|
270
|
+
}
|
|
271
|
+
async getEntry(packageId) {
|
|
272
|
+
const diag = new DiagnosticCollector();
|
|
273
|
+
const lock = await readMarketplaceLock(this.root, diag);
|
|
274
|
+
return lock?.installed[packageId] ?? null;
|
|
275
|
+
}
|
|
276
|
+
// -------------------------------------------------------------------------
|
|
277
|
+
// Sync (scan workspace → populate lock from config-driven globs)
|
|
278
|
+
// -------------------------------------------------------------------------
|
|
279
|
+
/**
|
|
280
|
+
* Scan workspace for plugins and adapters using glob patterns.
|
|
281
|
+
* Existing entries are preserved (not overwritten).
|
|
282
|
+
* Patterns come from kb.config.json marketplace.sync.include.
|
|
283
|
+
*/
|
|
284
|
+
async sync(opts) {
|
|
285
|
+
const autoEnable = opts.autoEnable ?? false;
|
|
286
|
+
const diag = new DiagnosticCollector();
|
|
287
|
+
const lock = await readMarketplaceLock(this.root, diag) ?? createEmptyLock();
|
|
288
|
+
const existingIds = new Set(Object.keys(lock.installed));
|
|
289
|
+
const added = [];
|
|
290
|
+
const skipped = [];
|
|
291
|
+
const includePatterns = opts.include.map((p) => path3.join(p, "package.json"));
|
|
292
|
+
const excludePatterns = opts.exclude ?? [];
|
|
293
|
+
const packageJsonPaths = await glob(includePatterns, {
|
|
294
|
+
cwd: this.root,
|
|
295
|
+
ignore: excludePatterns,
|
|
296
|
+
absolute: false
|
|
297
|
+
});
|
|
298
|
+
for (const relPkgJson of packageJsonPaths) {
|
|
299
|
+
const pkgDir = path3.resolve(this.root, path3.dirname(relPkgJson));
|
|
300
|
+
await this._syncPackage(pkgDir, relPkgJson, existingIds, autoEnable, lock, added, skipped);
|
|
301
|
+
}
|
|
302
|
+
await writeMarketplaceLock(this.root, lock);
|
|
303
|
+
return { added, skipped, total: Object.keys(lock.installed).length };
|
|
304
|
+
}
|
|
305
|
+
async _syncPackage(pkgDir, _relPkgJson, existingIds, autoEnable, lock, added, skipped) {
|
|
306
|
+
let pkgName;
|
|
307
|
+
let pkgVersion;
|
|
308
|
+
try {
|
|
309
|
+
const pkgJson = JSON.parse(await fs.readFile(path3.join(pkgDir, "package.json"), "utf-8"));
|
|
310
|
+
pkgName = pkgJson.name;
|
|
311
|
+
pkgVersion = pkgJson.version ?? "0.0.0";
|
|
312
|
+
if (!pkgName) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
} catch {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (existingIds.has(pkgName)) {
|
|
319
|
+
skipped.push({ id: pkgName, reason: "already in lock" });
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
let detected = false;
|
|
323
|
+
for (const strategy2 of this.strategies.values()) {
|
|
324
|
+
const kind = await strategy2.detectKind(pkgDir);
|
|
325
|
+
if (kind) {
|
|
326
|
+
detected = true;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (!detected) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const primaryKind = await this.detectKind(pkgDir);
|
|
334
|
+
const strategy = this.strategies.get(primaryKind);
|
|
335
|
+
const provides = strategy ? await strategy.extractProvides(pkgDir) : [primaryKind];
|
|
336
|
+
const integrity = await computeIntegrity(pkgDir);
|
|
337
|
+
const entry = createMarketplaceEntry({
|
|
338
|
+
version: pkgVersion,
|
|
339
|
+
integrity,
|
|
340
|
+
resolvedPath: relativeToRoot(this.root, pkgDir),
|
|
341
|
+
source: "local",
|
|
342
|
+
primaryKind,
|
|
343
|
+
provides
|
|
344
|
+
});
|
|
345
|
+
if (!autoEnable) {
|
|
346
|
+
entry.enabled = false;
|
|
347
|
+
}
|
|
348
|
+
lock.installed[pkgName] = entry;
|
|
349
|
+
added.push({ id: pkgName, primaryKind, version: pkgVersion });
|
|
350
|
+
}
|
|
351
|
+
// -------------------------------------------------------------------------
|
|
352
|
+
// Doctor
|
|
353
|
+
// -------------------------------------------------------------------------
|
|
354
|
+
async doctor() {
|
|
355
|
+
const diag = new DiagnosticCollector();
|
|
356
|
+
const lock = await readMarketplaceLock(this.root, diag);
|
|
357
|
+
const issues = [];
|
|
358
|
+
if (!lock) {
|
|
359
|
+
return { ok: true, total: 0, issues: [{ severity: "info", packageId: "", message: "No marketplace.lock found" }] };
|
|
360
|
+
}
|
|
361
|
+
const entries = Object.entries(lock.installed);
|
|
362
|
+
for (const [id, entry] of entries) {
|
|
363
|
+
const pkgRoot = path3.resolve(this.root, entry.resolvedPath);
|
|
364
|
+
try {
|
|
365
|
+
await fs.access(pkgRoot);
|
|
366
|
+
} catch {
|
|
367
|
+
issues.push({
|
|
368
|
+
severity: "error",
|
|
369
|
+
packageId: id,
|
|
370
|
+
message: `Package directory not found: ${pkgRoot}`,
|
|
371
|
+
remediation: `Run "kb marketplace install ${id}" to restore`
|
|
372
|
+
});
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (entry.integrity) {
|
|
376
|
+
const computed = await computeIntegrity(pkgRoot);
|
|
377
|
+
if (computed && computed !== entry.integrity) {
|
|
378
|
+
issues.push({
|
|
379
|
+
severity: "warning",
|
|
380
|
+
packageId: id,
|
|
381
|
+
message: `Integrity mismatch: expected ${entry.integrity}, got ${computed}`,
|
|
382
|
+
remediation: `Re-install: kb marketplace install ${id}`
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (!entry.signature) {
|
|
387
|
+
issues.push({
|
|
388
|
+
severity: "info",
|
|
389
|
+
packageId: id,
|
|
390
|
+
message: "Package is not signed",
|
|
391
|
+
remediation: "Publish through the official marketplace to get a platform signature"
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return {
|
|
396
|
+
ok: issues.filter((i) => i.severity === "error").length === 0,
|
|
397
|
+
total: entries.length,
|
|
398
|
+
issues
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
// -------------------------------------------------------------------------
|
|
402
|
+
// Private
|
|
403
|
+
// -------------------------------------------------------------------------
|
|
404
|
+
async detectKind(packageRoot) {
|
|
405
|
+
for (const strategy of this.strategies.values()) {
|
|
406
|
+
const kind = await strategy.detectKind(packageRoot);
|
|
407
|
+
if (kind) {
|
|
408
|
+
return kind;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return "plugin";
|
|
412
|
+
}
|
|
413
|
+
async cacheManifest(packageId, packageRoot, primaryKind, integrity) {
|
|
414
|
+
try {
|
|
415
|
+
if (primaryKind === "plugin") {
|
|
416
|
+
const diag = new DiagnosticCollector();
|
|
417
|
+
const manifest = await loadManifest(packageRoot, diag);
|
|
418
|
+
if (manifest) {
|
|
419
|
+
await setCacheEntry(this.root, packageId, {
|
|
420
|
+
manifestType: "plugin",
|
|
421
|
+
manifest,
|
|
422
|
+
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
423
|
+
integrity
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
} else if (primaryKind === "adapter") {
|
|
427
|
+
const distPath = path3.join(packageRoot, "dist", "index.js");
|
|
428
|
+
const mod = await import(pathToFileURL(distPath).href);
|
|
429
|
+
if (mod.manifest) {
|
|
430
|
+
await setCacheEntry(this.root, packageId, {
|
|
431
|
+
manifestType: "adapter",
|
|
432
|
+
manifest: mod.manifest,
|
|
433
|
+
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
434
|
+
integrity
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
} catch (err) {
|
|
439
|
+
console.warn(`[marketplace] Failed to cache manifest for "${packageId}": ${err.message}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
function relativeToRoot(root, absPath) {
|
|
444
|
+
const rel = path3.relative(root, absPath);
|
|
445
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
446
|
+
}
|
|
447
|
+
async function computeIntegrity(packageRoot) {
|
|
448
|
+
try {
|
|
449
|
+
const content = await fs.readFile(path3.join(packageRoot, "package.json"));
|
|
450
|
+
return `sha256-${crypto.createHash("sha256").update(content).digest("base64")}`;
|
|
451
|
+
} catch {
|
|
452
|
+
return "";
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export { AdapterStrategy, MarketplaceService, PluginStrategy, createEmptyManifestCache, readManifestCache, removeCacheEntry, setCacheEntry, writeManifestCache };
|
|
457
|
+
//# sourceMappingURL=index.js.map
|
|
458
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/manifest-cache.ts","../src/strategies/plugin-strategy.ts","../src/strategies/adapter-strategy.ts","../src/marketplace-service.ts"],"names":["path","path2","fs2","fs3","DiagnosticCollector","strategy","loadManifest","pathToFileURL"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module @kb-labs/marketplace-core/manifest-cache\n * Read/write .kb/marketplace.manifests.json — cached manifests for fast discovery.\n */\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport type { ManifestCache, ManifestCacheEntry } from '@kb-labs/marketplace-contracts';\n\nconst CACHE_FILE = '.kb/marketplace.manifests.json';\nconst SCHEMA_VERSION = 'kb.marketplace.manifests/1' as const;\n\n/**\n * Read the manifest cache from disk. Returns null if missing or invalid.\n */\nexport async function readManifestCache(root: string): Promise<ManifestCache | null> {\n const cachePath = path.join(root, CACHE_FILE);\n try {\n const raw = await fs.readFile(cachePath, 'utf-8');\n const parsed = JSON.parse(raw);\n if (parsed?.schema !== SCHEMA_VERSION) {return null;}\n return parsed as ManifestCache;\n } catch {\n return null;\n }\n}\n\n/**\n * Write the manifest cache atomically (tmp → rename).\n */\nexport async function writeManifestCache(root: string, cache: ManifestCache): Promise<void> {\n const cachePath = path.join(root, CACHE_FILE);\n const dir = path.dirname(cachePath);\n await fs.mkdir(dir, { recursive: true });\n\n const tmpPath = `${cachePath}.tmp.${randomUUID()}`;\n await fs.writeFile(tmpPath, JSON.stringify(cache, null, 2) + '\\n', 'utf-8');\n await fs.rename(tmpPath, cachePath);\n}\n\n/**\n * Create an empty manifest cache.\n */\nexport function createEmptyManifestCache(): ManifestCache {\n return { schema: SCHEMA_VERSION, entries: {} };\n}\n\n/**\n * Set a single entry in the manifest cache and persist.\n */\nexport async function setCacheEntry(\n root: string,\n packageId: string,\n entry: ManifestCacheEntry,\n): Promise<void> {\n const existing = await readManifestCache(root) ?? createEmptyManifestCache();\n existing.entries[packageId] = entry;\n await writeManifestCache(root, existing);\n}\n\n/**\n * Remove a single entry from the manifest cache and persist.\n */\nexport async function removeCacheEntry(\n root: string,\n packageId: string,\n): Promise<void> {\n const existing = await readManifestCache(root);\n if (!existing || !(packageId in existing.entries)) {return;}\n delete existing.entries[packageId];\n await writeManifestCache(root, existing);\n}\n","/**\n * @module @kb-labs/marketplace-core/strategies/plugin-strategy\n * Strategy for plugins — detects ManifestV3 and extracts entity kinds.\n */\n\nimport type { EntityKind } from '@kb-labs/core-discovery';\nimport type { EntityKindStrategy } from '@kb-labs/marketplace-contracts';\nimport { loadManifest, DiagnosticCollector, extractEntityKinds } from '@kb-labs/core-discovery';\n\nexport class PluginStrategy implements EntityKindStrategy {\n kind: EntityKind = 'plugin';\n\n async detectKind(packageRoot: string): Promise<EntityKind | null> {\n const diag = new DiagnosticCollector();\n const manifest = await loadManifest(packageRoot, diag);\n return manifest ? 'plugin' : null;\n }\n\n async extractProvides(packageRoot: string): Promise<EntityKind[]> {\n const diag = new DiagnosticCollector();\n const manifest = await loadManifest(packageRoot, diag);\n if (!manifest) {return ['plugin'];}\n return extractEntityKinds(manifest);\n }\n}\n","/**\n * @module @kb-labs/marketplace-core/strategies/adapter-strategy\n * Strategy for adapters — detects AdapterManifest, validates dependencies.\n */\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { EntityKind } from '@kb-labs/core-discovery';\nimport type { EntityKindStrategy, MarketplaceServiceAPI } from '@kb-labs/marketplace-contracts';\n\nexport class AdapterStrategy implements EntityKindStrategy {\n kind: EntityKind = 'adapter';\n\n async detectKind(packageRoot: string): Promise<EntityKind | null> {\n const manifest = await loadAdapterManifest(packageRoot);\n return manifest ? 'adapter' : null;\n }\n\n async extractProvides(_packageRoot: string): Promise<EntityKind[]> {\n return ['adapter'];\n }\n\n async afterInstall(\n packageId: string,\n packageRoot: string,\n service: MarketplaceServiceAPI,\n ): Promise<void> {\n const manifest = await loadAdapterManifest(packageRoot);\n if (!manifest?.requires?.adapters) {return;}\n\n const installed = await service.list({ kind: 'adapter' });\n const installedIds = new Set(installed.map(e => (e as any).id as string));\n\n for (const dep of manifest.requires.adapters) {\n const depId = typeof dep === 'string' ? dep : dep.id;\n // Check by package ID (exact match), not by path substring\n const found = installedIds.has(depId) ||\n installed.some(e => (e as any).id?.includes(`adapters-${depId}`));\n\n if (!found) {\n console.warn(\n `[marketplace] Adapter \"${packageId}\" requires adapter \"${depId}\" which is not installed. ` +\n `Run: kb marketplace link <adapter-package-that-provides-${depId}>`\n );\n }\n }\n }\n\n async beforeUninstall(\n packageId: string,\n service: MarketplaceServiceAPI,\n ): Promise<void> {\n // Check if any other installed adapter depends on this one\n // This requires loading manifests — for now, warn about potential breakage\n const installed = await service.list({ kind: 'adapter' });\n if (installed.length > 1) {\n console.warn(\n `[marketplace] Removing adapter \"${packageId}\" — verify no other adapters depend on it`\n );\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ninterface AdapterManifestLike {\n id?: string;\n type?: 'core' | 'extension' | 'proxy';\n implements?: string;\n requires?: {\n adapters?: Array<string | { id: string; alias?: string }>;\n };\n}\n\nasync function loadAdapterManifest(packageRoot: string): Promise<AdapterManifestLike | null> {\n try {\n const distPath = path.join(packageRoot, 'dist', 'index.js');\n await fs.access(distPath);\n const mod = await import(pathToFileURL(distPath).href);\n if (mod.manifest && typeof mod.manifest === 'object' && mod.manifest.implements) {\n return mod.manifest as AdapterManifestLike;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * @module @kb-labs/marketplace-core/marketplace-service\n * Unified marketplace service — install/uninstall/enable/disable for all entity types.\n * Works through PackageSource abstraction — never calls pnpm directly.\n */\n\nimport * as fs from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as crypto from 'node:crypto';\nimport { pathToFileURL } from 'node:url';\nimport { glob } from 'glob';\nimport type { EntityKind, MarketplaceEntry, MarketplaceLock } from '@kb-labs/core-discovery';\nimport {\n readMarketplaceLock,\n writeMarketplaceLock,\n createEmptyLock,\n createMarketplaceEntry,\n addToMarketplaceLock,\n removeFromMarketplaceLock,\n enablePlugin,\n disablePlugin,\n DiagnosticCollector,\n loadManifest,\n} from '@kb-labs/core-discovery';\nimport type {\n PackageSource,\n EntityKindStrategy,\n MarketplaceServiceAPI,\n MarketplaceEntryWithId,\n InstallResult,\n InstallResultEntry,\n SyncResult,\n DoctorReport,\n DoctorIssue,\n} from '@kb-labs/marketplace-contracts';\nimport { setCacheEntry, removeCacheEntry } from './manifest-cache.js';\nimport { PluginStrategy } from './strategies/plugin-strategy.js';\nimport { AdapterStrategy } from './strategies/adapter-strategy.js';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface MarketplaceServiceOptions {\n /** Workspace root directory */\n root: string;\n /** Package source (npm, registry, etc.) */\n source: PackageSource;\n /** Additional strategies beyond built-in plugin/adapter */\n strategies?: EntityKindStrategy[];\n}\n\n// ---------------------------------------------------------------------------\n// Service\n// ---------------------------------------------------------------------------\n\nexport class MarketplaceService implements MarketplaceServiceAPI {\n private readonly root: string;\n private readonly source: PackageSource;\n private readonly strategies = new Map<EntityKind, EntityKindStrategy>();\n\n constructor(opts: MarketplaceServiceOptions) {\n this.root = opts.root;\n this.source = opts.source;\n\n // Built-in strategies\n this.registerStrategy(new PluginStrategy());\n this.registerStrategy(new AdapterStrategy());\n\n // User-supplied strategies\n if (opts.strategies) {\n for (const s of opts.strategies) {\n this.registerStrategy(s);\n }\n }\n }\n\n registerStrategy(strategy: EntityKindStrategy): void {\n this.strategies.set(strategy.kind, strategy);\n }\n\n // -------------------------------------------------------------------------\n // Install\n // -------------------------------------------------------------------------\n\n async install(specs: string[], opts?: { dev?: boolean }): Promise<InstallResult> {\n const installed: InstallResultEntry[] = [];\n const warnings: string[] = [];\n\n for (const spec of specs) {\n const resolved = await this.source.resolve(spec);\n const result = await this.source.install(resolved, this.root, opts);\n\n // Detect primary kind via strategies\n const primaryKind = await this.detectKind(result.packageRoot);\n const strategy = this.strategies.get(primaryKind);\n const provides = strategy\n ? await strategy.extractProvides(result.packageRoot)\n : [primaryKind];\n\n // Write to marketplace.lock\n const entry = createMarketplaceEntry({\n version: result.version,\n integrity: result.integrity,\n resolvedPath: relativeToRoot(this.root, result.packageRoot),\n source: resolved.source,\n primaryKind,\n provides,\n });\n\n await addToMarketplaceLock(this.root, result.id, entry);\n\n // Cache manifest\n await this.cacheManifest(result.id, result.packageRoot, primaryKind, result.integrity);\n\n // Run post-install hook\n if (strategy?.afterInstall) {\n try {\n await strategy.afterInstall(result.id, result.packageRoot, this);\n } catch (err) {\n warnings.push(`afterInstall for ${result.id}: ${(err as Error).message}`);\n }\n }\n\n installed.push({\n id: result.id,\n version: result.version,\n primaryKind,\n provides,\n packageRoot: result.packageRoot,\n });\n }\n\n return { installed, warnings };\n }\n\n // -------------------------------------------------------------------------\n // Uninstall\n // -------------------------------------------------------------------------\n\n async uninstall(packageIds: string[]): Promise<void> {\n for (const id of packageIds) {\n // Run pre-uninstall hook\n const entry = await this.getEntry(id);\n if (entry) {\n const strategy = this.strategies.get(entry.primaryKind);\n if (strategy?.beforeUninstall) {\n await strategy.beforeUninstall(id, this);\n }\n }\n\n // Remove from lock\n await removeFromMarketplaceLock(this.root, id);\n\n // Remove from manifest cache\n await removeCacheEntry(this.root, id);\n\n // Remove from disk\n await this.source.remove(id, this.root);\n }\n }\n\n // -------------------------------------------------------------------------\n // Link / Unlink\n // -------------------------------------------------------------------------\n\n async link(packagePath: string): Promise<InstallResultEntry> {\n const absPath = path.resolve(this.root, packagePath);\n\n // Path traversal guard — linked path must be within workspace root\n if (!absPath.startsWith(this.root)) {\n throw new Error(`Path \"${packagePath}\" is outside workspace root — refusing to link`);\n }\n\n const pkgJson = JSON.parse(\n await fs.readFile(\n path.join(absPath, 'package.json'), 'utf-8',\n ),\n );\n const id: string = pkgJson.name;\n const version: string = pkgJson.version ?? '0.0.0';\n\n const primaryKind = await this.detectKind(absPath);\n const strategy = this.strategies.get(primaryKind);\n const provides = strategy\n ? await strategy.extractProvides(absPath)\n : [primaryKind];\n\n const integrity = await computeIntegrity(absPath);\n\n const entry = createMarketplaceEntry({\n version,\n integrity,\n resolvedPath: relativeToRoot(this.root, absPath),\n source: 'local',\n primaryKind,\n provides,\n });\n\n await addToMarketplaceLock(this.root, id, entry);\n await this.cacheManifest(id, absPath, primaryKind, integrity);\n\n if (strategy?.afterInstall) {\n await strategy.afterInstall(id, absPath, this);\n }\n\n return { id, version, primaryKind, provides, packageRoot: absPath };\n }\n\n async unlink(packageId: string): Promise<void> {\n await removeFromMarketplaceLock(this.root, packageId);\n await removeCacheEntry(this.root, packageId);\n }\n\n // -------------------------------------------------------------------------\n // Update\n // -------------------------------------------------------------------------\n\n async update(packageIds?: string[]): Promise<InstallResult> {\n const diag = new DiagnosticCollector();\n const lock = await readMarketplaceLock(this.root, diag);\n if (!lock) {return { installed: [], warnings: ['No marketplace.lock found'] };}\n\n const ids = packageIds ?? Object.keys(lock.installed);\n const specs = ids.filter(id => id in lock.installed);\n\n // Re-install at latest\n return this.install(specs);\n }\n\n // -------------------------------------------------------------------------\n // Enable / Disable\n // -------------------------------------------------------------------------\n\n async enable(packageId: string): Promise<void> {\n const ok = await enablePlugin(this.root, packageId);\n if (!ok) {\n throw new Error(`Package \"${packageId}\" not found in marketplace.lock`);\n }\n }\n\n async disable(packageId: string): Promise<void> {\n const ok = await disablePlugin(this.root, packageId);\n if (!ok) {\n throw new Error(`Package \"${packageId}\" not found in marketplace.lock`);\n }\n }\n\n // -------------------------------------------------------------------------\n // List / GetEntry (MarketplaceServiceAPI)\n // -------------------------------------------------------------------------\n\n async list(filter?: { kind?: EntityKind }): Promise<MarketplaceEntryWithId[]> {\n const diag = new DiagnosticCollector();\n const lock = await readMarketplaceLock(this.root, diag);\n if (!lock) {return [];}\n\n let entries = Object.entries(lock.installed).map(([id, entry]) => ({ ...entry, id }));\n if (filter?.kind) {\n entries = entries.filter(e => e.primaryKind === filter.kind);\n }\n return entries;\n }\n\n async getEntry(packageId: string): Promise<MarketplaceEntry | null> {\n const diag = new DiagnosticCollector();\n const lock = await readMarketplaceLock(this.root, diag);\n return lock?.installed[packageId] ?? null;\n }\n\n // -------------------------------------------------------------------------\n // Sync (scan workspace → populate lock from config-driven globs)\n // -------------------------------------------------------------------------\n\n /**\n * Scan workspace for plugins and adapters using glob patterns.\n * Existing entries are preserved (not overwritten).\n * Patterns come from kb.config.json marketplace.sync.include.\n */\n async sync(opts: {\n include: string[];\n exclude?: string[];\n autoEnable?: boolean;\n }): Promise<SyncResult> {\n const autoEnable = opts.autoEnable ?? false;\n const diag = new DiagnosticCollector();\n const lock = await readMarketplaceLock(this.root, diag) ?? createEmptyLock();\n const existingIds = new Set(Object.keys(lock.installed));\n\n const added: SyncResult['added'] = [];\n const skipped: SyncResult['skipped'] = [];\n\n // Resolve glob patterns to package directories\n const includePatterns = opts.include.map(p => path.join(p, 'package.json'));\n const excludePatterns = opts.exclude ?? [];\n\n const packageJsonPaths = await glob(includePatterns, {\n cwd: this.root,\n ignore: excludePatterns,\n absolute: false,\n });\n\n for (const relPkgJson of packageJsonPaths) {\n const pkgDir = path.resolve(this.root, path.dirname(relPkgJson));\n await this._syncPackage(pkgDir, relPkgJson, existingIds, autoEnable, lock, added, skipped);\n }\n\n await writeMarketplaceLock(this.root, lock);\n\n return { added, skipped, total: Object.keys(lock.installed).length };\n }\n\n private async _syncPackage(\n pkgDir: string,\n _relPkgJson: string,\n existingIds: Set<string>,\n autoEnable: boolean,\n lock: MarketplaceLock,\n added: SyncResult['added'],\n skipped: SyncResult['skipped'],\n ): Promise<void> {\n let pkgName: string;\n let pkgVersion: string;\n try {\n const pkgJson = JSON.parse(await fs.readFile(path.join(pkgDir, 'package.json'), 'utf-8'));\n pkgName = pkgJson.name;\n pkgVersion = pkgJson.version ?? '0.0.0';\n if (!pkgName) { return; }\n } catch {\n return;\n }\n\n if (existingIds.has(pkgName)) {\n skipped.push({ id: pkgName, reason: 'already in lock' });\n return;\n }\n\n let detected = false;\n for (const strategy of this.strategies.values()) {\n const kind = await strategy.detectKind(pkgDir);\n if (kind) { detected = true; break; }\n }\n if (!detected) { return; }\n\n const primaryKind = await this.detectKind(pkgDir);\n const strategy = this.strategies.get(primaryKind);\n const provides = strategy ? await strategy.extractProvides(pkgDir) : [primaryKind];\n const integrity = await computeIntegrity(pkgDir);\n\n const entry = createMarketplaceEntry({\n version: pkgVersion,\n integrity,\n resolvedPath: relativeToRoot(this.root, pkgDir),\n source: 'local',\n primaryKind,\n provides,\n });\n\n if (!autoEnable) { entry.enabled = false; }\n\n lock.installed[pkgName] = entry;\n added.push({ id: pkgName, primaryKind, version: pkgVersion });\n }\n\n // -------------------------------------------------------------------------\n // Doctor\n // -------------------------------------------------------------------------\n\n async doctor(): Promise<DoctorReport> {\n const diag = new DiagnosticCollector();\n const lock = await readMarketplaceLock(this.root, diag);\n const issues: DoctorIssue[] = [];\n\n if (!lock) {\n return { ok: true, total: 0, issues: [{ severity: 'info', packageId: '', message: 'No marketplace.lock found' }] };\n }\n\n const entries = Object.entries(lock.installed);\n\n for (const [id, entry] of entries) {\n const pkgRoot = path.resolve(this.root, entry.resolvedPath);\n\n // Check package exists\n try {\n await fs.access(pkgRoot);\n } catch {\n issues.push({\n severity: 'error',\n packageId: id,\n message: `Package directory not found: ${pkgRoot}`,\n remediation: `Run \"kb marketplace install ${id}\" to restore`,\n });\n continue;\n }\n\n // Check integrity\n if (entry.integrity) {\n const computed = await computeIntegrity(pkgRoot);\n if (computed && computed !== entry.integrity) {\n issues.push({\n severity: 'warning',\n packageId: id,\n message: `Integrity mismatch: expected ${entry.integrity}, got ${computed}`,\n remediation: `Re-install: kb marketplace install ${id}`,\n });\n }\n }\n\n // Check signature\n if (!entry.signature) {\n issues.push({\n severity: 'info',\n packageId: id,\n message: 'Package is not signed',\n remediation: 'Publish through the official marketplace to get a platform signature',\n });\n }\n }\n\n return {\n ok: issues.filter(i => i.severity === 'error').length === 0,\n total: entries.length,\n issues,\n };\n }\n\n // -------------------------------------------------------------------------\n // Private\n // -------------------------------------------------------------------------\n\n private async detectKind(packageRoot: string): Promise<EntityKind> {\n // Try each strategy in registration order\n for (const strategy of this.strategies.values()) {\n const kind = await strategy.detectKind(packageRoot);\n if (kind) {return kind;}\n }\n // Default: plugin\n return 'plugin';\n }\n\n private async cacheManifest(\n packageId: string,\n packageRoot: string,\n primaryKind: EntityKind,\n integrity: string,\n ): Promise<void> {\n try {\n if (primaryKind === 'plugin') {\n const diag = new DiagnosticCollector();\n const manifest = await loadManifest(packageRoot, diag);\n if (manifest) {\n await setCacheEntry(this.root, packageId, {\n manifestType: 'plugin',\n manifest,\n cachedAt: new Date().toISOString(),\n integrity,\n });\n }\n } else if (primaryKind === 'adapter') {\n const distPath = path.join(packageRoot, 'dist', 'index.js');\n const mod = await import(pathToFileURL(distPath).href);\n if (mod.manifest) {\n await setCacheEntry(this.root, packageId, {\n manifestType: 'adapter',\n manifest: mod.manifest,\n cachedAt: new Date().toISOString(),\n integrity,\n });\n }\n }\n } catch (err) {\n // Non-fatal — cache miss means slower next discovery, but log for diagnostics\n console.warn(`[marketplace] Failed to cache manifest for \"${packageId}\": ${(err as Error).message}`);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction relativeToRoot(root: string, absPath: string): string {\n const rel = path.relative(root, absPath);\n return rel.startsWith('.') ? rel : `./${rel}`;\n}\n\nasync function computeIntegrity(packageRoot: string): Promise<string> {\n try {\n const content = await fs.readFile(path.join(packageRoot, 'package.json'));\n return `sha256-${crypto.createHash('sha256').update(content).digest('base64')}`;\n } catch {\n return '';\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/marketplace-core",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Marketplace service — unified install/uninstall/enable/disable for all entity types",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"scripts": {
|
|
21
|
+
"clean": "rimraf dist",
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"type-check": "tsc --noEmit",
|
|
25
|
+
"lint": "eslint .",
|
|
26
|
+
"lint:fix": "eslint . --fix",
|
|
27
|
+
"test": "vitest run --passWithNoTests -c ../../vitest.config.ts",
|
|
28
|
+
"test:watch": "vitest -c ../../vitest.config.ts"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@kb-labs/marketplace-contracts": "^0.1.1",
|
|
32
|
+
"@kb-labs/core-discovery": "^1.5.0",
|
|
33
|
+
"@kb-labs/core-platform": "^1.5.0",
|
|
34
|
+
"@kb-labs/plugin-contracts": "^1.3.0",
|
|
35
|
+
"glob": "^11.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"tsup": "^8.5.0",
|
|
39
|
+
"vitest": "^3.2.4",
|
|
40
|
+
"@kb-labs/devkit": "link:../../../../infra/kb-labs-devkit"
|
|
41
|
+
}
|
|
42
|
+
}
|