@larkup/marketplace 0.1.3 → 0.1.4

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @larkup/marketplace
2
2
 
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - c769d08: Retry marketplace installs against the latest published package when a stale catalog version is unavailable, and improve server and analytics UI behavior.
8
+
3
9
  ## 0.1.3
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@larkup/marketplace",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -226,7 +226,7 @@ async function execInstall(
226
226
  version: string,
227
227
  target: DeploymentTarget,
228
228
  onProgress?: (message: string) => void,
229
- ): Promise<string> {
229
+ ): Promise<{ resolvedPath: string; version: string }> {
230
230
  const toolsDir = getToolsDir();
231
231
 
232
232
  switch (target) {
@@ -235,22 +235,39 @@ async function execInstall(
235
235
  // Initialize the isolated tools directory
236
236
  await ensureToolsPackageJson();
237
237
 
238
- const installCmd = `npm install ${packageName}@${version} --prefix "${toolsDir}" --save --no-audit --no-fund`;
239
- onProgress?.(`Running: npm install ${packageName}@${version}`);
240
-
241
- try {
242
- const { stdout, stderr } = await execAsync(installCmd, {
238
+ const install = async (specifier: string) => {
239
+ const installCmd = `npm install ${specifier} --prefix "${toolsDir}" --save --no-audit --no-fund`;
240
+ onProgress?.(`Running: npm install ${specifier}`);
241
+ const { stderr } = await execAsync(installCmd, {
243
242
  cwd: toolsDir,
244
243
  timeout: 120_000, // 2 minute timeout
245
244
  env: { ...process.env, NODE_ENV: 'production' },
246
245
  });
247
-
248
246
  if (stderr && !stderr.includes('npm warn')) {
249
247
  console.warn(`[marketplace] Install stderr: ${stderr}`);
250
248
  }
249
+ };
250
+
251
+ try {
252
+ await install(`${packageName}@${version}`);
251
253
  } catch (err) {
252
254
  const message = err instanceof Error ? err.message : 'Install command failed';
253
- throw new Error(`Failed to install ${packageName}: ${message}`);
255
+ if (!message.includes('ETARGET') && !message.includes('No matching version found')) {
256
+ throw new Error(`Failed to install ${packageName}: ${message}`);
257
+ }
258
+
259
+ // Hub entries can be cached longer than npm releases. Recover from an
260
+ // obsolete catalog version rather than leaving the marketplace unusable.
261
+ onProgress?.(
262
+ `Catalog version ${version} is unavailable; installing the latest published version…`,
263
+ );
264
+ try {
265
+ await install(`${packageName}@latest`);
266
+ } catch (latestErr) {
267
+ const latestMessage =
268
+ latestErr instanceof Error ? latestErr.message : 'Install command failed';
269
+ throw new Error(`Failed to install ${packageName}: ${latestMessage}`);
270
+ }
254
271
  }
255
272
 
256
273
  // Resolve the actual module path
@@ -263,7 +280,10 @@ async function execInstall(
263
280
  );
264
281
  }
265
282
 
266
- return resolvedPath;
283
+ const packageJson = JSON.parse(
284
+ await fs.readFile(path.join(resolvedPath, 'package.json'), 'utf8'),
285
+ ) as { version?: string };
286
+ return { resolvedPath, version: packageJson.version ?? version };
267
287
  }
268
288
 
269
289
  case 'serverless': {
@@ -282,7 +302,7 @@ async function execInstall(
282
302
  await fs.writeFile(pendingPath, JSON.stringify(pending, null, 2), 'utf8');
283
303
 
284
304
  // Return the expected path (will be available after redeploy)
285
- return path.join(toolsDir, 'node_modules', packageName);
305
+ return { resolvedPath: path.join(toolsDir, 'node_modules', packageName), version };
286
306
  }
287
307
 
288
308
  case 'sandbox': {
@@ -291,7 +311,7 @@ async function execInstall(
291
311
  await ensureToolsPackageJson();
292
312
  const installCmd = `npm install ${packageName}@${version} --prefix "${toolsDir}" --save --no-audit --no-fund`;
293
313
  await execAsync(installCmd, { cwd: toolsDir, timeout: 120_000 });
294
- return path.join(toolsDir, 'node_modules', packageName);
314
+ return { resolvedPath: path.join(toolsDir, 'node_modules', packageName), version };
295
315
  }
296
316
 
297
317
  default:
@@ -374,6 +394,7 @@ export async function installTool(
374
394
  const isWorkspace = await isWorkspaceTool(descriptor.packageName);
375
395
  const target = detectDeploymentTarget();
376
396
  let resolvedPath: string;
397
+ let installedVersion = descriptor.version;
377
398
  let source: ToolSource;
378
399
 
379
400
  if (isWorkspace) {
@@ -394,9 +415,14 @@ export async function installTool(
394
415
  } else {
395
416
  // Remote install — download from npm into isolated directory
396
417
  report('downloading', 30, `Downloading ${descriptor.packageName}@${descriptor.version}…`);
397
- resolvedPath = await execInstall(descriptor.packageName, descriptor.version, target, (msg) =>
398
- report('downloading', 50, msg),
418
+ const installed = await execInstall(
419
+ descriptor.packageName,
420
+ descriptor.version,
421
+ target,
422
+ (msg) => report('downloading', 50, msg),
399
423
  );
424
+ resolvedPath = installed.resolvedPath;
425
+ installedVersion = installed.version;
400
426
  source = target === 'sandbox' ? 'sandbox' : 'registry';
401
427
  report('installing', 70, 'Package installed.');
402
428
  }
@@ -409,7 +435,7 @@ export async function installTool(
409
435
 
410
436
  const entry: InstalledTool = {
411
437
  id: toolId,
412
- version: descriptor.version,
438
+ version: installedVersion,
413
439
  installedAt: new Date().toISOString(),
414
440
  packageName: descriptor.packageName,
415
441
  resolvedPath,
@@ -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.0',
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.1',
229
+ version: '0.2.3',
230
230
  pricing: 'free',
231
231
  emoji: '📝',
232
232
  icon: 'FileEdit',