prpm 0.2.0 → 1.0.0

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.
Files changed (48) hide show
  1. package/dist/index.js +14257 -109
  2. package/package.json +11 -9
  3. package/dist/__tests__/e2e/test-helpers.js +0 -153
  4. package/dist/commands/buy-credits.js +0 -224
  5. package/dist/commands/catalog.js +0 -365
  6. package/dist/commands/collections.js +0 -655
  7. package/dist/commands/config.js +0 -161
  8. package/dist/commands/credits.js +0 -186
  9. package/dist/commands/index.js +0 -184
  10. package/dist/commands/info.js +0 -78
  11. package/dist/commands/init.js +0 -684
  12. package/dist/commands/install.js +0 -829
  13. package/dist/commands/list.js +0 -198
  14. package/dist/commands/login.js +0 -316
  15. package/dist/commands/outdated.js +0 -130
  16. package/dist/commands/playground.js +0 -637
  17. package/dist/commands/popular.js +0 -33
  18. package/dist/commands/publish.js +0 -803
  19. package/dist/commands/schema.js +0 -41
  20. package/dist/commands/search.js +0 -446
  21. package/dist/commands/starred.js +0 -147
  22. package/dist/commands/subscribe.js +0 -211
  23. package/dist/commands/telemetry.js +0 -104
  24. package/dist/commands/trending.js +0 -86
  25. package/dist/commands/uninstall.js +0 -120
  26. package/dist/commands/update.js +0 -121
  27. package/dist/commands/upgrade.js +0 -121
  28. package/dist/commands/whoami.js +0 -83
  29. package/dist/core/claude-config.js +0 -91
  30. package/dist/core/cursor-config.js +0 -130
  31. package/dist/core/downloader.js +0 -64
  32. package/dist/core/errors.js +0 -29
  33. package/dist/core/filesystem.js +0 -246
  34. package/dist/core/lockfile.js +0 -292
  35. package/dist/core/marketplace-converter.js +0 -224
  36. package/dist/core/prompts.js +0 -62
  37. package/dist/core/registry-client.js +0 -305
  38. package/dist/core/schema-validator.js +0 -74
  39. package/dist/core/telemetry.js +0 -253
  40. package/dist/core/user-config.js +0 -147
  41. package/dist/types/registry.js +0 -12
  42. package/dist/types.js +0 -9
  43. package/dist/utils/license-extractor.js +0 -122
  44. package/dist/utils/multi-package.js +0 -117
  45. package/dist/utils/parallel-publisher.js +0 -144
  46. package/dist/utils/script-executor.js +0 -72
  47. package/dist/utils/snippet-extractor.js +0 -77
  48. package/dist/utils/webapp-url.js +0 -44
@@ -1,803 +0,0 @@
1
- "use strict";
2
- /**
3
- * Publish command implementation
4
- */
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.handlePublish = handlePublish;
40
- exports.createPublishCommand = createPublishCommand;
41
- const commander_1 = require("commander");
42
- const promises_1 = require("fs/promises");
43
- const path_1 = require("path");
44
- const tar = __importStar(require("tar"));
45
- const os_1 = require("os");
46
- const crypto_1 = require("crypto");
47
- const registry_client_1 = require("@pr-pm/registry-client");
48
- const user_config_1 = require("../core/user-config");
49
- const telemetry_1 = require("../core/telemetry");
50
- const errors_1 = require("../core/errors");
51
- const marketplace_converter_1 = require("../core/marketplace-converter");
52
- const schema_validator_1 = require("../core/schema-validator");
53
- const license_extractor_1 = require("../utils/license-extractor");
54
- const snippet_extractor_1 = require("../utils/snippet-extractor");
55
- const script_executor_1 = require("../utils/script-executor");
56
- /**
57
- * Try to find and load manifest files
58
- * Checks for:
59
- * 1. prpm.json (native format) - returns single manifest or array of packages and collections
60
- * 2. .claude/marketplace.json (Claude format) - returns all plugins as separate manifests
61
- * 3. .claude-plugin/marketplace.json (Claude format - alternative location) - returns all plugins
62
- */
63
- async function findAndLoadManifests() {
64
- // Try prpm.json first (native format)
65
- const prpmJsonPath = (0, path_1.join)(process.cwd(), 'prpm.json');
66
- let prpmJsonExists = false;
67
- let prpmJsonError = null;
68
- try {
69
- const content = await (0, promises_1.readFile)(prpmJsonPath, 'utf-8');
70
- prpmJsonExists = true; // Mark file as found after successful read
71
- const manifest = JSON.parse(content);
72
- // Extract collections if present
73
- const collections = [];
74
- if ('collections' in manifest && Array.isArray(manifest.collections)) {
75
- const rawCollections = manifest.collections;
76
- collections.push(...rawCollections);
77
- }
78
- // Check if this is a multi-package manifest
79
- if ('packages' in manifest && Array.isArray(manifest.packages)) {
80
- const multiManifest = manifest;
81
- // Validate each package in the array
82
- const validatedManifests = multiManifest.packages.map((pkg, idx) => {
83
- // Inherit top-level fields if not specified in package - using explicit undefined checks
84
- const packageWithDefaults = {
85
- name: pkg.name,
86
- version: pkg.version,
87
- description: pkg.description,
88
- format: pkg.format,
89
- files: pkg.files,
90
- author: pkg.author ?? multiManifest.author,
91
- license: pkg.license ?? multiManifest.license,
92
- repository: pkg.repository ?? multiManifest.repository,
93
- homepage: pkg.homepage ?? multiManifest.homepage,
94
- documentation: pkg.documentation ?? multiManifest.documentation,
95
- organization: pkg.organization ?? multiManifest.organization,
96
- private: pkg.private ?? multiManifest.private,
97
- tags: pkg.tags ?? multiManifest.tags,
98
- keywords: pkg.keywords ?? multiManifest.keywords,
99
- subtype: pkg.subtype,
100
- dependencies: pkg.dependencies,
101
- peerDependencies: pkg.peerDependencies,
102
- engines: pkg.engines,
103
- main: pkg.main,
104
- };
105
- return validateManifest(packageWithDefaults);
106
- });
107
- return { manifests: validatedManifests, collections, source: 'prpm.json (multi-package)' };
108
- }
109
- // Collections-only manifest (no packages)
110
- if (collections.length > 0) {
111
- return { manifests: [], collections, source: 'prpm.json (collections-only)' };
112
- }
113
- // Single package manifest
114
- const validated = validateManifest(manifest);
115
- return { manifests: [validated], collections, source: 'prpm.json' };
116
- }
117
- catch (error) {
118
- // Store error for later
119
- prpmJsonError = error;
120
- // If it's a validation or parsing error, throw it immediately (don't try marketplace.json)
121
- if (prpmJsonExists && error instanceof Error && (error.message.includes('Invalid JSON') ||
122
- error.message.includes('Manifest validation failed') ||
123
- error.message.includes('Claude skill') ||
124
- error.message.includes('SKILL.md'))) {
125
- throw error;
126
- }
127
- // Otherwise, prpm.json not found or other error, try marketplace.json
128
- }
129
- // Try .claude/marketplace.json (Claude format)
130
- const marketplaceJsonPath = (0, path_1.join)(process.cwd(), '.claude', 'marketplace.json');
131
- try {
132
- const content = await (0, promises_1.readFile)(marketplaceJsonPath, 'utf-8');
133
- const marketplaceData = JSON.parse(content);
134
- if (!(0, marketplace_converter_1.validateMarketplaceJson)(marketplaceData)) {
135
- throw new Error('Invalid marketplace.json format');
136
- }
137
- // Convert each plugin in marketplace.json to a separate PRPM manifest
138
- const manifests = [];
139
- for (let i = 0; i < marketplaceData.plugins.length; i++) {
140
- const manifest = (0, marketplace_converter_1.marketplaceToManifest)(marketplaceData, i);
141
- const validated = validateManifest(manifest);
142
- manifests.push(validated);
143
- }
144
- return { manifests, collections: [], source: '.claude/marketplace.json' };
145
- }
146
- catch (error) {
147
- // marketplace.json not found or invalid at .claude path, try .claude-plugin
148
- }
149
- // Try .claude-plugin/marketplace.json (alternative Claude format)
150
- const marketplaceJsonPluginPath = (0, path_1.join)(process.cwd(), '.claude-plugin', 'marketplace.json');
151
- try {
152
- const content = await (0, promises_1.readFile)(marketplaceJsonPluginPath, 'utf-8');
153
- const marketplaceData = JSON.parse(content);
154
- if (!(0, marketplace_converter_1.validateMarketplaceJson)(marketplaceData)) {
155
- throw new Error('Invalid marketplace.json format');
156
- }
157
- // Convert each plugin in marketplace.json to a separate PRPM manifest
158
- const manifests = [];
159
- for (let i = 0; i < marketplaceData.plugins.length; i++) {
160
- const manifest = (0, marketplace_converter_1.marketplaceToManifest)(marketplaceData, i);
161
- const validated = validateManifest(manifest);
162
- manifests.push(validated);
163
- }
164
- return { manifests, collections: [], source: '.claude-plugin/marketplace.json' };
165
- }
166
- catch (error) {
167
- // marketplace.json not found or invalid
168
- }
169
- // No manifest file found
170
- throw new Error('No manifest file found. Expected either:\n' +
171
- ' - prpm.json in the current directory, or\n' +
172
- ' - .claude/marketplace.json (Claude format), or\n' +
173
- ' - .claude-plugin/marketplace.json (Claude format)');
174
- }
175
- /**
176
- * Validate package manifest
177
- */
178
- function validateManifest(manifest) {
179
- // Set default subtype to 'rule' if not provided
180
- if (!manifest.subtype) {
181
- manifest.subtype = 'rule';
182
- }
183
- // First, validate against JSON schema
184
- const schemaValidation = (0, schema_validator_1.validateManifestSchema)(manifest);
185
- if (!schemaValidation.valid) {
186
- const errorMessages = schemaValidation.errors?.join('\n - ') || 'Unknown validation error';
187
- throw new Error(`Manifest validation failed:\n - ${errorMessages}`);
188
- }
189
- // Additional custom validations (beyond what JSON schema can express)
190
- // Check if using enhanced format (file objects)
191
- const hasEnhancedFormat = manifest.files.some(f => typeof f === 'object');
192
- if (hasEnhancedFormat) {
193
- // Check if files have multiple distinct formats
194
- const fileFormats = new Set(manifest.files
195
- .filter(f => typeof f === 'object')
196
- .map(f => f.format));
197
- // Only suggest "collection" if there are multiple distinct formats
198
- if (fileFormats.size > 1 && manifest.subtype !== 'collection') {
199
- console.warn('āš ļø Package contains multiple file formats. Consider setting subtype to "collection" for clarity.');
200
- }
201
- }
202
- // Enforce SKILL.md filename for Claude skills
203
- if (manifest.format === 'claude' && manifest.subtype === 'skill') {
204
- const filePaths = normalizeFilePaths(manifest.files);
205
- const hasSkillMd = filePaths.some(path => path.endsWith('/SKILL.md') || path === 'SKILL.md');
206
- if (!hasSkillMd) {
207
- throw new Error('Claude skills must contain a SKILL.md file.\n' +
208
- 'According to Claude documentation at https://docs.claude.com/en/docs/claude-code/skills,\n' +
209
- 'skills must have a file named SKILL.md in their directory.\n' +
210
- 'Please rename your skill file to SKILL.md (all caps) and update your prpm.json files array.');
211
- }
212
- // Validate skill name length (max 64 characters)
213
- if (manifest.name.length > 64) {
214
- throw new Error(`Claude skill name "${manifest.name}" exceeds 64 character limit (${manifest.name.length} characters).\n` +
215
- 'According to Claude documentation, skill names must be max 64 characters.\n' +
216
- 'Please shorten your package name.');
217
- }
218
- // Validate skill name format (lowercase, numbers, hyphens only)
219
- if (!/^[a-z0-9-]+$/.test(manifest.name)) {
220
- throw new Error(`Claude skill name "${manifest.name}" contains invalid characters.\n` +
221
- 'According to Claude documentation, skill names must use lowercase letters, numbers, and hyphens only.\n' +
222
- 'Please update your package name.');
223
- }
224
- // Validate description length (max 1024 characters)
225
- if (manifest.description.length > 1024) {
226
- throw new Error(`Claude skill description exceeds 1024 character limit (${manifest.description.length} characters).\n` +
227
- 'According to Claude documentation, skill descriptions must be max 1024 characters.\n' +
228
- 'Please shorten your description.');
229
- }
230
- // Warn if description is approaching the limit (80% = 819 chars)
231
- if (manifest.description.length > 819) {
232
- console.warn(`āš ļø Warning: Skill description is ${manifest.description.length}/1024 characters (${Math.round(manifest.description.length / 1024 * 100)}% of limit).\n` +
233
- ' Consider keeping it concise for better discoverability.');
234
- }
235
- // Warn if description is too short (less than 100 chars)
236
- if (manifest.description.length < 100) {
237
- console.warn(`āš ļø Warning: Skill description is only ${manifest.description.length} characters.\n` +
238
- ' Claude uses descriptions for skill discovery - consider adding more detail about:\n' +
239
- ' - What the skill does\n' +
240
- ' - When Claude should use it\n' +
241
- ' - What problems it solves');
242
- }
243
- }
244
- return manifest;
245
- }
246
- /**
247
- * Normalize files array to string paths
248
- * Converts both simple and enhanced formats to string array
249
- */
250
- function normalizeFilePaths(files) {
251
- return files.map(file => {
252
- if (typeof file === 'string') {
253
- return file;
254
- }
255
- else {
256
- return file.path;
257
- }
258
- });
259
- }
260
- /**
261
- * Predict what the scoped package name will be after publishing
262
- * This matches the server-side logic in packages.ts
263
- */
264
- function predictScopedPackageName(manifestName, username, organization) {
265
- const usernameLowercase = username.toLowerCase();
266
- // If organization is specified, use @org-name/
267
- if (organization) {
268
- const orgNameLowercase = organization.toLowerCase();
269
- const expectedPrefix = `@${orgNameLowercase}/`;
270
- if (!manifestName.startsWith(expectedPrefix)) {
271
- return `${expectedPrefix}${manifestName}`;
272
- }
273
- return manifestName;
274
- }
275
- // If package name doesn't already have a scope, add @username/
276
- if (!manifestName.startsWith('@')) {
277
- return `@${usernameLowercase}/${manifestName}`;
278
- }
279
- // Package already has a scope, return as-is
280
- return manifestName;
281
- }
282
- /**
283
- * Create tarball from current directory
284
- */
285
- async function createTarball(manifest) {
286
- const tmpDir = (0, path_1.join)((0, os_1.tmpdir)(), `prpm-${(0, crypto_1.randomBytes)(8).toString('hex')}`);
287
- const tarballPath = (0, path_1.join)(tmpDir, 'package.tar.gz');
288
- try {
289
- // Create temp directory
290
- await (0, promises_1.mkdir)(tmpDir, { recursive: true });
291
- // Get files to include - normalize to string paths
292
- const filePaths = normalizeFilePaths(manifest.files);
293
- // Add standard files if not already included
294
- const standardFiles = ['prpm.json', 'README.md', 'LICENSE'];
295
- for (const file of standardFiles) {
296
- if (!filePaths.includes(file)) {
297
- filePaths.push(file);
298
- }
299
- }
300
- // Check which files exist
301
- const existingFiles = [];
302
- for (const file of filePaths) {
303
- try {
304
- await (0, promises_1.stat)(file);
305
- existingFiles.push(file);
306
- }
307
- catch {
308
- // File doesn't exist, skip
309
- }
310
- }
311
- if (existingFiles.length === 0) {
312
- throw new Error('No package files found to include in tarball');
313
- }
314
- // Create tarball
315
- await tar.create({
316
- gzip: true,
317
- file: tarballPath,
318
- cwd: process.cwd(),
319
- }, existingFiles);
320
- // Read tarball into buffer
321
- const tarballBuffer = await (0, promises_1.readFile)(tarballPath);
322
- // Check size (max 10MB)
323
- const sizeMB = tarballBuffer.length / (1024 * 1024);
324
- if (sizeMB > 10) {
325
- throw new Error(`Package size (${sizeMB.toFixed(2)}MB) exceeds 10MB limit`);
326
- }
327
- return tarballBuffer;
328
- }
329
- catch (error) {
330
- throw error;
331
- }
332
- finally {
333
- // Clean up temp directory
334
- try {
335
- await (0, promises_1.rm)(tmpDir, { recursive: true, force: true });
336
- }
337
- catch {
338
- // Ignore cleanup errors
339
- }
340
- }
341
- }
342
- /**
343
- * Publish a package to the registry
344
- */
345
- async function handlePublish(options) {
346
- const startTime = Date.now();
347
- let success = false;
348
- let error;
349
- let packageName;
350
- let version;
351
- try {
352
- const config = await (0, user_config_1.getConfig)();
353
- // Check if logged in
354
- if (!config.token) {
355
- throw new errors_1.CLIError('āŒ Not logged in. Run "prpm login" first.', 1);
356
- }
357
- console.log('šŸ“¦ Publishing package...\n');
358
- // Read and validate manifests
359
- console.log('šŸ” Validating package manifest(s)...');
360
- const { manifests, collections, source } = await findAndLoadManifests();
361
- // Execute prepublishOnly script if defined (for multi-package manifests)
362
- // This runs before any packages are published
363
- if (source === 'prpm.json (multi-package)' || source === 'prpm.json') {
364
- try {
365
- // Re-read the raw prpm.json to check for scripts
366
- const prpmJsonPath = (0, path_1.join)(process.cwd(), 'prpm.json');
367
- const prpmContent = await (0, promises_1.readFile)(prpmJsonPath, 'utf-8');
368
- const prpmManifest = JSON.parse(prpmContent);
369
- if (prpmManifest.scripts) {
370
- await (0, script_executor_1.executePrepublishOnly)(prpmManifest.scripts);
371
- }
372
- }
373
- catch (error) {
374
- // If script execution fails, abort publish
375
- if (error instanceof Error && error.message.includes('script')) {
376
- throw error;
377
- }
378
- // Ignore other errors (e.g., file not found - shouldn't happen at this point)
379
- }
380
- }
381
- if (manifests.length > 1 || collections.length > 0) {
382
- if (manifests.length > 0) {
383
- console.log(` Found ${manifests.length} package(s) in ${source}`);
384
- if (options.package) {
385
- console.log(` Filtering to package: ${options.package}`);
386
- }
387
- }
388
- if (collections.length > 0) {
389
- console.log(` Found ${collections.length} collection(s) in ${source}`);
390
- if (options.collection) {
391
- console.log(` Filtering to collection: ${options.collection}`);
392
- }
393
- }
394
- console.log(' Will publish each separately\n');
395
- }
396
- // Filter to specific package if requested
397
- let filteredManifests = manifests;
398
- if (options.package) {
399
- filteredManifests = manifests.filter(m => m.name === options.package);
400
- if (filteredManifests.length === 0) {
401
- throw new Error(`Package "${options.package}" not found in manifest. Available packages: ${manifests.map(m => m.name).join(', ')}`);
402
- }
403
- console.log(` āœ“ Found package "${options.package}"\n`);
404
- }
405
- // Get user info to check for organizations (once for all packages)
406
- console.log('šŸ” Checking authentication...');
407
- const client = (0, registry_client_1.getRegistryClient)(config);
408
- let userInfo;
409
- try {
410
- userInfo = await client.whoami();
411
- }
412
- catch (err) {
413
- console.log(' Could not fetch user organizations, publishing as personal packages');
414
- }
415
- console.log('');
416
- // Check for duplicate package names (only in filtered set)
417
- if (filteredManifests.length > 1) {
418
- const nameMap = new Map();
419
- const duplicates = [];
420
- filteredManifests.forEach((manifest, index) => {
421
- const existingIndex = nameMap.get(manifest.name);
422
- if (existingIndex !== undefined) {
423
- duplicates.push(` - "${manifest.name}" appears in positions ${existingIndex + 1} and ${index + 1}`);
424
- }
425
- else {
426
- nameMap.set(manifest.name, index);
427
- }
428
- });
429
- if (duplicates.length > 0) {
430
- console.error('āŒ Duplicate package names detected:\n');
431
- duplicates.forEach(dup => console.error(dup));
432
- console.error('\nāš ļø Each package must have a unique name.');
433
- console.error(' Package names are globally unique per author/organization.');
434
- console.error(' If you want to publish the same package for different formats,');
435
- console.error(' use different names (e.g., "react-rules-cursor" vs "react-rules-claude").\n');
436
- throw new Error('Cannot publish packages with duplicate names');
437
- }
438
- }
439
- // Track published packages and collections
440
- const publishedPackages = [];
441
- const failedPackages = [];
442
- const publishedCollections = [];
443
- const failedCollections = [];
444
- // Publish each manifest (filtered set)
445
- for (let i = 0; i < filteredManifests.length; i++) {
446
- const manifest = filteredManifests[i];
447
- packageName = manifest.name;
448
- version = manifest.version;
449
- if (filteredManifests.length > 1) {
450
- console.log(`\n${'='.repeat(60)}`);
451
- console.log(`šŸ“¦ Publishing plugin ${i + 1} of ${filteredManifests.length}`);
452
- console.log(`${'='.repeat(60)}\n`);
453
- }
454
- try {
455
- // Debug: Log access override logic only if DEBUG env var is set
456
- if (process.env.DEBUG) {
457
- console.log(`\nšŸ” Before access override:`);
458
- console.log(` - manifest.private: ${manifest.private}`);
459
- console.log(` - options.access: ${options.access}`);
460
- }
461
- // Determine access level:
462
- // 1. If --access flag is provided, it overrides manifest setting
463
- // 2. Otherwise, use manifest setting (defaults to false/public if not specified)
464
- let isPrivate;
465
- if (options.access !== undefined) {
466
- // CLI flag explicitly provided - use it
467
- isPrivate = options.access === 'private';
468
- if (process.env.DEBUG) {
469
- console.log(` - Using CLI flag override: ${options.access}`);
470
- }
471
- }
472
- else {
473
- // No CLI flag - use manifest setting
474
- isPrivate = manifest.private || false;
475
- if (process.env.DEBUG) {
476
- console.log(` - Using manifest setting: ${isPrivate}`);
477
- }
478
- }
479
- if (process.env.DEBUG) {
480
- console.log(` - calculated isPrivate: ${isPrivate}`);
481
- }
482
- // Update manifest with final private setting
483
- manifest.private = isPrivate;
484
- if (process.env.DEBUG) {
485
- console.log(` - final manifest.private: ${manifest.private}`);
486
- console.log('');
487
- }
488
- let selectedOrgId;
489
- let selectedOrgName;
490
- // Check if organization is specified in manifest
491
- if (manifest.organization && userInfo) {
492
- const orgFromManifest = userInfo.organizations?.find((org) => org.name === manifest.organization || org.id === manifest.organization);
493
- if (!orgFromManifest) {
494
- throw new Error(`Organization "${manifest.organization}" not found or you are not a member`);
495
- }
496
- // Check if user has publishing rights
497
- if (!['owner', 'admin', 'maintainer'].includes(orgFromManifest.role)) {
498
- throw new Error(`You do not have permission to publish to organization "${orgFromManifest.name}". ` +
499
- `Your role: ${orgFromManifest.role}. Required: owner, admin, or maintainer`);
500
- }
501
- selectedOrgId = orgFromManifest.id;
502
- selectedOrgName = orgFromManifest.name;
503
- }
504
- // Predict what the scoped package name will be
505
- const scopedPackageName = predictScopedPackageName(manifest.name, userInfo?.username || config.username || 'unknown', selectedOrgName || manifest.organization);
506
- console.log(` Source: ${source}`);
507
- console.log(` Package: ${scopedPackageName}@${manifest.version}`);
508
- console.log(` Format: ${manifest.format} | Subtype: ${manifest.subtype}`);
509
- console.log(` Description: ${manifest.description}`);
510
- console.log(` Access: ${manifest.private ? 'private' : 'public'}`);
511
- if (selectedOrgId && userInfo) {
512
- const selectedOrg = userInfo.organizations.find((org) => org.id === selectedOrgId);
513
- console.log(` Publishing to: ${selectedOrg?.name || 'organization'}`);
514
- }
515
- console.log('');
516
- // Extract license information
517
- console.log('šŸ“„ Extracting license information...');
518
- const licenseInfo = await (0, license_extractor_1.extractLicenseInfo)(manifest.repository);
519
- // Update manifest with license information from LICENSE file if found
520
- // Only set fields that aren't already manually specified in prpm.json
521
- if (licenseInfo.type && !manifest.license) {
522
- manifest.license = licenseInfo.type;
523
- }
524
- if (licenseInfo.text && !manifest.license_text) {
525
- manifest.license_text = licenseInfo.text;
526
- }
527
- if (licenseInfo.url && !manifest.license_url) {
528
- manifest.license_url = licenseInfo.url || undefined;
529
- }
530
- // Validate and warn about license (optional - will extract if present)
531
- (0, license_extractor_1.validateLicenseInfo)(licenseInfo, scopedPackageName);
532
- console.log('');
533
- // Extract content snippet
534
- console.log('šŸ“ Extracting content snippet...');
535
- const snippet = await (0, snippet_extractor_1.extractSnippet)(manifest);
536
- if (snippet) {
537
- manifest.snippet = snippet;
538
- }
539
- (0, snippet_extractor_1.validateSnippet)(snippet, scopedPackageName);
540
- console.log('');
541
- // Create tarball
542
- console.log('šŸ“¦ Creating package tarball...');
543
- const tarball = await createTarball(manifest);
544
- // Display size in KB or MB depending on size
545
- const sizeInBytes = tarball.length;
546
- const sizeInKB = sizeInBytes / 1024;
547
- const sizeInMB = sizeInBytes / (1024 * 1024);
548
- let sizeDisplay;
549
- if (sizeInMB >= 1) {
550
- sizeDisplay = `${sizeInMB.toFixed(2)}MB`;
551
- }
552
- else {
553
- sizeDisplay = `${sizeInKB.toFixed(2)}KB`;
554
- }
555
- console.log(` Size: ${sizeDisplay}`);
556
- console.log('');
557
- if (options.dryRun) {
558
- console.log('āœ… Dry run successful! Package is ready to publish.');
559
- publishedPackages.push({
560
- name: scopedPackageName,
561
- version: manifest.version,
562
- url: ''
563
- });
564
- continue;
565
- }
566
- // Publish to registry
567
- console.log('šŸš€ Publishing to registry...');
568
- if (selectedOrgId) {
569
- console.log(` Publishing as organization: ${userInfo.organizations.find((org) => org.id === selectedOrgId)?.name}`);
570
- console.log(` Organization ID: ${selectedOrgId}`);
571
- }
572
- const result = await client.publish(manifest, tarball, selectedOrgId ? { orgId: selectedOrgId } : undefined);
573
- // Determine the webapp URL based on registry URL
574
- let webappUrl;
575
- const registryUrl = config.registryUrl || 'https://registry.prpm.dev';
576
- if (registryUrl.includes('localhost') || registryUrl.includes('127.0.0.1')) {
577
- // Local development - webapp is on port 5173
578
- webappUrl = 'http://localhost:5173';
579
- }
580
- else if (registryUrl.includes('registry.prpm.dev')) {
581
- // Production - webapp is on prpm.dev
582
- webappUrl = 'https://prpm.dev';
583
- }
584
- else {
585
- // Default to registry URL for unknown environments
586
- webappUrl = registryUrl;
587
- }
588
- // Use the name returned from the API (which includes auto-prefixed scope)
589
- const packageUrl = `${webappUrl}/packages/${encodeURIComponent(result.name)}`;
590
- console.log('');
591
- console.log('āœ… Package published successfully!');
592
- console.log('');
593
- console.log(` Package: ${result.name}@${result.version}`);
594
- console.log(` Install: prpm install ${result.name}`);
595
- console.log('');
596
- publishedPackages.push({
597
- name: result.name, // Use scoped name from server
598
- version: result.version,
599
- url: packageUrl
600
- });
601
- }
602
- catch (err) {
603
- const pkgError = err instanceof Error ? err.message : String(err);
604
- // Try to use scoped name if we have user info, otherwise fall back to manifest name
605
- const displayName = userInfo
606
- ? predictScopedPackageName(manifest.name, userInfo.username, manifest.organization)
607
- : manifest.name;
608
- console.error(`\nāŒ Failed to publish ${displayName}: ${pkgError}\n`);
609
- failedPackages.push({
610
- name: displayName,
611
- error: pkgError
612
- });
613
- }
614
- }
615
- // Publish collections if present
616
- if (collections.length > 0) {
617
- // Filter to specific collection if requested
618
- let filteredCollections = collections;
619
- if (options.collection) {
620
- filteredCollections = collections.filter(c => c.id === options.collection);
621
- if (filteredCollections.length === 0) {
622
- throw new Error(`Collection "${options.collection}" not found in manifest. Available collections: ${collections.map(c => c.id).join(', ')}`);
623
- }
624
- console.log(` āœ“ Found collection "${options.collection}"\n`);
625
- }
626
- for (const collection of filteredCollections) {
627
- if (filteredCollections.length > 1) {
628
- console.log(`\n${'='.repeat(60)}`);
629
- console.log(`šŸ“š Publishing collection`);
630
- console.log(`${'='.repeat(60)}\n`);
631
- }
632
- try {
633
- console.log(`šŸ“š Publishing collection "${collection.name}"...`);
634
- console.log(` ID: ${collection.id}`);
635
- console.log(` Packages: ${collection.packages.length}`);
636
- console.log('');
637
- if (options.dryRun) {
638
- console.log('āœ… Dry run successful! Collection is ready to publish.');
639
- publishedCollections.push({
640
- id: collection.id,
641
- name: collection.name,
642
- version: collection.version || '1.0.0'
643
- });
644
- continue;
645
- }
646
- // Import and call the collection publish logic
647
- const { handleCollectionPublish } = await Promise.resolve().then(() => __importStar(require('./collections.js')));
648
- // Create a temporary manifest object for the collection
649
- const collectionData = {
650
- id: collection.id,
651
- name: collection.name,
652
- description: collection.description,
653
- version: collection.version,
654
- category: collection.category,
655
- tags: collection.tags,
656
- icon: collection.icon,
657
- packages: collection.packages.map(pkg => ({
658
- packageId: pkg.packageId,
659
- version: pkg.version,
660
- required: pkg.required !== false,
661
- reason: pkg.reason,
662
- })),
663
- };
664
- const result = await client.createCollection(collectionData);
665
- console.log(`āœ… Collection published successfully!`);
666
- console.log(` Scope: ${result.scope}`);
667
- console.log(` Name: ${result.name_slug}`);
668
- console.log(` Version: ${result.version || '1.0.0'}`);
669
- console.log('');
670
- console.log(`šŸ’” Install: prpm install collections/${result.name_slug}`);
671
- console.log('');
672
- publishedCollections.push({
673
- id: collection.id,
674
- name: collection.name,
675
- version: result.version || '1.0.0'
676
- });
677
- }
678
- catch (err) {
679
- const collError = err instanceof Error ? err.message : String(err);
680
- console.error(`\nāŒ Failed to publish collection ${collection.id}: ${collError}\n`);
681
- failedCollections.push({
682
- id: collection.id,
683
- error: collError
684
- });
685
- }
686
- }
687
- // Add collection results to summary
688
- if (publishedCollections.length > 0) {
689
- console.log(`āœ… Successfully published ${publishedCollections.length} collection(s):`);
690
- publishedCollections.forEach(coll => {
691
- console.log(` - ${coll.name} (${coll.id}) v${coll.version}`);
692
- });
693
- console.log('');
694
- }
695
- if (failedCollections.length > 0) {
696
- console.log(`āŒ Failed to publish ${failedCollections.length} collection(s):`);
697
- failedCollections.forEach(coll => {
698
- console.log(` - ${coll.id}: ${coll.error}`);
699
- });
700
- console.log('');
701
- }
702
- }
703
- // Print summary if multiple packages
704
- if (manifests.length > 1) {
705
- console.log(`\n${'='.repeat(60)}`);
706
- console.log(`šŸ“Š Publishing Summary`);
707
- console.log(`${'='.repeat(60)}\n`);
708
- if (publishedPackages.length > 0) {
709
- console.log(`āœ… Successfully published ${publishedPackages.length} package(s):`);
710
- publishedPackages.forEach(pkg => {
711
- console.log(` - ${pkg.name}@${pkg.version}`);
712
- if (pkg.url) {
713
- console.log(` ${pkg.url}`);
714
- }
715
- });
716
- console.log('');
717
- }
718
- if (failedPackages.length > 0) {
719
- console.log(`āŒ Failed to publish ${failedPackages.length} package(s):`);
720
- failedPackages.forEach(pkg => {
721
- console.log(` - ${pkg.name}: ${pkg.error}`);
722
- });
723
- console.log('');
724
- // Provide hints for common permission errors
725
- if (failedPackages.some(pkg => pkg.error.includes('Forbidden'))) {
726
- console.log('šŸ’” Forbidden errors usually mean:');
727
- console.log(' - The package already exists and you don\'t have permission to update it');
728
- console.log(' - The package belongs to an organization and you\'re not a member with publish rights');
729
- console.log(' - Try: prpm whoami (to check your organization memberships)');
730
- console.log('');
731
- }
732
- }
733
- }
734
- // Success if we published any packages OR collections
735
- success = publishedPackages.length > 0 || publishedCollections.length > 0;
736
- if (failedPackages.length > 0 && publishedPackages.length === 0 && publishedCollections.length === 0) {
737
- // Use the first failed package's error for telemetry
738
- const firstError = failedPackages[0]?.error || 'Unknown error';
739
- throw new errors_1.CLIError(firstError, 1);
740
- }
741
- }
742
- catch (err) {
743
- error = err instanceof Error ? err.message : String(err);
744
- if (err instanceof errors_1.CLIError) {
745
- throw err;
746
- }
747
- let errorMsg = `\nāŒ Failed to publish package: ${error}\n`;
748
- // Provide helpful hints based on error type
749
- if (error.includes('Manifest validation failed')) {
750
- errorMsg += '\nšŸ’” Common validation issues:\n';
751
- errorMsg += ' - Missing required fields (name, version, description, format)\n';
752
- errorMsg += ' - Invalid format or subtype values\n';
753
- errorMsg += ' - Description too short (min 10 chars) or too long (max 500 chars)\n';
754
- errorMsg += ' - Package name must be lowercase with hyphens only\n';
755
- errorMsg += '\nšŸ’” For Claude skills specifically:\n';
756
- errorMsg += ' - Add "subtype": "skill" to your prpm.json\n';
757
- errorMsg += ' - Ensure files include a SKILL.md file\n';
758
- errorMsg += ' - Package name must be max 64 characters\n';
759
- errorMsg += '\nšŸ’” View the schema: prpm schema\n';
760
- }
761
- else if (error.includes('SKILL.md')) {
762
- errorMsg += '\nšŸ’” Claude skills require:\n';
763
- errorMsg += ' - A file named SKILL.md (all caps) in your package\n';
764
- errorMsg += ' - "format": "claude" and "subtype": "skill" in prpm.json\n';
765
- }
766
- else if (error.includes('No manifest file found')) {
767
- errorMsg += '\nšŸ’” Create a manifest file:\n';
768
- errorMsg += ' - Run: prpm init\n';
769
- errorMsg += ' - Or create prpm.json manually\n';
770
- }
771
- throw new errors_1.CLIError(errorMsg, 1);
772
- }
773
- finally {
774
- // Track telemetry
775
- await telemetry_1.telemetry.track({
776
- command: 'publish',
777
- success,
778
- error,
779
- duration: Date.now() - startTime,
780
- data: {
781
- packageName,
782
- version,
783
- dryRun: options.dryRun,
784
- },
785
- });
786
- await telemetry_1.telemetry.shutdown();
787
- }
788
- }
789
- /**
790
- * Create the publish command
791
- */
792
- function createPublishCommand() {
793
- return new commander_1.Command('publish')
794
- .description('Publish packages and collections to the registry')
795
- .option('--access <type>', 'Package access (public or private) - overrides manifest setting')
796
- .option('--tag <tag>', 'NPM-style tag (e.g., latest, beta)', 'latest')
797
- .option('--dry-run', 'Validate package without publishing')
798
- .option('--package <name>', 'Publish only a specific package from multi-package manifest')
799
- .option('--collection <id>', 'Publish only a specific collection from manifest')
800
- .action(async (options) => {
801
- await handlePublish(options);
802
- });
803
- }