prpm 0.0.12 → 0.0.13
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/dist/commands/catalog.js +348 -0
- package/dist/commands/collections.js +19 -0
- package/dist/commands/install.js +1 -0
- package/dist/commands/publish.js +23 -0
- package/dist/core/filesystem.js +41 -0
- package/dist/core/lockfile.js +52 -0
- package/dist/index.js +2 -0
- package/dist/utils/snippet-extractor.js +27 -12
- package/package.json +3 -3
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Catalog command - Discover and catalog existing packages
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.handleCatalog = handleCatalog;
|
|
7
|
+
exports.createCatalogCommand = createCatalogCommand;
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const promises_1 = require("fs/promises");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
const telemetry_1 = require("../core/telemetry");
|
|
12
|
+
/**
|
|
13
|
+
* Detect format and subtype from file path and content
|
|
14
|
+
*/
|
|
15
|
+
function detectPackageInfo(filePath, content) {
|
|
16
|
+
const fileName = (0, path_1.basename)(filePath);
|
|
17
|
+
const lowerFileName = fileName.toLowerCase();
|
|
18
|
+
// Claude skills - SKILL.md files
|
|
19
|
+
if (fileName === 'SKILL.md') {
|
|
20
|
+
const dirName = (0, path_1.basename)((0, path_1.join)(filePath, '..'));
|
|
21
|
+
return {
|
|
22
|
+
format: 'claude',
|
|
23
|
+
subtype: 'skill',
|
|
24
|
+
name: dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Claude agents
|
|
28
|
+
if (filePath.includes('.claude/agents') || filePath.includes('.claude-plugin/agents')) {
|
|
29
|
+
return {
|
|
30
|
+
format: 'claude',
|
|
31
|
+
subtype: 'agent',
|
|
32
|
+
name: fileName.replace(/\.(md|txt)$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Cursor rules
|
|
36
|
+
if (filePath.includes('.cursor/rules') || lowerFileName.endsWith('.mdc')) {
|
|
37
|
+
return {
|
|
38
|
+
format: 'cursor',
|
|
39
|
+
subtype: 'rule',
|
|
40
|
+
name: fileName.replace(/\.(md|mdc|txt)$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
// Windsurf rules
|
|
44
|
+
if (filePath.includes('.windsurf/rules')) {
|
|
45
|
+
return {
|
|
46
|
+
format: 'windsurf',
|
|
47
|
+
subtype: 'rule',
|
|
48
|
+
name: fileName.replace(/\.(md|txt)$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Continue config
|
|
52
|
+
if (filePath.includes('.continue/prompts')) {
|
|
53
|
+
return {
|
|
54
|
+
format: 'continue',
|
|
55
|
+
subtype: 'prompt',
|
|
56
|
+
name: fileName.replace(/\.(md|txt)$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
// Generic markdown files in root that look like prompts
|
|
60
|
+
if (lowerFileName.endsWith('.md') && content.length > 50) {
|
|
61
|
+
return {
|
|
62
|
+
format: 'generic',
|
|
63
|
+
subtype: 'prompt',
|
|
64
|
+
name: fileName.replace(/\.md$/, '').toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Recursively scan directories for packages
|
|
71
|
+
*/
|
|
72
|
+
async function scanDirectory(dirPath, baseDir, scanDir, maxDepth = 5, currentDepth = 0) {
|
|
73
|
+
if (currentDepth > maxDepth) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
const discovered = [];
|
|
77
|
+
try {
|
|
78
|
+
const entries = await (0, promises_1.readdir)(dirPath, { withFileTypes: true });
|
|
79
|
+
for (const entry of entries) {
|
|
80
|
+
const fullPath = (0, path_1.join)(dirPath, entry.name);
|
|
81
|
+
const relativePath = (0, path_1.relative)(baseDir, fullPath);
|
|
82
|
+
// Skip node_modules, .git, and other common dirs
|
|
83
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist' || entry.name === 'build') {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (entry.isDirectory()) {
|
|
87
|
+
// Recursively scan subdirectories
|
|
88
|
+
const subDirPackages = await scanDirectory(fullPath, baseDir, scanDir, maxDepth, currentDepth + 1);
|
|
89
|
+
discovered.push(...subDirPackages);
|
|
90
|
+
}
|
|
91
|
+
else if (entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.mdc') || entry.name.endsWith('.txt'))) {
|
|
92
|
+
// Check if this is a package file
|
|
93
|
+
try {
|
|
94
|
+
const content = await (0, promises_1.readFile)(fullPath, 'utf-8');
|
|
95
|
+
const packageInfo = detectPackageInfo(fullPath, content);
|
|
96
|
+
if (packageInfo) {
|
|
97
|
+
discovered.push({
|
|
98
|
+
path: relativePath,
|
|
99
|
+
format: packageInfo.format,
|
|
100
|
+
subtype: packageInfo.subtype,
|
|
101
|
+
name: packageInfo.name,
|
|
102
|
+
files: [relativePath],
|
|
103
|
+
scanDir,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
// Skip files we can't read
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
// Skip directories we can't read
|
|
115
|
+
}
|
|
116
|
+
return discovered;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Extract description from file content
|
|
120
|
+
* Tries multiple strategies:
|
|
121
|
+
* 1. YAML frontmatter (---\ndescription: ...\n---)
|
|
122
|
+
* 2. Markdown description field (description: ...)
|
|
123
|
+
* 3. First substantial paragraph after title
|
|
124
|
+
*/
|
|
125
|
+
function extractDescription(content) {
|
|
126
|
+
const lines = content.split('\n');
|
|
127
|
+
// Strategy 1: YAML frontmatter
|
|
128
|
+
if (lines[0]?.trim() === '---') {
|
|
129
|
+
let foundClosing = false;
|
|
130
|
+
let frontmatterLines = [];
|
|
131
|
+
for (let i = 1; i < lines.length && i < 50; i++) {
|
|
132
|
+
const line = lines[i];
|
|
133
|
+
if (line.trim() === '---') {
|
|
134
|
+
foundClosing = true;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
frontmatterLines.push(line);
|
|
138
|
+
}
|
|
139
|
+
if (foundClosing && frontmatterLines.length > 0) {
|
|
140
|
+
// Parse YAML-like frontmatter
|
|
141
|
+
for (const line of frontmatterLines) {
|
|
142
|
+
const match = line.match(/^description:\s*(.+)$/i);
|
|
143
|
+
if (match) {
|
|
144
|
+
return match[1].trim().replace(/^["']|["']$/g, '').substring(0, 200);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Strategy 2: Look for "description:" field anywhere in first 20 lines
|
|
150
|
+
for (let i = 0; i < Math.min(lines.length, 20); i++) {
|
|
151
|
+
const line = lines[i];
|
|
152
|
+
const match = line.match(/^description:\s*(.+)$/i);
|
|
153
|
+
if (match) {
|
|
154
|
+
return match[1].trim().replace(/^["']|["']$/g, '').substring(0, 200);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Strategy 3: First substantial non-header paragraph
|
|
158
|
+
let foundTitle = false;
|
|
159
|
+
for (let i = 0; i < Math.min(lines.length, 30); i++) {
|
|
160
|
+
const line = lines[i].trim();
|
|
161
|
+
// Skip empty lines and YAML frontmatter
|
|
162
|
+
if (line === '' || line === '---') {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// Skip markdown headers
|
|
166
|
+
if (line.startsWith('#')) {
|
|
167
|
+
foundTitle = true;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
// Found a substantial line after the title
|
|
171
|
+
if (foundTitle && line.length >= 20 && !line.startsWith('```')) {
|
|
172
|
+
return line.substring(0, 200);
|
|
173
|
+
}
|
|
174
|
+
// If no title found yet but line is substantial, use it
|
|
175
|
+
if (!foundTitle && line.length >= 30) {
|
|
176
|
+
return line.substring(0, 200);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Discover packages in specified directories
|
|
183
|
+
*/
|
|
184
|
+
async function handleCatalog(directories, options) {
|
|
185
|
+
const startTime = Date.now();
|
|
186
|
+
let success = false;
|
|
187
|
+
let error;
|
|
188
|
+
try {
|
|
189
|
+
console.log('🔍 Scanning for packages...\n');
|
|
190
|
+
const allDiscovered = [];
|
|
191
|
+
// Scan each directory
|
|
192
|
+
for (const dir of directories) {
|
|
193
|
+
console.log(` Scanning ${dir}...`);
|
|
194
|
+
try {
|
|
195
|
+
const dirStat = await (0, promises_1.stat)(dir);
|
|
196
|
+
if (!dirStat.isDirectory()) {
|
|
197
|
+
console.log(` ⚠️ Skipping ${dir} (not a directory)`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const discovered = await scanDirectory(dir, dir, dir);
|
|
201
|
+
allDiscovered.push(...discovered);
|
|
202
|
+
console.log(` Found ${discovered.length} package(s) in ${dir}`);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
console.log(` ⚠️ Could not access ${dir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
console.log(`\n✨ Discovered ${allDiscovered.length} package(s) total:\n`);
|
|
209
|
+
if (allDiscovered.length === 0) {
|
|
210
|
+
console.log('No packages found. Try scanning different directories.');
|
|
211
|
+
success = true;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Display discovered packages
|
|
215
|
+
const byFormat = new Map();
|
|
216
|
+
for (const pkg of allDiscovered) {
|
|
217
|
+
if (!byFormat.has(pkg.format)) {
|
|
218
|
+
byFormat.set(pkg.format, []);
|
|
219
|
+
}
|
|
220
|
+
byFormat.get(pkg.format).push(pkg);
|
|
221
|
+
}
|
|
222
|
+
for (const [format, packages] of byFormat.entries()) {
|
|
223
|
+
console.log(`📦 ${format} (${packages.length}):`);
|
|
224
|
+
for (const pkg of packages) {
|
|
225
|
+
console.log(` - ${pkg.name} (${pkg.subtype}): ${pkg.path}`);
|
|
226
|
+
}
|
|
227
|
+
console.log('');
|
|
228
|
+
}
|
|
229
|
+
if (options.dryRun) {
|
|
230
|
+
console.log('🔍 Dry run - would update prpm.json\n');
|
|
231
|
+
success = true;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
// Load or create prpm.json
|
|
235
|
+
const prpmJsonPath = options.output || (0, path_1.join)(process.cwd(), 'prpm.json');
|
|
236
|
+
let manifest;
|
|
237
|
+
if (options.append) {
|
|
238
|
+
try {
|
|
239
|
+
const existingContent = await (0, promises_1.readFile)(prpmJsonPath, 'utf-8');
|
|
240
|
+
const existing = JSON.parse(existingContent);
|
|
241
|
+
// Check if it's a multi-package manifest
|
|
242
|
+
if ('packages' in existing && Array.isArray(existing.packages)) {
|
|
243
|
+
manifest = existing;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Convert single package to multi-package
|
|
247
|
+
manifest = {
|
|
248
|
+
name: 'multi-package',
|
|
249
|
+
version: '1.0.0',
|
|
250
|
+
packages: [existing],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
// File doesn't exist, create new
|
|
256
|
+
manifest = {
|
|
257
|
+
name: 'multi-package',
|
|
258
|
+
version: '1.0.0',
|
|
259
|
+
packages: [],
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
manifest = {
|
|
265
|
+
name: 'multi-package',
|
|
266
|
+
version: '1.0.0',
|
|
267
|
+
packages: [],
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
// Convert discovered packages to manifests
|
|
271
|
+
const existingNames = new Set(manifest.packages.map(p => p.name));
|
|
272
|
+
let addedCount = 0;
|
|
273
|
+
for (const discovered of allDiscovered) {
|
|
274
|
+
// Skip if already exists
|
|
275
|
+
if (existingNames.has(discovered.name)) {
|
|
276
|
+
console.log(` ⚠️ Skipping ${discovered.name} (already in prpm.json)`);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
// Extract description from first file
|
|
280
|
+
let description = `${discovered.format} ${discovered.subtype}`;
|
|
281
|
+
try {
|
|
282
|
+
const firstFilePath = (0, path_1.join)(process.cwd(), discovered.scanDir, discovered.files[0]);
|
|
283
|
+
const content = await (0, promises_1.readFile)(firstFilePath, 'utf-8');
|
|
284
|
+
const extractedDesc = extractDescription(content);
|
|
285
|
+
if (extractedDesc) {
|
|
286
|
+
description = extractedDesc;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
// Use default description
|
|
291
|
+
}
|
|
292
|
+
const packageManifest = {
|
|
293
|
+
name: discovered.name,
|
|
294
|
+
version: '1.0.0',
|
|
295
|
+
description,
|
|
296
|
+
author: '', // User should fill this in
|
|
297
|
+
format: discovered.format,
|
|
298
|
+
subtype: discovered.subtype,
|
|
299
|
+
files: discovered.files,
|
|
300
|
+
};
|
|
301
|
+
manifest.packages.push(packageManifest);
|
|
302
|
+
addedCount++;
|
|
303
|
+
}
|
|
304
|
+
// Write updated prpm.json
|
|
305
|
+
await (0, promises_1.writeFile)(prpmJsonPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
|
|
306
|
+
console.log(`\n✅ Updated ${prpmJsonPath}`);
|
|
307
|
+
console.log(` Added ${addedCount} new package(s)`);
|
|
308
|
+
console.log(` Total: ${manifest.packages.length} package(s)\n`);
|
|
309
|
+
console.log('💡 Next steps:');
|
|
310
|
+
console.log(' 1. Review and edit package metadata in prpm.json');
|
|
311
|
+
console.log(' 2. Add author, license, and other fields');
|
|
312
|
+
console.log(' 3. Run: prpm publish --dry-run to validate');
|
|
313
|
+
console.log(' 4. Run: prpm publish to publish packages\n');
|
|
314
|
+
success = true;
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
error = err instanceof Error ? err.message : String(err);
|
|
318
|
+
console.error(`\n❌ Failed to catalog packages: ${error}\n`);
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
finally {
|
|
322
|
+
await telemetry_1.telemetry.track({
|
|
323
|
+
command: 'catalog',
|
|
324
|
+
success,
|
|
325
|
+
error,
|
|
326
|
+
duration: Date.now() - startTime,
|
|
327
|
+
data: {
|
|
328
|
+
directories: directories.length,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
await telemetry_1.telemetry.shutdown();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Create the catalog command
|
|
336
|
+
*/
|
|
337
|
+
function createCatalogCommand() {
|
|
338
|
+
return new commander_1.Command('catalog')
|
|
339
|
+
.description('Discover and catalog existing packages from directories')
|
|
340
|
+
.argument('[directories...]', 'Directories to scan for packages (defaults to current directory)', ['.'])
|
|
341
|
+
.option('-o, --output <path>', 'Output path for prpm.json (default: ./prpm.json)')
|
|
342
|
+
.option('-a, --append', 'Append to existing prpm.json instead of overwriting')
|
|
343
|
+
.option('--dry-run', 'Show what would be cataloged without making changes')
|
|
344
|
+
.action(async (directories, options) => {
|
|
345
|
+
await handleCatalog(directories, options);
|
|
346
|
+
process.exit(0);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
@@ -47,6 +47,7 @@ const registry_client_1 = require("@pr-pm/registry-client");
|
|
|
47
47
|
const user_config_1 = require("../core/user-config");
|
|
48
48
|
const install_1 = require("./install");
|
|
49
49
|
const telemetry_1 = require("../core/telemetry");
|
|
50
|
+
const lockfile_1 = require("../core/lockfile");
|
|
50
51
|
/**
|
|
51
52
|
* Search collections by query
|
|
52
53
|
*/
|
|
@@ -494,6 +495,7 @@ async function handleCollectionInstall(collectionSpec, options) {
|
|
|
494
495
|
return;
|
|
495
496
|
}
|
|
496
497
|
// Install packages sequentially
|
|
498
|
+
const installedPackageIds = [];
|
|
497
499
|
for (let i = 0; i < packages.length; i++) {
|
|
498
500
|
const pkg = packages[i];
|
|
499
501
|
const progress = `${i + 1}/${packages.length}`;
|
|
@@ -501,8 +503,14 @@ async function handleCollectionInstall(collectionSpec, options) {
|
|
|
501
503
|
console.log(`\n ${progress} Installing ${pkg.packageId}@${pkg.version}...`);
|
|
502
504
|
await (0, install_1.handleInstall)(`${pkg.packageId}@${pkg.version}`, {
|
|
503
505
|
as: pkg.format,
|
|
506
|
+
fromCollection: {
|
|
507
|
+
scope,
|
|
508
|
+
name_slug,
|
|
509
|
+
version: collection.version || version || '1.0.0',
|
|
510
|
+
},
|
|
504
511
|
});
|
|
505
512
|
console.log(` ${progress} ✓ ${pkg.packageId}`);
|
|
513
|
+
installedPackageIds.push(pkg.packageId);
|
|
506
514
|
packagesInstalled++;
|
|
507
515
|
}
|
|
508
516
|
catch (error) {
|
|
@@ -514,11 +522,22 @@ async function handleCollectionInstall(collectionSpec, options) {
|
|
|
514
522
|
}
|
|
515
523
|
}
|
|
516
524
|
}
|
|
525
|
+
// Update lockfile with collection info
|
|
526
|
+
const lockfile = (await (0, lockfile_1.readLockfile)()) || (0, lockfile_1.createLockfile)();
|
|
527
|
+
const collectionKey = `@${scope}/${name_slug}`;
|
|
528
|
+
(0, lockfile_1.addCollectionToLockfile)(lockfile, collectionKey, {
|
|
529
|
+
scope,
|
|
530
|
+
name_slug,
|
|
531
|
+
version: collection.version || version || '1.0.0',
|
|
532
|
+
packages: installedPackageIds,
|
|
533
|
+
});
|
|
534
|
+
await (0, lockfile_1.writeLockfile)(lockfile);
|
|
517
535
|
console.log(`\n✅ Collection installed successfully!`);
|
|
518
536
|
console.log(` ${packagesInstalled}/${packages.length} packages installed`);
|
|
519
537
|
if (packagesFailed > 0) {
|
|
520
538
|
console.log(` ${packagesFailed} optional packages failed`);
|
|
521
539
|
}
|
|
540
|
+
console.log(` 🔒 Collection tracked in lock file`);
|
|
522
541
|
console.log('');
|
|
523
542
|
await telemetry_1.telemetry.track({
|
|
524
543
|
command: 'collections:install',
|
package/dist/commands/install.js
CHANGED
|
@@ -364,6 +364,7 @@ async function handleInstall(packageSpec, options) {
|
|
|
364
364
|
format: pkg.format, // Preserve original package format
|
|
365
365
|
subtype: pkg.subtype, // Preserve original package subtype
|
|
366
366
|
installedPath: destPath,
|
|
367
|
+
fromCollection: options.fromCollection,
|
|
367
368
|
});
|
|
368
369
|
(0, lockfile_1.setPackageIntegrity)(updatedLockfile, packageId, tarball);
|
|
369
370
|
await (0, lockfile_1.writeLockfile)(updatedLockfile);
|
package/dist/commands/publish.js
CHANGED
|
@@ -363,6 +363,29 @@ async function handlePublish(options) {
|
|
|
363
363
|
console.log(' Could not fetch user organizations, publishing as personal packages');
|
|
364
364
|
}
|
|
365
365
|
console.log('');
|
|
366
|
+
// Check for duplicate package names
|
|
367
|
+
if (manifests.length > 1) {
|
|
368
|
+
const nameMap = new Map();
|
|
369
|
+
const duplicates = [];
|
|
370
|
+
manifests.forEach((manifest, index) => {
|
|
371
|
+
const existingIndex = nameMap.get(manifest.name);
|
|
372
|
+
if (existingIndex !== undefined) {
|
|
373
|
+
duplicates.push(` - "${manifest.name}" appears in positions ${existingIndex + 1} and ${index + 1}`);
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
nameMap.set(manifest.name, index);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
if (duplicates.length > 0) {
|
|
380
|
+
console.error('❌ Duplicate package names detected:\n');
|
|
381
|
+
duplicates.forEach(dup => console.error(dup));
|
|
382
|
+
console.error('\n⚠️ Each package must have a unique name.');
|
|
383
|
+
console.error(' Package names are globally unique per author/organization.');
|
|
384
|
+
console.error(' If you want to publish the same package for different formats,');
|
|
385
|
+
console.error(' use different names (e.g., "react-rules-cursor" vs "react-rules-claude").\n');
|
|
386
|
+
throw new Error('Cannot publish packages with duplicate names');
|
|
387
|
+
}
|
|
388
|
+
}
|
|
366
389
|
// Track published packages
|
|
367
390
|
const publishedPackages = [];
|
|
368
391
|
const failedPackages = [];
|
package/dist/core/filesystem.js
CHANGED
|
@@ -13,6 +13,8 @@ exports.deleteFile = deleteFile;
|
|
|
13
13
|
exports.fileExists = fileExists;
|
|
14
14
|
exports.generateId = generateId;
|
|
15
15
|
exports.stripAuthorNamespace = stripAuthorNamespace;
|
|
16
|
+
exports.getInstalledFilePath = getInstalledFilePath;
|
|
17
|
+
exports.getInstalledFilePaths = getInstalledFilePaths;
|
|
16
18
|
const fs_1 = require("fs");
|
|
17
19
|
const path_1 = __importDefault(require("path"));
|
|
18
20
|
/**
|
|
@@ -140,3 +142,42 @@ function stripAuthorNamespace(packageId) {
|
|
|
140
142
|
const parts = packageId.split('/');
|
|
141
143
|
return parts[parts.length - 1];
|
|
142
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Get the expected installed file path for a package
|
|
147
|
+
* This matches the logic used by the install command to determine where files are placed
|
|
148
|
+
*
|
|
149
|
+
* @param packageName - Full package name (e.g., '@prpm/typescript-rules')
|
|
150
|
+
* @param format - Package format
|
|
151
|
+
* @param subtype - Package subtype
|
|
152
|
+
* @param fileName - Optional specific file name (defaults to main file)
|
|
153
|
+
* @returns Path where the file will be installed relative to working directory
|
|
154
|
+
*/
|
|
155
|
+
function getInstalledFilePath(packageName, format, subtype, fileName) {
|
|
156
|
+
const destDir = getDestinationDir(format, subtype, packageName);
|
|
157
|
+
const packageBaseName = stripAuthorNamespace(packageName);
|
|
158
|
+
// If a specific file name is provided, use it
|
|
159
|
+
if (fileName) {
|
|
160
|
+
return path_1.default.join(destDir, fileName);
|
|
161
|
+
}
|
|
162
|
+
// Claude skills always use SKILL.md
|
|
163
|
+
if (format === 'claude' && subtype === 'skill') {
|
|
164
|
+
return path_1.default.join(destDir, 'SKILL.md');
|
|
165
|
+
}
|
|
166
|
+
// Determine file extension
|
|
167
|
+
const fileExtension = format === 'cursor' ? 'mdc' : 'md';
|
|
168
|
+
// For other formats, use package name as filename
|
|
169
|
+
return path_1.default.join(destDir, `${packageBaseName}.${fileExtension}`);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get all expected installed file paths for a multi-file package
|
|
173
|
+
*
|
|
174
|
+
* @param packageName - Full package name
|
|
175
|
+
* @param format - Package format
|
|
176
|
+
* @param subtype - Package subtype
|
|
177
|
+
* @param fileNames - Array of file names in the package
|
|
178
|
+
* @returns Array of paths where files will be installed
|
|
179
|
+
*/
|
|
180
|
+
function getInstalledFilePaths(packageName, format, subtype, fileNames) {
|
|
181
|
+
const destDir = getDestinationDir(format, subtype, packageName);
|
|
182
|
+
return fileNames.map(fileName => path_1.default.join(destDir, fileName));
|
|
183
|
+
}
|
package/dist/core/lockfile.js
CHANGED
|
@@ -18,6 +18,10 @@ exports.addPackage = addPackage;
|
|
|
18
18
|
exports.removePackage = removePackage;
|
|
19
19
|
exports.listPackages = listPackages;
|
|
20
20
|
exports.getPackage = getPackage;
|
|
21
|
+
exports.addCollectionToLockfile = addCollectionToLockfile;
|
|
22
|
+
exports.getCollectionFromLockfile = getCollectionFromLockfile;
|
|
23
|
+
exports.removeCollectionFromLockfile = removeCollectionFromLockfile;
|
|
24
|
+
exports.listCollectionsFromLockfile = listCollectionsFromLockfile;
|
|
21
25
|
const fs_1 = require("fs");
|
|
22
26
|
const path_1 = require("path");
|
|
23
27
|
const crypto_1 = require("crypto");
|
|
@@ -75,6 +79,7 @@ function addToLockfile(lockfile, packageId, packageInfo) {
|
|
|
75
79
|
format: packageInfo.format,
|
|
76
80
|
subtype: packageInfo.subtype,
|
|
77
81
|
installedPath: packageInfo.installedPath,
|
|
82
|
+
fromCollection: packageInfo.fromCollection,
|
|
78
83
|
};
|
|
79
84
|
lockfile.generated = new Date().toISOString();
|
|
80
85
|
}
|
|
@@ -237,3 +242,50 @@ async function getPackage(packageId) {
|
|
|
237
242
|
}
|
|
238
243
|
return lockfile.packages[packageId];
|
|
239
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Add or update collection in lock file
|
|
247
|
+
*/
|
|
248
|
+
function addCollectionToLockfile(lockfile, collectionKey, collectionInfo) {
|
|
249
|
+
if (!lockfile.collections) {
|
|
250
|
+
lockfile.collections = {};
|
|
251
|
+
}
|
|
252
|
+
lockfile.collections[collectionKey] = {
|
|
253
|
+
scope: collectionInfo.scope,
|
|
254
|
+
name_slug: collectionInfo.name_slug,
|
|
255
|
+
version: collectionInfo.version,
|
|
256
|
+
installedAt: new Date().toISOString(),
|
|
257
|
+
packages: collectionInfo.packages,
|
|
258
|
+
};
|
|
259
|
+
lockfile.generated = new Date().toISOString();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Get collection from lock file
|
|
263
|
+
*/
|
|
264
|
+
function getCollectionFromLockfile(lockfile, collectionKey) {
|
|
265
|
+
if (!lockfile || !lockfile.collections) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return lockfile.collections[collectionKey] || null;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Remove collection from lock file
|
|
272
|
+
*/
|
|
273
|
+
function removeCollectionFromLockfile(lockfile, collectionKey) {
|
|
274
|
+
if (!lockfile.collections) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
delete lockfile.collections[collectionKey];
|
|
278
|
+
lockfile.generated = new Date().toISOString();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* List all collections in lock file
|
|
282
|
+
*/
|
|
283
|
+
function listCollectionsFromLockfile(lockfile) {
|
|
284
|
+
if (!lockfile || !lockfile.collections) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
return Object.entries(lockfile.collections).map(([key, collection]) => ({
|
|
288
|
+
key,
|
|
289
|
+
...collection,
|
|
290
|
+
}));
|
|
291
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const upgrade_1 = require("./commands/upgrade");
|
|
|
25
25
|
const schema_1 = require("./commands/schema");
|
|
26
26
|
const init_1 = require("./commands/init");
|
|
27
27
|
const config_1 = require("./commands/config");
|
|
28
|
+
const catalog_1 = require("./commands/catalog");
|
|
28
29
|
const telemetry_2 = require("./core/telemetry");
|
|
29
30
|
// Read version from package.json
|
|
30
31
|
function getVersion() {
|
|
@@ -44,6 +45,7 @@ program
|
|
|
44
45
|
.version(getVersion());
|
|
45
46
|
// Package creation commands
|
|
46
47
|
program.addCommand((0, init_1.createInitCommand)());
|
|
48
|
+
program.addCommand((0, catalog_1.createCatalogCommand)());
|
|
47
49
|
// Registry commands (new)
|
|
48
50
|
program.addCommand((0, search_1.createSearchCommand)());
|
|
49
51
|
program.addCommand((0, install_1.createInstallCommand)());
|
|
@@ -8,30 +8,45 @@ exports.extractSnippet = extractSnippet;
|
|
|
8
8
|
exports.validateSnippet = validateSnippet;
|
|
9
9
|
const promises_1 = require("fs/promises");
|
|
10
10
|
const path_1 = require("path");
|
|
11
|
+
const filesystem_1 = require("../core/filesystem");
|
|
11
12
|
const MAX_SNIPPET_LENGTH = 2000;
|
|
12
13
|
/**
|
|
13
14
|
* Extract a preview snippet from package files
|
|
14
|
-
*
|
|
15
|
+
* Uses the same path logic as the install command to determine where files will be placed
|
|
15
16
|
*/
|
|
16
17
|
async function extractSnippet(manifest) {
|
|
17
18
|
const cwd = process.cwd();
|
|
18
19
|
try {
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// Validate manifest has required fields
|
|
21
|
+
if (!manifest.format || !manifest.name || !manifest.subtype) {
|
|
22
|
+
console.warn('⚠️ Cannot extract snippet: manifest missing format, name, or subtype');
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
// Determine which file to extract snippet from
|
|
26
|
+
let targetFilePath;
|
|
27
|
+
if (manifest.main) {
|
|
28
|
+
// If main file is specified, use it directly (for multi-file packages)
|
|
29
|
+
targetFilePath = (0, filesystem_1.getInstalledFilePath)(manifest.name, manifest.format, manifest.subtype, manifest.main);
|
|
30
|
+
}
|
|
31
|
+
else if (manifest.files && manifest.files.length > 0) {
|
|
32
|
+
// Get the first file from the manifest
|
|
33
|
+
const firstFile = manifest.files[0];
|
|
34
|
+
const fileName = typeof firstFile === 'string'
|
|
35
|
+
? firstFile
|
|
36
|
+
: firstFile.path;
|
|
37
|
+
// For single-file packages or when no main is specified,
|
|
38
|
+
// use the format-aware path construction
|
|
39
|
+
targetFilePath = (0, filesystem_1.getInstalledFilePath)(manifest.name, manifest.format, manifest.subtype, fileName);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// No files specified, try to construct the default path
|
|
43
|
+
targetFilePath = (0, filesystem_1.getInstalledFilePath)(manifest.name, manifest.format, manifest.subtype);
|
|
44
|
+
}
|
|
45
|
+
const fullPath = (0, path_1.join)(cwd, targetFilePath);
|
|
31
46
|
// Check if path is a directory
|
|
32
47
|
const stats = await (0, promises_1.stat)(fullPath);
|
|
33
48
|
if (stats.isDirectory()) {
|
|
34
|
-
console.warn(`⚠️ Skipping snippet extraction: "${
|
|
49
|
+
console.warn(`⚠️ Skipping snippet extraction: "${targetFilePath}" is a directory`);
|
|
35
50
|
return null;
|
|
36
51
|
}
|
|
37
52
|
// Read the file content
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prpm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "Prompt Package Manager CLI - Install and manage prompt-based files",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"license": "MIT",
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@octokit/rest": "^22.0.0",
|
|
48
|
-
"@pr-pm/registry-client": "^1.2.
|
|
49
|
-
"@pr-pm/types": "^0.1.
|
|
48
|
+
"@pr-pm/registry-client": "^1.2.7",
|
|
49
|
+
"@pr-pm/types": "^0.1.7",
|
|
50
50
|
"ajv": "^8.17.1",
|
|
51
51
|
"ajv-formats": "^3.0.1",
|
|
52
52
|
"commander": "^11.1.0",
|