@larkup/marketplace 0.1.3 → 0.1.5
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/CHANGELOG.md +12 -0
- package/package.json +1 -1
- package/src/tool-installer.ts +49 -14
- package/src/tool-loader.ts +24 -4
- package/src/tool-registry.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @larkup/marketplace
|
|
2
2
|
|
|
3
|
+
## 0.1.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 19767f8: Fix isolated Marketplace tool loading in Docker, improve Video & Audio setup guidance, and reliably index PDF images.
|
|
8
|
+
|
|
9
|
+
## 0.1.4
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- c769d08: Retry marketplace installs against the latest published package when a stale catalog version is unavailable, and improve server and analytics UI behavior.
|
|
14
|
+
|
|
3
15
|
## 0.1.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
package/src/tool-installer.ts
CHANGED
|
@@ -147,8 +147,15 @@ export async function checkSystemDeps(toolId: string): Promise<string[]> {
|
|
|
147
147
|
const descriptor = await getToolById(toolId);
|
|
148
148
|
if (!descriptor?.systemDeps?.length) return [];
|
|
149
149
|
|
|
150
|
+
const target = detectDeploymentTarget();
|
|
151
|
+
|
|
150
152
|
const missing: string[] = [];
|
|
151
153
|
for (const dep of descriptor.systemDeps) {
|
|
154
|
+
// A container cannot normally talk to the host Docker daemon. Do not block
|
|
155
|
+
// installation of tools that use Docker only for optional capabilities
|
|
156
|
+
// (for example DOCX/PPTX editing); those capabilities report their own
|
|
157
|
+
// actionable error when actually used.
|
|
158
|
+
if (target === 'docker' && dep === 'docker') continue;
|
|
152
159
|
try {
|
|
153
160
|
await execAsync(`which ${dep}`);
|
|
154
161
|
} catch {
|
|
@@ -226,7 +233,7 @@ async function execInstall(
|
|
|
226
233
|
version: string,
|
|
227
234
|
target: DeploymentTarget,
|
|
228
235
|
onProgress?: (message: string) => void,
|
|
229
|
-
): Promise<string> {
|
|
236
|
+
): Promise<{ resolvedPath: string; version: string }> {
|
|
230
237
|
const toolsDir = getToolsDir();
|
|
231
238
|
|
|
232
239
|
switch (target) {
|
|
@@ -235,22 +242,39 @@ async function execInstall(
|
|
|
235
242
|
// Initialize the isolated tools directory
|
|
236
243
|
await ensureToolsPackageJson();
|
|
237
244
|
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const { stdout, stderr } = await execAsync(installCmd, {
|
|
245
|
+
const install = async (specifier: string) => {
|
|
246
|
+
const installCmd = `npm install ${specifier} --prefix "${toolsDir}" --save --no-audit --no-fund`;
|
|
247
|
+
onProgress?.(`Running: npm install ${specifier}`);
|
|
248
|
+
const { stderr } = await execAsync(installCmd, {
|
|
243
249
|
cwd: toolsDir,
|
|
244
250
|
timeout: 120_000, // 2 minute timeout
|
|
245
251
|
env: { ...process.env, NODE_ENV: 'production' },
|
|
246
252
|
});
|
|
247
|
-
|
|
248
253
|
if (stderr && !stderr.includes('npm warn')) {
|
|
249
254
|
console.warn(`[marketplace] Install stderr: ${stderr}`);
|
|
250
255
|
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
await install(`${packageName}@${version}`);
|
|
251
260
|
} catch (err) {
|
|
252
261
|
const message = err instanceof Error ? err.message : 'Install command failed';
|
|
253
|
-
|
|
262
|
+
if (!message.includes('ETARGET') && !message.includes('No matching version found')) {
|
|
263
|
+
throw new Error(`Failed to install ${packageName}: ${message}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Hub entries can be cached longer than npm releases. Recover from an
|
|
267
|
+
// obsolete catalog version rather than leaving the marketplace unusable.
|
|
268
|
+
onProgress?.(
|
|
269
|
+
`Catalog version ${version} is unavailable; installing the latest published version…`,
|
|
270
|
+
);
|
|
271
|
+
try {
|
|
272
|
+
await install(`${packageName}@latest`);
|
|
273
|
+
} catch (latestErr) {
|
|
274
|
+
const latestMessage =
|
|
275
|
+
latestErr instanceof Error ? latestErr.message : 'Install command failed';
|
|
276
|
+
throw new Error(`Failed to install ${packageName}: ${latestMessage}`);
|
|
277
|
+
}
|
|
254
278
|
}
|
|
255
279
|
|
|
256
280
|
// Resolve the actual module path
|
|
@@ -263,7 +287,10 @@ async function execInstall(
|
|
|
263
287
|
);
|
|
264
288
|
}
|
|
265
289
|
|
|
266
|
-
|
|
290
|
+
const packageJson = JSON.parse(
|
|
291
|
+
await fs.readFile(path.join(resolvedPath, 'package.json'), 'utf8'),
|
|
292
|
+
) as { version?: string };
|
|
293
|
+
return { resolvedPath, version: packageJson.version ?? version };
|
|
267
294
|
}
|
|
268
295
|
|
|
269
296
|
case 'serverless': {
|
|
@@ -282,7 +309,7 @@ async function execInstall(
|
|
|
282
309
|
await fs.writeFile(pendingPath, JSON.stringify(pending, null, 2), 'utf8');
|
|
283
310
|
|
|
284
311
|
// Return the expected path (will be available after redeploy)
|
|
285
|
-
return path.join(toolsDir, 'node_modules', packageName);
|
|
312
|
+
return { resolvedPath: path.join(toolsDir, 'node_modules', packageName), version };
|
|
286
313
|
}
|
|
287
314
|
|
|
288
315
|
case 'sandbox': {
|
|
@@ -291,7 +318,7 @@ async function execInstall(
|
|
|
291
318
|
await ensureToolsPackageJson();
|
|
292
319
|
const installCmd = `npm install ${packageName}@${version} --prefix "${toolsDir}" --save --no-audit --no-fund`;
|
|
293
320
|
await execAsync(installCmd, { cwd: toolsDir, timeout: 120_000 });
|
|
294
|
-
return path.join(toolsDir, 'node_modules', packageName);
|
|
321
|
+
return { resolvedPath: path.join(toolsDir, 'node_modules', packageName), version };
|
|
295
322
|
}
|
|
296
323
|
|
|
297
324
|
default:
|
|
@@ -374,6 +401,7 @@ export async function installTool(
|
|
|
374
401
|
const isWorkspace = await isWorkspaceTool(descriptor.packageName);
|
|
375
402
|
const target = detectDeploymentTarget();
|
|
376
403
|
let resolvedPath: string;
|
|
404
|
+
let installedVersion = descriptor.version;
|
|
377
405
|
let source: ToolSource;
|
|
378
406
|
|
|
379
407
|
if (isWorkspace) {
|
|
@@ -394,9 +422,14 @@ export async function installTool(
|
|
|
394
422
|
} else {
|
|
395
423
|
// Remote install — download from npm into isolated directory
|
|
396
424
|
report('downloading', 30, `Downloading ${descriptor.packageName}@${descriptor.version}…`);
|
|
397
|
-
|
|
398
|
-
|
|
425
|
+
const installed = await execInstall(
|
|
426
|
+
descriptor.packageName,
|
|
427
|
+
descriptor.version,
|
|
428
|
+
target,
|
|
429
|
+
(msg) => report('downloading', 50, msg),
|
|
399
430
|
);
|
|
431
|
+
resolvedPath = installed.resolvedPath;
|
|
432
|
+
installedVersion = installed.version;
|
|
400
433
|
source = target === 'sandbox' ? 'sandbox' : 'registry';
|
|
401
434
|
report('installing', 70, 'Package installed.');
|
|
402
435
|
}
|
|
@@ -409,7 +442,7 @@ export async function installTool(
|
|
|
409
442
|
|
|
410
443
|
const entry: InstalledTool = {
|
|
411
444
|
id: toolId,
|
|
412
|
-
version:
|
|
445
|
+
version: installedVersion,
|
|
413
446
|
installedAt: new Date().toISOString(),
|
|
414
447
|
packageName: descriptor.packageName,
|
|
415
448
|
resolvedPath,
|
|
@@ -466,6 +499,8 @@ export async function uninstallTool(toolId: string): Promise<void> {
|
|
|
466
499
|
|
|
467
500
|
// Refresh registry cache
|
|
468
501
|
invalidateRegistryCache();
|
|
502
|
+
const { unloadTool } = await import('./tool-loader');
|
|
503
|
+
unloadTool(toolId);
|
|
469
504
|
}
|
|
470
505
|
|
|
471
506
|
export async function updateToolConfig(toolId: string, config: Record<string, any>): Promise<void> {
|
package/src/tool-loader.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
1
2
|
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
2
4
|
import type { InstalledTool } from './types';
|
|
3
5
|
import { getInstalledTool } from './tool-installer';
|
|
4
6
|
|
|
@@ -37,7 +39,7 @@ export async function loadTool<T = any>(toolId: string): Promise<T | null> {
|
|
|
37
39
|
|
|
38
40
|
try {
|
|
39
41
|
// Determine what to import
|
|
40
|
-
const importPath = resolveImportPath(installed);
|
|
42
|
+
const importPath = await resolveImportPath(installed);
|
|
41
43
|
const mod = await import(/* webpackIgnore: true */ importPath);
|
|
42
44
|
moduleCache.set(toolId, mod);
|
|
43
45
|
return mod as T;
|
|
@@ -54,7 +56,7 @@ export async function loadTool<T = any>(toolId: string): Promise<T | null> {
|
|
|
54
56
|
* - `registry` (npm): import from the isolated node_modules via absolute path
|
|
55
57
|
* - `sandbox`: import from the sandbox tool path
|
|
56
58
|
*/
|
|
57
|
-
function resolveImportPath(installed: InstalledTool): string {
|
|
59
|
+
async function resolveImportPath(installed: InstalledTool): Promise<string> {
|
|
58
60
|
switch (installed.source) {
|
|
59
61
|
case 'local':
|
|
60
62
|
// In monorepo, the package name resolves via pnpm workspace linking
|
|
@@ -62,8 +64,10 @@ function resolveImportPath(installed: InstalledTool): string {
|
|
|
62
64
|
|
|
63
65
|
case 'registry':
|
|
64
66
|
case 'sandbox':
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
+
// Node ESM cannot import a package directory by absolute path. Resolve
|
|
68
|
+
// the package's declared ESM entry so isolated marketplace installs work
|
|
69
|
+
// the same way as a normal package-name import.
|
|
70
|
+
return resolvePackageEntry(installed.resolvedPath);
|
|
67
71
|
|
|
68
72
|
default:
|
|
69
73
|
// Fallback: try package name (backwards compat)
|
|
@@ -71,6 +75,22 @@ function resolveImportPath(installed: InstalledTool): string {
|
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
async function resolvePackageEntry(packageDir: string): Promise<string> {
|
|
79
|
+
const manifestPath = path.join(packageDir, 'package.json');
|
|
80
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8')) as {
|
|
81
|
+
exports?: string | { '.': string | { import?: string; default?: string } };
|
|
82
|
+
main?: string;
|
|
83
|
+
};
|
|
84
|
+
const rootExport =
|
|
85
|
+
typeof manifest.exports === 'object' ? manifest.exports['.'] : manifest.exports;
|
|
86
|
+
const entry =
|
|
87
|
+
(typeof rootExport === 'object' ? rootExport.import ?? rootExport.default : rootExport) ??
|
|
88
|
+
manifest.main ??
|
|
89
|
+
'index.js';
|
|
90
|
+
|
|
91
|
+
return pathToFileURL(path.resolve(packageDir, entry)).href;
|
|
92
|
+
}
|
|
93
|
+
|
|
74
94
|
/**
|
|
75
95
|
* Check if a tool is loaded and available for use.
|
|
76
96
|
*/
|
package/src/tool-registry.ts
CHANGED
|
@@ -143,7 +143,7 @@ const FALLBACK_REGISTRY: Record<string, ToolDescriptor> = {
|
|
|
143
143
|
name: 'Video & Audio',
|
|
144
144
|
description: 'Index video and audio files with transcription and frame analysis.',
|
|
145
145
|
category: 'media',
|
|
146
|
-
version: '0.2.
|
|
146
|
+
version: '0.2.1',
|
|
147
147
|
pricing: 'free',
|
|
148
148
|
emoji: '🎬',
|
|
149
149
|
icon: 'Film',
|
|
@@ -226,7 +226,7 @@ const FALLBACK_REGISTRY: Record<string, ToolDescriptor> = {
|
|
|
226
226
|
name: 'Document Editor',
|
|
227
227
|
description: 'AI-powered form filling and document editing with Canvas-style live preview.',
|
|
228
228
|
category: 'utility',
|
|
229
|
-
version: '0.2.
|
|
229
|
+
version: '0.2.3',
|
|
230
230
|
pricing: 'free',
|
|
231
231
|
emoji: '📝',
|
|
232
232
|
icon: 'FileEdit',
|