prpm 0.0.1 ā 0.0.2
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 +4 -4
- package/dist/__tests__/e2e/test-helpers.js +150 -0
- package/dist/commands/collections.js +248 -51
- package/dist/commands/index.js +110 -51
- package/dist/commands/info.js +8 -7
- package/dist/commands/install.js +270 -45
- package/dist/commands/list.js +91 -14
- package/dist/commands/login.js +86 -24
- package/dist/commands/outdated.js +3 -2
- package/dist/commands/publish.js +98 -41
- package/dist/commands/remove.js +43 -10
- package/dist/commands/schema.js +37 -0
- package/dist/commands/search.js +263 -38
- package/dist/commands/trending.js +4 -3
- package/dist/commands/uninstall.js +77 -0
- package/dist/commands/update.js +3 -2
- package/dist/commands/upgrade.js +3 -2
- package/dist/commands/whoami.js +25 -1
- package/dist/core/claude-config.js +91 -0
- package/dist/core/cursor-config.js +130 -0
- package/dist/core/filesystem.js +30 -0
- package/dist/core/lockfile.js +57 -0
- package/dist/core/marketplace-converter.js +198 -0
- package/dist/core/schema-validator.js +74 -0
- package/dist/core/telemetry.js +5 -0
- package/dist/index.js +6 -7
- package/dist/types/registry.js +5 -0
- package/package.json +15 -7
- package/dist/commands/add.js +0 -107
- package/dist/commands/deps.js +0 -92
- package/dist/core/config.js +0 -91
package/README.md
CHANGED
|
@@ -109,21 +109,21 @@ prpm add https://example.com/my-rules.md --as claude
|
|
|
109
109
|
|
|
110
110
|
---
|
|
111
111
|
|
|
112
|
-
#### `prpm
|
|
112
|
+
#### `prpm uninstall <id>`
|
|
113
113
|
|
|
114
114
|
Remove an installed package.
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
|
-
prpm
|
|
117
|
+
prpm uninstall react-rules
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
**Examples:**
|
|
121
121
|
```bash
|
|
122
122
|
# Remove by package ID
|
|
123
|
-
prpm
|
|
123
|
+
prpm uninstall typescript-rules
|
|
124
124
|
|
|
125
125
|
# Remove cursor rules
|
|
126
|
-
prpm
|
|
126
|
+
prpm uninstall cursor-rules
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
---
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* E2E Test Helpers
|
|
4
|
+
* Shared utilities for end-to-end CLI testing
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.createTestDir = createTestDir;
|
|
8
|
+
exports.cleanupTestDir = cleanupTestDir;
|
|
9
|
+
exports.createMockPackage = createMockPackage;
|
|
10
|
+
exports.createMockCollection = createMockCollection;
|
|
11
|
+
exports.createMockConfig = createMockConfig;
|
|
12
|
+
exports.createMockFetch = createMockFetch;
|
|
13
|
+
exports.delay = delay;
|
|
14
|
+
exports.setupGlobalMocks = setupGlobalMocks;
|
|
15
|
+
exports.mockProcessExit = mockProcessExit;
|
|
16
|
+
const promises_1 = require("fs/promises");
|
|
17
|
+
const path_1 = require("path");
|
|
18
|
+
const os_1 = require("os");
|
|
19
|
+
/**
|
|
20
|
+
* Create a temporary test directory
|
|
21
|
+
*/
|
|
22
|
+
async function createTestDir() {
|
|
23
|
+
const testDir = (0, path_1.join)((0, os_1.tmpdir)(), `prpm-e2e-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
24
|
+
await (0, promises_1.mkdir)(testDir, { recursive: true });
|
|
25
|
+
return testDir;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Clean up test directory
|
|
29
|
+
*/
|
|
30
|
+
async function cleanupTestDir(testDir) {
|
|
31
|
+
try {
|
|
32
|
+
await (0, promises_1.rm)(testDir, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Ignore cleanup errors
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a mock package manifest
|
|
40
|
+
*/
|
|
41
|
+
async function createMockPackage(testDir, name, type = 'cursor', version = '1.0.0') {
|
|
42
|
+
const manifest = {
|
|
43
|
+
name,
|
|
44
|
+
version,
|
|
45
|
+
description: `Test package ${name}`,
|
|
46
|
+
type,
|
|
47
|
+
author: 'test-author',
|
|
48
|
+
tags: ['test', type],
|
|
49
|
+
};
|
|
50
|
+
const manifestPath = (0, path_1.join)(testDir, 'prpm.json');
|
|
51
|
+
await (0, promises_1.writeFile)(manifestPath, JSON.stringify(manifest, null, 2));
|
|
52
|
+
// Create a sample .cursorrules file
|
|
53
|
+
const rulesPath = (0, path_1.join)(testDir, '.cursorrules');
|
|
54
|
+
await (0, promises_1.writeFile)(rulesPath, '# Test cursor rules\n\nAlways write tests.\n');
|
|
55
|
+
return manifestPath;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a mock collection manifest
|
|
59
|
+
*/
|
|
60
|
+
async function createMockCollection(testDir, id, packages) {
|
|
61
|
+
const manifest = {
|
|
62
|
+
id,
|
|
63
|
+
name: `Test Collection ${id}`,
|
|
64
|
+
description: 'A test collection for E2E testing',
|
|
65
|
+
category: 'development',
|
|
66
|
+
tags: ['test', 'automation'],
|
|
67
|
+
packages,
|
|
68
|
+
icon: 'š¦',
|
|
69
|
+
};
|
|
70
|
+
const manifestPath = (0, path_1.join)(testDir, 'collection.json');
|
|
71
|
+
await (0, promises_1.writeFile)(manifestPath, JSON.stringify(manifest, null, 2));
|
|
72
|
+
return manifestPath;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create a mock user config
|
|
76
|
+
*/
|
|
77
|
+
async function createMockConfig(configPath, options) {
|
|
78
|
+
const config = {
|
|
79
|
+
token: options.token || 'test-token-123',
|
|
80
|
+
registryUrl: options.registryUrl || 'http://localhost:3000',
|
|
81
|
+
};
|
|
82
|
+
await (0, promises_1.mkdir)((0, path_1.join)(configPath, '..'), { recursive: true });
|
|
83
|
+
await (0, promises_1.writeFile)(configPath, JSON.stringify(config, null, 2));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Mock fetch response for registry API
|
|
87
|
+
*/
|
|
88
|
+
function createMockFetch() {
|
|
89
|
+
const responses = new Map();
|
|
90
|
+
const mockFetch = jest.fn(async (url, options) => {
|
|
91
|
+
const key = `${options?.method || 'GET'} ${url}`;
|
|
92
|
+
const response = responses.get(key) || responses.get(url);
|
|
93
|
+
if (!response) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
status: 404,
|
|
97
|
+
statusText: 'Not Found',
|
|
98
|
+
json: async () => ({ error: 'Not found' }),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (typeof response === 'function') {
|
|
102
|
+
return response(url, options);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
ok: true,
|
|
106
|
+
status: 200,
|
|
107
|
+
json: async () => response,
|
|
108
|
+
arrayBuffer: async () => Buffer.from('mock-data').buffer,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
fetch: mockFetch,
|
|
113
|
+
addResponse: (key, response) => {
|
|
114
|
+
responses.set(key, response);
|
|
115
|
+
},
|
|
116
|
+
clear: () => {
|
|
117
|
+
responses.clear();
|
|
118
|
+
mockFetch.mockClear();
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Wait for async operations
|
|
124
|
+
*/
|
|
125
|
+
function delay(ms) {
|
|
126
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Setup global mocks for E2E tests
|
|
130
|
+
*/
|
|
131
|
+
function setupGlobalMocks() {
|
|
132
|
+
// Mock console to reduce noise
|
|
133
|
+
beforeAll(() => {
|
|
134
|
+
jest.spyOn(console, 'log').mockImplementation();
|
|
135
|
+
jest.spyOn(console, 'error').mockImplementation();
|
|
136
|
+
jest.spyOn(console, 'warn').mockImplementation();
|
|
137
|
+
});
|
|
138
|
+
afterAll(() => {
|
|
139
|
+
jest.restoreAllMocks();
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Mock process.exit to throw instead of exiting
|
|
144
|
+
*/
|
|
145
|
+
function mockProcessExit() {
|
|
146
|
+
const mockExit = jest.spyOn(process, 'exit').mockImplementation((code) => {
|
|
147
|
+
throw new Error(`Process exited with code ${code}`);
|
|
148
|
+
});
|
|
149
|
+
return mockExit;
|
|
150
|
+
}
|
|
@@ -2,10 +2,44 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Collections command - Manage package collections
|
|
4
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
|
+
})();
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
exports.handleCollectionsSearch = handleCollectionsSearch;
|
|
7
40
|
exports.handleCollectionsList = handleCollectionsList;
|
|
8
41
|
exports.handleCollectionInfo = handleCollectionInfo;
|
|
42
|
+
exports.handleCollectionPublish = handleCollectionPublish;
|
|
9
43
|
exports.handleCollectionInstall = handleCollectionInstall;
|
|
10
44
|
exports.createCollectionsCommand = createCollectionsCommand;
|
|
11
45
|
const commander_1 = require("commander");
|
|
@@ -22,20 +56,15 @@ async function handleCollectionsSearch(query, options) {
|
|
|
22
56
|
const config = await (0, user_config_1.getConfig)();
|
|
23
57
|
const client = (0, registry_client_1.getRegistryClient)(config);
|
|
24
58
|
console.log(`š Searching collections for "${query}"...\n`);
|
|
25
|
-
//
|
|
59
|
+
// Use server-side search with full-text index
|
|
26
60
|
const result = await client.getCollections({
|
|
61
|
+
query,
|
|
27
62
|
category: options.category,
|
|
28
63
|
tag: options.tag,
|
|
29
64
|
official: options.official,
|
|
30
65
|
limit: options.limit || 50,
|
|
31
66
|
});
|
|
32
|
-
|
|
33
|
-
const queryLower = query.toLowerCase();
|
|
34
|
-
const filtered = result.collections.filter(c => c.name.toLowerCase().includes(queryLower) ||
|
|
35
|
-
c.description.toLowerCase().includes(queryLower) ||
|
|
36
|
-
c.tags.some(tag => tag.toLowerCase().includes(queryLower)) ||
|
|
37
|
-
c.id.toLowerCase().includes(queryLower));
|
|
38
|
-
if (filtered.length === 0) {
|
|
67
|
+
if (result.collections.length === 0) {
|
|
39
68
|
console.log('No collections found matching your search.');
|
|
40
69
|
console.log('\nš” Try:');
|
|
41
70
|
console.log(' - Broadening your search terms');
|
|
@@ -43,45 +72,50 @@ async function handleCollectionsSearch(query, options) {
|
|
|
43
72
|
console.log(' - Browsing all: prpm collections list');
|
|
44
73
|
return;
|
|
45
74
|
}
|
|
46
|
-
console.log(`⨠Found ${
|
|
75
|
+
console.log(`⨠Found ${result.collections.length} collection(s):\n`);
|
|
47
76
|
// Group by official vs community
|
|
48
|
-
const official =
|
|
49
|
-
const community =
|
|
77
|
+
const official = result.collections.filter(c => c.official);
|
|
78
|
+
const community = result.collections.filter(c => !c.official);
|
|
50
79
|
if (official.length > 0) {
|
|
51
|
-
console.log(
|
|
80
|
+
console.log(`š¦ Official Collections (${official.length}):\n`);
|
|
52
81
|
official.forEach(c => {
|
|
53
|
-
const fullName =
|
|
82
|
+
const fullName = c.name_slug.padEnd(35);
|
|
54
83
|
const pkgCount = `(${c.package_count} packages)`.padEnd(15);
|
|
55
84
|
console.log(` ${c.icon || 'š¦'} ${fullName} ${pkgCount} ${c.name}`);
|
|
56
85
|
if (c.description) {
|
|
57
86
|
console.log(` ${c.description.substring(0, 70)}${c.description.length > 70 ? '...' : ''}`);
|
|
58
87
|
}
|
|
88
|
+
console.log(` š¤ by @${c.author}${c.verified ? ' ā' : ''}`);
|
|
59
89
|
console.log(` ā¬ļø ${c.downloads.toLocaleString()} installs Ā· ā ${c.stars.toLocaleString()} stars`);
|
|
60
90
|
console.log('');
|
|
61
91
|
});
|
|
62
92
|
}
|
|
63
93
|
if (community.length > 0) {
|
|
64
|
-
console.log(
|
|
94
|
+
console.log(`\nš Community Collections (${community.length}):\n`);
|
|
65
95
|
community.forEach(c => {
|
|
66
|
-
const fullName =
|
|
96
|
+
const fullName = c.name_slug.padEnd(35);
|
|
67
97
|
const pkgCount = `(${c.package_count} packages)`.padEnd(15);
|
|
68
98
|
console.log(` ${c.icon || 'š¦'} ${fullName} ${pkgCount} ${c.name}`);
|
|
69
99
|
if (c.description) {
|
|
70
100
|
console.log(` ${c.description.substring(0, 70)}${c.description.length > 70 ? '...' : ''}`);
|
|
71
101
|
}
|
|
102
|
+
console.log(` š¤ by @${c.author}${c.verified ? ' ā' : ''}`);
|
|
72
103
|
console.log(` ā¬ļø ${c.downloads.toLocaleString()} installs Ā· ā ${c.stars.toLocaleString()} stars`);
|
|
73
104
|
console.log('');
|
|
74
105
|
});
|
|
75
106
|
}
|
|
76
|
-
|
|
77
|
-
console.log(
|
|
107
|
+
// Show results count
|
|
108
|
+
console.log(`\nš Found: ${result.collections.length} matching collection${result.collections.length === 1 ? '' : 's'} (searched ${result.total} total)\n`);
|
|
109
|
+
console.log(`š” View details: prpm collection info <collection>`);
|
|
110
|
+
console.log(`š” Install: prpm install <collection>`);
|
|
78
111
|
await telemetry_1.telemetry.track({
|
|
79
112
|
command: 'collections:search',
|
|
80
113
|
success: true,
|
|
81
114
|
duration: Date.now() - startTime,
|
|
82
115
|
data: {
|
|
83
116
|
query: query.substring(0, 100),
|
|
84
|
-
count:
|
|
117
|
+
count: result.collections.length,
|
|
118
|
+
total: result.total,
|
|
85
119
|
filters: options,
|
|
86
120
|
},
|
|
87
121
|
});
|
|
@@ -97,6 +131,9 @@ async function handleCollectionsSearch(query, options) {
|
|
|
97
131
|
});
|
|
98
132
|
process.exit(1);
|
|
99
133
|
}
|
|
134
|
+
finally {
|
|
135
|
+
await telemetry_1.telemetry.shutdown();
|
|
136
|
+
}
|
|
100
137
|
}
|
|
101
138
|
/**
|
|
102
139
|
* List available collections
|
|
@@ -112,7 +149,7 @@ async function handleCollectionsList(options) {
|
|
|
112
149
|
tag: options.tag,
|
|
113
150
|
official: options.official,
|
|
114
151
|
scope: options.scope,
|
|
115
|
-
limit:
|
|
152
|
+
limit: 500, // Increased limit to show more collections
|
|
116
153
|
});
|
|
117
154
|
if (result.collections.length === 0) {
|
|
118
155
|
console.log('No collections found matching your criteria.');
|
|
@@ -122,39 +159,51 @@ async function handleCollectionsList(options) {
|
|
|
122
159
|
const official = result.collections.filter(c => c.official);
|
|
123
160
|
const community = result.collections.filter(c => !c.official);
|
|
124
161
|
if (official.length > 0) {
|
|
125
|
-
console.log(
|
|
162
|
+
console.log(`š¦ Official Collections (${official.length}):\n`);
|
|
126
163
|
official.forEach(c => {
|
|
127
|
-
const fullName =
|
|
164
|
+
const fullName = c.name_slug.padEnd(35);
|
|
128
165
|
const pkgCount = `(${c.package_count} packages)`.padEnd(15);
|
|
129
166
|
console.log(` ${c.icon || 'š¦'} ${fullName} ${pkgCount} ${c.name}`);
|
|
130
167
|
if (c.description) {
|
|
131
168
|
console.log(` ${c.description.substring(0, 70)}${c.description.length > 70 ? '...' : ''}`);
|
|
132
169
|
}
|
|
170
|
+
console.log(` š¤ by @${c.author}${c.verified ? ' ā' : ''}`);
|
|
133
171
|
console.log(` ā¬ļø ${c.downloads.toLocaleString()} installs Ā· ā ${c.stars.toLocaleString()} stars`);
|
|
134
172
|
console.log('');
|
|
135
173
|
});
|
|
136
174
|
}
|
|
137
175
|
if (community.length > 0) {
|
|
138
|
-
console.log(
|
|
176
|
+
console.log(`\nš Community Collections (${community.length}):\n`);
|
|
139
177
|
community.forEach(c => {
|
|
140
|
-
const fullName =
|
|
178
|
+
const fullName = c.name_slug.padEnd(35);
|
|
141
179
|
const pkgCount = `(${c.package_count} packages)`.padEnd(15);
|
|
142
180
|
console.log(` ${c.icon || 'š¦'} ${fullName} ${pkgCount} ${c.name}`);
|
|
143
181
|
if (c.description) {
|
|
144
182
|
console.log(` ${c.description.substring(0, 70)}${c.description.length > 70 ? '...' : ''}`);
|
|
145
183
|
}
|
|
184
|
+
console.log(` š¤ by @${c.author}${c.verified ? ' ā' : ''}`);
|
|
146
185
|
console.log(` ā¬ļø ${c.downloads.toLocaleString()} installs Ā· ā ${c.stars.toLocaleString()} stars`);
|
|
147
186
|
console.log('');
|
|
148
187
|
});
|
|
149
188
|
}
|
|
150
|
-
|
|
151
|
-
|
|
189
|
+
// Show total from API (which includes all collections, not just the ones returned)
|
|
190
|
+
const showing = result.collections.length;
|
|
191
|
+
const total = result.total;
|
|
192
|
+
if (showing < total) {
|
|
193
|
+
console.log(`\nš Showing ${showing} of ${total} collection${total === 1 ? '' : 's'}\n`);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
console.log(`\nš Total: ${total} collection${total === 1 ? '' : 's'}\n`);
|
|
197
|
+
}
|
|
198
|
+
console.log(`š” View details: prpm collection info <collection>`);
|
|
199
|
+
console.log(`š” Install: prpm install <collection>`);
|
|
152
200
|
await telemetry_1.telemetry.track({
|
|
153
201
|
command: 'collections:list',
|
|
154
202
|
success: true,
|
|
155
203
|
duration: Date.now() - startTime,
|
|
156
204
|
data: {
|
|
157
205
|
count: result.collections.length,
|
|
206
|
+
total: result.total,
|
|
158
207
|
filters: options,
|
|
159
208
|
},
|
|
160
209
|
});
|
|
@@ -170,6 +219,9 @@ async function handleCollectionsList(options) {
|
|
|
170
219
|
});
|
|
171
220
|
process.exit(1);
|
|
172
221
|
}
|
|
222
|
+
finally {
|
|
223
|
+
await telemetry_1.telemetry.shutdown();
|
|
224
|
+
}
|
|
173
225
|
}
|
|
174
226
|
/**
|
|
175
227
|
* Show collection details
|
|
@@ -177,16 +229,28 @@ async function handleCollectionsList(options) {
|
|
|
177
229
|
async function handleCollectionInfo(collectionSpec) {
|
|
178
230
|
const startTime = Date.now();
|
|
179
231
|
try {
|
|
180
|
-
// Parse collection spec: @scope/
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
232
|
+
// Parse collection spec: @scope/name_slug, scope/name_slug, or just name_slug (defaults to 'collection' scope)
|
|
233
|
+
let scope;
|
|
234
|
+
let name_slug;
|
|
235
|
+
let version;
|
|
236
|
+
const matchWithScope = collectionSpec.match(/^@?([^/]+)\/([^/@]+)(?:@(.+))?$/);
|
|
237
|
+
if (matchWithScope) {
|
|
238
|
+
// Has explicit scope: @scope/name or scope/name
|
|
239
|
+
[, scope, name_slug, version] = matchWithScope;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
// No scope, assume 'collection' scope: just name or name@version
|
|
243
|
+
const matchNoScope = collectionSpec.match(/^([^/@]+)(?:@(.+))?$/);
|
|
244
|
+
if (!matchNoScope) {
|
|
245
|
+
throw new Error('Invalid collection format. Use: name, @scope/name, or scope/name (optionally with @version)');
|
|
246
|
+
}
|
|
247
|
+
[, name_slug, version] = matchNoScope;
|
|
248
|
+
scope = 'collection'; // Default scope
|
|
184
249
|
}
|
|
185
|
-
const [, scope, id, version] = match;
|
|
186
250
|
const config = await (0, user_config_1.getConfig)();
|
|
187
251
|
const client = (0, registry_client_1.getRegistryClient)(config);
|
|
188
|
-
console.log(`š¦ Loading collection:
|
|
189
|
-
const collection = await client.getCollection(scope,
|
|
252
|
+
console.log(`š¦ Loading collection: ${scope === 'collection' ? name_slug : `@${scope}/${name_slug}`}...\n`);
|
|
253
|
+
const collection = await client.getCollection(scope, name_slug, version);
|
|
190
254
|
// Header
|
|
191
255
|
console.log(`${collection.icon || 'š¦'} ${collection.name}`);
|
|
192
256
|
console.log(`${'='.repeat(collection.name.length + 2)}`);
|
|
@@ -215,9 +279,9 @@ async function handleCollectionInfo(collectionSpec) {
|
|
|
215
279
|
if (requiredPkgs.length > 0) {
|
|
216
280
|
console.log(' Required:');
|
|
217
281
|
requiredPkgs.forEach((pkg, i) => {
|
|
218
|
-
console.log(` ${i + 1}. ā ${pkg
|
|
219
|
-
if (pkg.package) {
|
|
220
|
-
console.log(` ${pkg.package.description
|
|
282
|
+
console.log(` ${i + 1}. ā ${pkg?.package?.name}@${pkg.version || 'latest'}`);
|
|
283
|
+
if (pkg.package && pkg.package.description) {
|
|
284
|
+
console.log(` ${pkg.package.description}`);
|
|
221
285
|
}
|
|
222
286
|
if (pkg.reason) {
|
|
223
287
|
console.log(` š” ${pkg.reason}`);
|
|
@@ -228,9 +292,9 @@ async function handleCollectionInfo(collectionSpec) {
|
|
|
228
292
|
if (optionalPkgs.length > 0) {
|
|
229
293
|
console.log(' Optional:');
|
|
230
294
|
optionalPkgs.forEach((pkg, i) => {
|
|
231
|
-
console.log(` ${i + 1}. ā ${pkg
|
|
232
|
-
if (pkg.package) {
|
|
233
|
-
console.log(` ${pkg.package.description
|
|
295
|
+
console.log(` ${i + 1}. ā ${pkg?.package?.name}@${pkg.version || 'latest'}`);
|
|
296
|
+
if (pkg.package && pkg.package.description) {
|
|
297
|
+
console.log(` ${pkg.package.description}`);
|
|
234
298
|
}
|
|
235
299
|
if (pkg.reason) {
|
|
236
300
|
console.log(` š” ${pkg.reason}`);
|
|
@@ -240,9 +304,17 @@ async function handleCollectionInfo(collectionSpec) {
|
|
|
240
304
|
}
|
|
241
305
|
// Installation
|
|
242
306
|
console.log('š” Install:');
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
307
|
+
if (scope === 'collection') {
|
|
308
|
+
console.log(` prpm install ${name_slug}`);
|
|
309
|
+
if (optionalPkgs.length > 0) {
|
|
310
|
+
console.log(` prpm install ${name_slug} --skip-optional # Skip optional packages`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
console.log(` prpm install @${scope}/${name_slug}`);
|
|
315
|
+
if (optionalPkgs.length > 0) {
|
|
316
|
+
console.log(` prpm install @${scope}/${name_slug} --skip-optional # Skip optional packages`);
|
|
317
|
+
}
|
|
246
318
|
}
|
|
247
319
|
console.log('');
|
|
248
320
|
await telemetry_1.telemetry.track({
|
|
@@ -251,7 +323,7 @@ async function handleCollectionInfo(collectionSpec) {
|
|
|
251
323
|
duration: Date.now() - startTime,
|
|
252
324
|
data: {
|
|
253
325
|
scope,
|
|
254
|
-
|
|
326
|
+
name_slug,
|
|
255
327
|
packageCount: collection.packages.length,
|
|
256
328
|
},
|
|
257
329
|
});
|
|
@@ -267,6 +339,109 @@ async function handleCollectionInfo(collectionSpec) {
|
|
|
267
339
|
});
|
|
268
340
|
process.exit(1);
|
|
269
341
|
}
|
|
342
|
+
finally {
|
|
343
|
+
await telemetry_1.telemetry.shutdown();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Publish/create a collection
|
|
348
|
+
*/
|
|
349
|
+
async function handleCollectionPublish(manifestPath = './collection.json') {
|
|
350
|
+
const startTime = Date.now();
|
|
351
|
+
try {
|
|
352
|
+
const config = await (0, user_config_1.getConfig)();
|
|
353
|
+
const client = (0, registry_client_1.getRegistryClient)(config);
|
|
354
|
+
// Check authentication
|
|
355
|
+
if (!config.token) {
|
|
356
|
+
console.error('\nā Authentication required. Run `prpm login` first.\n');
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
console.log('š¦ Publishing collection...\n');
|
|
360
|
+
// Read collection manifest
|
|
361
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
362
|
+
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
363
|
+
const manifest = JSON.parse(manifestContent);
|
|
364
|
+
// Validate manifest
|
|
365
|
+
const required = ['id', 'name', 'description', 'packages'];
|
|
366
|
+
const missing = required.filter(field => !manifest[field]);
|
|
367
|
+
if (missing.length > 0) {
|
|
368
|
+
throw new Error(`Missing required fields: ${missing.join(', ')}`);
|
|
369
|
+
}
|
|
370
|
+
// Validate id format (must be lowercase alphanumeric with hyphens)
|
|
371
|
+
if (!/^[a-z0-9-]+$/.test(manifest.id)) {
|
|
372
|
+
throw new Error('Collection id must be lowercase alphanumeric with hyphens only');
|
|
373
|
+
}
|
|
374
|
+
// Validate name length
|
|
375
|
+
if (manifest.name.length < 3) {
|
|
376
|
+
throw new Error('Collection name must be at least 3 characters');
|
|
377
|
+
}
|
|
378
|
+
// Validate description length
|
|
379
|
+
if (manifest.description.length < 10) {
|
|
380
|
+
throw new Error('Collection description must be at least 10 characters');
|
|
381
|
+
}
|
|
382
|
+
// Validate packages array
|
|
383
|
+
if (!Array.isArray(manifest.packages) || manifest.packages.length === 0) {
|
|
384
|
+
throw new Error('Collection must include at least one package');
|
|
385
|
+
}
|
|
386
|
+
// Validate each package
|
|
387
|
+
manifest.packages.forEach((pkg, idx) => {
|
|
388
|
+
if (!pkg.packageId) {
|
|
389
|
+
throw new Error(`Package at index ${idx} is missing packageId`);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
console.log(`š Validating collection manifest...`);
|
|
393
|
+
console.log(` Collection: ${manifest.name}`);
|
|
394
|
+
console.log(` ID: ${manifest.id}`);
|
|
395
|
+
console.log(` Packages: ${manifest.packages.length}`);
|
|
396
|
+
console.log('');
|
|
397
|
+
// Publish to registry
|
|
398
|
+
console.log('š Publishing to registry...\n');
|
|
399
|
+
const result = await client.createCollection({
|
|
400
|
+
id: manifest.id,
|
|
401
|
+
name: manifest.name,
|
|
402
|
+
description: manifest.description,
|
|
403
|
+
category: manifest.category,
|
|
404
|
+
tags: manifest.tags,
|
|
405
|
+
packages: manifest.packages.map((pkg) => ({
|
|
406
|
+
packageId: pkg.packageId,
|
|
407
|
+
version: pkg.version,
|
|
408
|
+
required: pkg.required !== false,
|
|
409
|
+
reason: pkg.reason,
|
|
410
|
+
})),
|
|
411
|
+
icon: manifest.icon,
|
|
412
|
+
});
|
|
413
|
+
console.log(`ā
Collection published successfully!`);
|
|
414
|
+
console.log(` Scope: ${result.scope}`);
|
|
415
|
+
console.log(` Name: ${result.name_slug}`);
|
|
416
|
+
console.log(` Version: ${result.version || '1.0.0'}`);
|
|
417
|
+
console.log('');
|
|
418
|
+
console.log(`š” View: prpm collection info @${result.scope}/${result.name_slug}`);
|
|
419
|
+
console.log(`š” Install: prpm install @${result.scope}/${result.name_slug}`);
|
|
420
|
+
console.log('');
|
|
421
|
+
await telemetry_1.telemetry.track({
|
|
422
|
+
command: 'collections:publish',
|
|
423
|
+
success: true,
|
|
424
|
+
duration: Date.now() - startTime,
|
|
425
|
+
data: {
|
|
426
|
+
id: manifest.id,
|
|
427
|
+
packageCount: manifest.packages.length,
|
|
428
|
+
},
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
433
|
+
console.error(`\nā Failed to publish collection: ${errorMessage}\n`);
|
|
434
|
+
await telemetry_1.telemetry.track({
|
|
435
|
+
command: 'collections:publish',
|
|
436
|
+
success: false,
|
|
437
|
+
error: errorMessage,
|
|
438
|
+
duration: Date.now() - startTime,
|
|
439
|
+
});
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
await telemetry_1.telemetry.shutdown();
|
|
444
|
+
}
|
|
270
445
|
}
|
|
271
446
|
/**
|
|
272
447
|
* Install a collection
|
|
@@ -276,19 +451,31 @@ async function handleCollectionInstall(collectionSpec, options) {
|
|
|
276
451
|
let packagesInstalled = 0;
|
|
277
452
|
let packagesFailed = 0;
|
|
278
453
|
try {
|
|
279
|
-
// Parse collection spec
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
454
|
+
// Parse collection spec: @scope/name_slug, scope/name_slug, or just name_slug (defaults to 'collection' scope)
|
|
455
|
+
let scope;
|
|
456
|
+
let name_slug;
|
|
457
|
+
let version;
|
|
458
|
+
const matchWithScope = collectionSpec.match(/^@?([^/]+)\/([^/@]+)(?:@(.+))?$/);
|
|
459
|
+
if (matchWithScope) {
|
|
460
|
+
// Has explicit scope: @scope/name or scope/name
|
|
461
|
+
[, scope, name_slug, version] = matchWithScope;
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
// No scope, assume 'collection' scope: just name or name@version
|
|
465
|
+
const matchNoScope = collectionSpec.match(/^([^/@]+)(?:@(.+))?$/);
|
|
466
|
+
if (!matchNoScope) {
|
|
467
|
+
throw new Error('Invalid collection format. Use: name, @scope/name, or scope/name (optionally with @version)');
|
|
468
|
+
}
|
|
469
|
+
[, name_slug, version] = matchNoScope;
|
|
470
|
+
scope = 'collection'; // Default scope
|
|
283
471
|
}
|
|
284
|
-
const [, scope, id, version] = match;
|
|
285
472
|
const config = await (0, user_config_1.getConfig)();
|
|
286
473
|
const client = (0, registry_client_1.getRegistryClient)(config);
|
|
287
474
|
// Get collection installation plan
|
|
288
|
-
console.log(`š¦ Installing collection:
|
|
475
|
+
console.log(`š¦ Installing collection: ${scope === 'collection' ? name_slug : `@${scope}/${name_slug}`}...\n`);
|
|
289
476
|
const installResult = await client.installCollection({
|
|
290
477
|
scope,
|
|
291
|
-
id,
|
|
478
|
+
id: name_slug,
|
|
292
479
|
version,
|
|
293
480
|
format: options.format,
|
|
294
481
|
skipOptional: options.skipOptional,
|
|
@@ -339,7 +526,7 @@ async function handleCollectionInstall(collectionSpec, options) {
|
|
|
339
526
|
duration: Date.now() - startTime,
|
|
340
527
|
data: {
|
|
341
528
|
scope,
|
|
342
|
-
|
|
529
|
+
name_slug,
|
|
343
530
|
packageCount: packages.length,
|
|
344
531
|
installed: packagesInstalled,
|
|
345
532
|
failed: packagesFailed,
|
|
@@ -362,6 +549,9 @@ async function handleCollectionInstall(collectionSpec, options) {
|
|
|
362
549
|
});
|
|
363
550
|
process.exit(1);
|
|
364
551
|
}
|
|
552
|
+
finally {
|
|
553
|
+
await telemetry_1.telemetry.shutdown();
|
|
554
|
+
}
|
|
365
555
|
}
|
|
366
556
|
/**
|
|
367
557
|
* Create collections command group
|
|
@@ -387,7 +577,7 @@ function createCollectionsCommand() {
|
|
|
387
577
|
category: options.category,
|
|
388
578
|
tag: options.tag,
|
|
389
579
|
official: options.official,
|
|
390
|
-
limit: parseInt(options.limit, 10),
|
|
580
|
+
limit: options.limit ? parseInt(options.limit, 10) : 50,
|
|
391
581
|
});
|
|
392
582
|
});
|
|
393
583
|
// List subcommand
|
|
@@ -404,6 +594,13 @@ function createCollectionsCommand() {
|
|
|
404
594
|
.command('info <collection>')
|
|
405
595
|
.description('Show collection details')
|
|
406
596
|
.action(handleCollectionInfo);
|
|
597
|
+
// Publish subcommand
|
|
598
|
+
command
|
|
599
|
+
.command('publish [manifest]')
|
|
600
|
+
.description('Publish a collection from collection.json')
|
|
601
|
+
.action(async (manifest) => {
|
|
602
|
+
await handleCollectionPublish(manifest);
|
|
603
|
+
});
|
|
407
604
|
// Install handled by main install command with @scope/id syntax
|
|
408
605
|
return command;
|
|
409
606
|
}
|