prpm 0.1.17 → 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.
- package/dist/index.js +14257 -107
- package/package.json +11 -9
- package/dist/__tests__/e2e/test-helpers.js +0 -151
- package/dist/commands/buy-credits.js +0 -224
- package/dist/commands/catalog.js +0 -365
- package/dist/commands/collections.js +0 -655
- package/dist/commands/config.js +0 -161
- package/dist/commands/credits.js +0 -186
- package/dist/commands/index.js +0 -184
- package/dist/commands/info.js +0 -78
- package/dist/commands/init.js +0 -684
- package/dist/commands/install.js +0 -789
- package/dist/commands/list.js +0 -189
- package/dist/commands/login.js +0 -316
- package/dist/commands/outdated.js +0 -130
- package/dist/commands/playground.js +0 -570
- package/dist/commands/popular.js +0 -33
- package/dist/commands/publish.js +0 -803
- package/dist/commands/schema.js +0 -41
- package/dist/commands/search.js +0 -446
- package/dist/commands/subscribe.js +0 -211
- package/dist/commands/telemetry.js +0 -104
- package/dist/commands/trending.js +0 -86
- package/dist/commands/uninstall.js +0 -120
- package/dist/commands/update.js +0 -121
- package/dist/commands/upgrade.js +0 -121
- package/dist/commands/whoami.js +0 -83
- package/dist/core/claude-config.js +0 -91
- package/dist/core/cursor-config.js +0 -130
- package/dist/core/downloader.js +0 -64
- package/dist/core/errors.js +0 -29
- package/dist/core/filesystem.js +0 -242
- package/dist/core/lockfile.js +0 -292
- package/dist/core/marketplace-converter.js +0 -224
- package/dist/core/registry-client.js +0 -305
- package/dist/core/schema-validator.js +0 -74
- package/dist/core/telemetry.js +0 -253
- package/dist/core/user-config.js +0 -147
- package/dist/types/registry.js +0 -12
- package/dist/types.js +0 -36
- package/dist/utils/license-extractor.js +0 -122
- package/dist/utils/multi-package.js +0 -117
- package/dist/utils/parallel-publisher.js +0 -144
- package/dist/utils/script-executor.js +0 -72
- package/dist/utils/snippet-extractor.js +0 -77
- package/dist/utils/webapp-url.js +0 -44
package/dist/core/filesystem.js
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* File system operations for managing prompt files
|
|
4
|
-
*/
|
|
5
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
-
};
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.getDestinationDir = getDestinationDir;
|
|
10
|
-
exports.ensureDirectoryExists = ensureDirectoryExists;
|
|
11
|
-
exports.saveFile = saveFile;
|
|
12
|
-
exports.deleteFile = deleteFile;
|
|
13
|
-
exports.fileExists = fileExists;
|
|
14
|
-
exports.directoryExists = directoryExists;
|
|
15
|
-
exports.autoDetectFormat = autoDetectFormat;
|
|
16
|
-
exports.generateId = generateId;
|
|
17
|
-
exports.stripAuthorNamespace = stripAuthorNamespace;
|
|
18
|
-
exports.getInstalledFilePath = getInstalledFilePath;
|
|
19
|
-
exports.getInstalledFilePaths = getInstalledFilePaths;
|
|
20
|
-
const fs_1 = require("fs");
|
|
21
|
-
const path_1 = __importDefault(require("path"));
|
|
22
|
-
/**
|
|
23
|
-
* Get the destination directory for a package based on format and subtype
|
|
24
|
-
* @param format - Package format (cursor, claude, etc.)
|
|
25
|
-
* @param subtype - Package subtype (skill, agent, rule, etc.)
|
|
26
|
-
* @param name - Package name (optional, only needed for Claude skills which create subdirectories)
|
|
27
|
-
*/
|
|
28
|
-
function getDestinationDir(format, subtype, name) {
|
|
29
|
-
// Strip author namespace from package name to avoid nested directories
|
|
30
|
-
const packageName = stripAuthorNamespace(name);
|
|
31
|
-
switch (format) {
|
|
32
|
-
case 'cursor':
|
|
33
|
-
if (subtype === 'agent')
|
|
34
|
-
return '.cursor/agents';
|
|
35
|
-
if (subtype === 'slash-command')
|
|
36
|
-
return '.cursor/commands';
|
|
37
|
-
return '.cursor/rules';
|
|
38
|
-
case 'claude':
|
|
39
|
-
// Only create subdirectory for skills if name is provided
|
|
40
|
-
if (subtype === 'skill' && packageName)
|
|
41
|
-
return `.claude/skills/${packageName}`;
|
|
42
|
-
if (subtype === 'skill')
|
|
43
|
-
return '.claude/skills';
|
|
44
|
-
if (subtype === 'slash-command')
|
|
45
|
-
return '.claude/commands';
|
|
46
|
-
if (subtype === 'agent')
|
|
47
|
-
return '.claude/agents';
|
|
48
|
-
// Hooks are configured in settings.json, return .claude directory
|
|
49
|
-
if (subtype === 'hook')
|
|
50
|
-
return '.claude';
|
|
51
|
-
return '.claude/agents'; // Default for claude
|
|
52
|
-
case 'continue':
|
|
53
|
-
// Continue has separate directories for prompts (slash commands) and rules
|
|
54
|
-
if (subtype === 'rule')
|
|
55
|
-
return '.continue/rules';
|
|
56
|
-
return '.continue/prompts';
|
|
57
|
-
case 'windsurf':
|
|
58
|
-
return '.windsurf/rules';
|
|
59
|
-
case 'copilot':
|
|
60
|
-
// Copilot has different locations based on subtype:
|
|
61
|
-
// - Repository-wide instructions: .github/copilot-instructions.md
|
|
62
|
-
// - Path-specific instructions: .github/instructions/*.instructions.md
|
|
63
|
-
// - Chat modes: .github/chatmodes/*.chatmode.md
|
|
64
|
-
if (subtype === 'chatmode')
|
|
65
|
-
return '.github/chatmodes';
|
|
66
|
-
// Default to path-specific instructions directory
|
|
67
|
-
return '.github/instructions';
|
|
68
|
-
case 'kiro':
|
|
69
|
-
// Kiro has different locations based on subtype:
|
|
70
|
-
// - Steering files: .kiro/steering/*.md
|
|
71
|
-
// - Hooks: .kiro/hooks/*.kiro.hook (JSON files)
|
|
72
|
-
if (subtype === 'hook')
|
|
73
|
-
return '.kiro/hooks';
|
|
74
|
-
return '.kiro/steering';
|
|
75
|
-
case 'agents.md':
|
|
76
|
-
return '.agents';
|
|
77
|
-
case 'generic':
|
|
78
|
-
return '.prompts';
|
|
79
|
-
case 'mcp':
|
|
80
|
-
return '.mcp/tools';
|
|
81
|
-
default:
|
|
82
|
-
throw new Error(`Unknown format: ${format}`);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Ensure directory exists, creating it if necessary
|
|
87
|
-
*/
|
|
88
|
-
async function ensureDirectoryExists(dirPath) {
|
|
89
|
-
try {
|
|
90
|
-
await fs_1.promises.mkdir(dirPath, { recursive: true });
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
throw new Error(`Failed to create directory ${dirPath}: ${error}`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* Save content to a file
|
|
98
|
-
*/
|
|
99
|
-
async function saveFile(filePath, content) {
|
|
100
|
-
try {
|
|
101
|
-
// Ensure parent directory exists
|
|
102
|
-
const dir = path_1.default.dirname(filePath);
|
|
103
|
-
await ensureDirectoryExists(dir);
|
|
104
|
-
// Write file
|
|
105
|
-
await fs_1.promises.writeFile(filePath, content, 'utf-8');
|
|
106
|
-
}
|
|
107
|
-
catch (error) {
|
|
108
|
-
throw new Error(`Failed to save file ${filePath}: ${error}`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Delete a file
|
|
113
|
-
*/
|
|
114
|
-
async function deleteFile(filePath) {
|
|
115
|
-
try {
|
|
116
|
-
await fs_1.promises.unlink(filePath);
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
const err = error;
|
|
120
|
-
if (err.code === 'ENOENT') {
|
|
121
|
-
// File doesn't exist, that's fine
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
throw new Error(`Failed to delete file ${filePath}: ${error}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Check if a file exists
|
|
129
|
-
*/
|
|
130
|
-
async function fileExists(filePath) {
|
|
131
|
-
try {
|
|
132
|
-
await fs_1.promises.access(filePath);
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Check if a directory exists
|
|
141
|
-
*/
|
|
142
|
-
async function directoryExists(dirPath) {
|
|
143
|
-
try {
|
|
144
|
-
const stats = await fs_1.promises.stat(dirPath);
|
|
145
|
-
return stats.isDirectory();
|
|
146
|
-
}
|
|
147
|
-
catch {
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Auto-detect the format based on existing directories in the current project
|
|
153
|
-
* Returns the format if a matching directory is found, or null if none found
|
|
154
|
-
*/
|
|
155
|
-
async function autoDetectFormat() {
|
|
156
|
-
const formatDirs = [
|
|
157
|
-
{ format: 'cursor', dir: '.cursor' },
|
|
158
|
-
{ format: 'claude', dir: '.claude' },
|
|
159
|
-
{ format: 'continue', dir: '.continue' },
|
|
160
|
-
{ format: 'windsurf', dir: '.windsurf' },
|
|
161
|
-
{ format: 'copilot', dir: '.github/instructions' },
|
|
162
|
-
{ format: 'kiro', dir: '.kiro' },
|
|
163
|
-
{ format: 'agents.md', dir: '.agents' },
|
|
164
|
-
];
|
|
165
|
-
for (const { format, dir } of formatDirs) {
|
|
166
|
-
if (await directoryExists(dir)) {
|
|
167
|
-
return format;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Generate a unique ID from filename
|
|
174
|
-
*/
|
|
175
|
-
function generateId(filename) {
|
|
176
|
-
// Remove extension and convert to kebab-case
|
|
177
|
-
const name = filename.replace(/\.[^/.]+$/, '');
|
|
178
|
-
return name
|
|
179
|
-
.toLowerCase()
|
|
180
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
181
|
-
.replace(/^-+|-+$/g, '');
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Strip author namespace from package ID and return just the package name
|
|
185
|
-
* @example
|
|
186
|
-
* stripAuthorNamespace('@community/git-workflow-manager') // 'git-workflow-manager'
|
|
187
|
-
* stripAuthorNamespace('community/git-workflow-manager') // 'git-workflow-manager'
|
|
188
|
-
* stripAuthorNamespace('@wshobson/commands/agent-orchestration/improve-agent') // 'improve-agent'
|
|
189
|
-
* stripAuthorNamespace('git-workflow-manager') // 'git-workflow-manager'
|
|
190
|
-
*/
|
|
191
|
-
function stripAuthorNamespace(packageId) {
|
|
192
|
-
// Handle undefined or empty string
|
|
193
|
-
if (!packageId) {
|
|
194
|
-
return '';
|
|
195
|
-
}
|
|
196
|
-
// Split by '/' and get the last segment (the actual package name)
|
|
197
|
-
const parts = packageId.split('/');
|
|
198
|
-
return parts[parts.length - 1];
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Get the expected installed file path for a package
|
|
202
|
-
* This matches the logic used by the install command to determine where files are placed
|
|
203
|
-
*
|
|
204
|
-
* @param packageName - Full package name (e.g., '@prpm/typescript-rules')
|
|
205
|
-
* @param format - Package format
|
|
206
|
-
* @param subtype - Package subtype
|
|
207
|
-
* @param fileName - Optional specific file name (defaults to main file)
|
|
208
|
-
* @returns Path where the file will be installed relative to working directory
|
|
209
|
-
*/
|
|
210
|
-
function getInstalledFilePath(packageName, format, subtype, fileName) {
|
|
211
|
-
const destDir = getDestinationDir(format, subtype, packageName);
|
|
212
|
-
const packageBaseName = stripAuthorNamespace(packageName);
|
|
213
|
-
// If a specific file name is provided, use it
|
|
214
|
-
if (fileName) {
|
|
215
|
-
return path_1.default.join(destDir, fileName);
|
|
216
|
-
}
|
|
217
|
-
// Claude skills always use SKILL.md
|
|
218
|
-
if (format === 'claude' && subtype === 'skill') {
|
|
219
|
-
return path_1.default.join(destDir, 'SKILL.md');
|
|
220
|
-
}
|
|
221
|
-
// agents.md uses package-name/AGENTS.md structure
|
|
222
|
-
if (format === 'agents.md') {
|
|
223
|
-
return path_1.default.join(destDir, packageBaseName, 'AGENTS.md');
|
|
224
|
-
}
|
|
225
|
-
// Determine file extension
|
|
226
|
-
const fileExtension = format === 'cursor' ? 'mdc' : 'md';
|
|
227
|
-
// For other formats, use package name as filename
|
|
228
|
-
return path_1.default.join(destDir, `${packageBaseName}.${fileExtension}`);
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Get all expected installed file paths for a multi-file package
|
|
232
|
-
*
|
|
233
|
-
* @param packageName - Full package name
|
|
234
|
-
* @param format - Package format
|
|
235
|
-
* @param subtype - Package subtype
|
|
236
|
-
* @param fileNames - Array of file names in the package
|
|
237
|
-
* @returns Array of paths where files will be installed
|
|
238
|
-
*/
|
|
239
|
-
function getInstalledFilePaths(packageName, format, subtype, fileNames) {
|
|
240
|
-
const destDir = getDestinationDir(format, subtype, packageName);
|
|
241
|
-
return fileNames.map(fileName => path_1.default.join(destDir, fileName));
|
|
242
|
-
}
|
package/dist/core/lockfile.js
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Lock file management for reproducible installations
|
|
4
|
-
* prpm.lock format similar to package-lock.json
|
|
5
|
-
*/
|
|
6
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
exports.readLockfile = readLockfile;
|
|
8
|
-
exports.writeLockfile = writeLockfile;
|
|
9
|
-
exports.createLockfile = createLockfile;
|
|
10
|
-
exports.addToLockfile = addToLockfile;
|
|
11
|
-
exports.setPackageIntegrity = setPackageIntegrity;
|
|
12
|
-
exports.verifyPackageIntegrity = verifyPackageIntegrity;
|
|
13
|
-
exports.getLockedVersion = getLockedVersion;
|
|
14
|
-
exports.isLockfileOutOfSync = isLockfileOutOfSync;
|
|
15
|
-
exports.mergeLockfiles = mergeLockfiles;
|
|
16
|
-
exports.pruneLockfile = pruneLockfile;
|
|
17
|
-
exports.addPackage = addPackage;
|
|
18
|
-
exports.removePackage = removePackage;
|
|
19
|
-
exports.listPackages = listPackages;
|
|
20
|
-
exports.getPackage = getPackage;
|
|
21
|
-
exports.addCollectionToLockfile = addCollectionToLockfile;
|
|
22
|
-
exports.getCollectionFromLockfile = getCollectionFromLockfile;
|
|
23
|
-
exports.removeCollectionFromLockfile = removeCollectionFromLockfile;
|
|
24
|
-
exports.listCollectionsFromLockfile = listCollectionsFromLockfile;
|
|
25
|
-
const fs_1 = require("fs");
|
|
26
|
-
const path_1 = require("path");
|
|
27
|
-
const crypto_1 = require("crypto");
|
|
28
|
-
const LOCKFILE_NAME = 'prpm.lock';
|
|
29
|
-
const LOCKFILE_VERSION = 1;
|
|
30
|
-
/**
|
|
31
|
-
* Read lock file from current directory
|
|
32
|
-
*/
|
|
33
|
-
async function readLockfile(cwd = process.cwd()) {
|
|
34
|
-
try {
|
|
35
|
-
const lockfilePath = (0, path_1.join)(cwd, LOCKFILE_NAME);
|
|
36
|
-
const content = await fs_1.promises.readFile(lockfilePath, 'utf-8');
|
|
37
|
-
return JSON.parse(content);
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
if (error.code === 'ENOENT') {
|
|
41
|
-
return null; // Lock file doesn't exist
|
|
42
|
-
}
|
|
43
|
-
throw new Error(`Failed to read lock file: ${error}`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Write lock file to current directory
|
|
48
|
-
*/
|
|
49
|
-
async function writeLockfile(lockfile, cwd = process.cwd()) {
|
|
50
|
-
try {
|
|
51
|
-
const lockfilePath = (0, path_1.join)(cwd, LOCKFILE_NAME);
|
|
52
|
-
const content = JSON.stringify(lockfile, null, 2);
|
|
53
|
-
await fs_1.promises.writeFile(lockfilePath, content, 'utf-8');
|
|
54
|
-
}
|
|
55
|
-
catch (error) {
|
|
56
|
-
throw new Error(`Failed to write lock file: ${error}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Create new lock file
|
|
61
|
-
*/
|
|
62
|
-
function createLockfile() {
|
|
63
|
-
return {
|
|
64
|
-
version: '1.0.0',
|
|
65
|
-
lockfileVersion: LOCKFILE_VERSION,
|
|
66
|
-
packages: {},
|
|
67
|
-
generated: new Date().toISOString(),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Add package to lock file
|
|
72
|
-
*/
|
|
73
|
-
function addToLockfile(lockfile, packageId, packageInfo) {
|
|
74
|
-
lockfile.packages[packageId] = {
|
|
75
|
-
version: packageInfo.version,
|
|
76
|
-
resolved: packageInfo.tarballUrl,
|
|
77
|
-
integrity: '', // Will be set after download
|
|
78
|
-
dependencies: packageInfo.dependencies,
|
|
79
|
-
format: packageInfo.format,
|
|
80
|
-
subtype: packageInfo.subtype,
|
|
81
|
-
installedPath: packageInfo.installedPath,
|
|
82
|
-
fromCollection: packageInfo.fromCollection,
|
|
83
|
-
hookMetadata: packageInfo.hookMetadata,
|
|
84
|
-
};
|
|
85
|
-
lockfile.generated = new Date().toISOString();
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Update package integrity hash after download
|
|
89
|
-
*/
|
|
90
|
-
function setPackageIntegrity(lockfile, packageId, tarballBuffer) {
|
|
91
|
-
if (!lockfile.packages[packageId]) {
|
|
92
|
-
throw new Error(`Package ${packageId} not found in lock file`);
|
|
93
|
-
}
|
|
94
|
-
const hash = (0, crypto_1.createHash)('sha256').update(tarballBuffer).digest('hex');
|
|
95
|
-
lockfile.packages[packageId].integrity = `sha256-${hash}`;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Verify package integrity
|
|
99
|
-
*/
|
|
100
|
-
function verifyPackageIntegrity(lockfile, packageId, tarballBuffer) {
|
|
101
|
-
const pkg = lockfile.packages[packageId];
|
|
102
|
-
if (!pkg || !pkg.integrity) {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
const hash = (0, crypto_1.createHash)('sha256').update(tarballBuffer).digest('hex');
|
|
106
|
-
const expectedHash = pkg.integrity.replace('sha256-', '');
|
|
107
|
-
return hash === expectedHash;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Get locked version for a package
|
|
111
|
-
*/
|
|
112
|
-
function getLockedVersion(lockfile, packageId) {
|
|
113
|
-
if (!lockfile || !lockfile.packages[packageId]) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
return lockfile.packages[packageId].version;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Check if lock file is out of sync with dependencies
|
|
120
|
-
*/
|
|
121
|
-
function isLockfileOutOfSync(lockfile, requiredPackages) {
|
|
122
|
-
if (!lockfile) {
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
// Check if all required packages are in lock file
|
|
126
|
-
for (const [pkgId, version] of Object.entries(requiredPackages)) {
|
|
127
|
-
const locked = lockfile.packages[pkgId];
|
|
128
|
-
if (!locked || locked.version !== version) {
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Merge lock files (for conflict resolution)
|
|
136
|
-
*/
|
|
137
|
-
function mergeLockfiles(base, incoming) {
|
|
138
|
-
const merged = createLockfile();
|
|
139
|
-
// Merge packages from both lock files
|
|
140
|
-
const allPackages = new Set([
|
|
141
|
-
...Object.keys(base.packages),
|
|
142
|
-
...Object.keys(incoming.packages),
|
|
143
|
-
]);
|
|
144
|
-
for (const pkgId of allPackages) {
|
|
145
|
-
const basePkg = base.packages[pkgId];
|
|
146
|
-
const incomingPkg = incoming.packages[pkgId];
|
|
147
|
-
if (!basePkg) {
|
|
148
|
-
merged.packages[pkgId] = incomingPkg;
|
|
149
|
-
}
|
|
150
|
-
else if (!incomingPkg) {
|
|
151
|
-
merged.packages[pkgId] = basePkg;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
// Both exist - prefer newer version
|
|
155
|
-
const baseVersion = basePkg.version;
|
|
156
|
-
const incomingVersion = incomingPkg.version;
|
|
157
|
-
merged.packages[pkgId] = compareVersions(baseVersion, incomingVersion) >= 0
|
|
158
|
-
? basePkg
|
|
159
|
-
: incomingPkg;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return merged;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Simple semver comparison (returns 1 if a > b, -1 if a < b, 0 if equal)
|
|
166
|
-
*/
|
|
167
|
-
function compareVersions(a, b) {
|
|
168
|
-
const aParts = a.split('.').map(Number);
|
|
169
|
-
const bParts = b.split('.').map(Number);
|
|
170
|
-
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
171
|
-
const aVal = aParts[i] || 0;
|
|
172
|
-
const bVal = bParts[i] || 0;
|
|
173
|
-
if (aVal > bVal)
|
|
174
|
-
return 1;
|
|
175
|
-
if (aVal < bVal)
|
|
176
|
-
return -1;
|
|
177
|
-
}
|
|
178
|
-
return 0;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Prune unused packages from lock file
|
|
182
|
-
*/
|
|
183
|
-
function pruneLockfile(lockfile, requiredPackages) {
|
|
184
|
-
const pruned = { ...lockfile };
|
|
185
|
-
pruned.packages = {};
|
|
186
|
-
for (const pkgId of requiredPackages) {
|
|
187
|
-
if (lockfile.packages[pkgId]) {
|
|
188
|
-
pruned.packages[pkgId] = lockfile.packages[pkgId];
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
pruned.generated = new Date().toISOString();
|
|
192
|
-
return pruned;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Add package to lock file (convenience wrapper)
|
|
196
|
-
*/
|
|
197
|
-
async function addPackage(packageInfo) {
|
|
198
|
-
const lockfile = (await readLockfile()) || createLockfile();
|
|
199
|
-
addToLockfile(lockfile, packageInfo.id, {
|
|
200
|
-
version: packageInfo.version,
|
|
201
|
-
tarballUrl: packageInfo.tarballUrl,
|
|
202
|
-
dependencies: packageInfo.dependencies,
|
|
203
|
-
format: packageInfo.format,
|
|
204
|
-
subtype: packageInfo.subtype,
|
|
205
|
-
installedPath: packageInfo.installedPath,
|
|
206
|
-
});
|
|
207
|
-
await writeLockfile(lockfile);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Remove package from lock file
|
|
211
|
-
*/
|
|
212
|
-
async function removePackage(packageId) {
|
|
213
|
-
const lockfile = await readLockfile();
|
|
214
|
-
if (!lockfile || !lockfile.packages[packageId]) {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
const removed = lockfile.packages[packageId];
|
|
218
|
-
delete lockfile.packages[packageId];
|
|
219
|
-
lockfile.generated = new Date().toISOString();
|
|
220
|
-
await writeLockfile(lockfile);
|
|
221
|
-
return removed;
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* List all packages in lock file
|
|
225
|
-
*/
|
|
226
|
-
async function listPackages() {
|
|
227
|
-
const lockfile = await readLockfile();
|
|
228
|
-
if (!lockfile) {
|
|
229
|
-
return [];
|
|
230
|
-
}
|
|
231
|
-
return Object.entries(lockfile.packages).map(([id, pkg]) => ({
|
|
232
|
-
id,
|
|
233
|
-
...pkg,
|
|
234
|
-
}));
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Get a specific package from lock file
|
|
238
|
-
*/
|
|
239
|
-
async function getPackage(packageId) {
|
|
240
|
-
const lockfile = await readLockfile();
|
|
241
|
-
if (!lockfile || !lockfile.packages[packageId]) {
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
return lockfile.packages[packageId];
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Add or update collection in lock file
|
|
248
|
-
*/
|
|
249
|
-
function addCollectionToLockfile(lockfile, collectionKey, collectionInfo) {
|
|
250
|
-
if (!lockfile.collections) {
|
|
251
|
-
lockfile.collections = {};
|
|
252
|
-
}
|
|
253
|
-
lockfile.collections[collectionKey] = {
|
|
254
|
-
scope: collectionInfo.scope,
|
|
255
|
-
name_slug: collectionInfo.name_slug,
|
|
256
|
-
version: collectionInfo.version,
|
|
257
|
-
installedAt: new Date().toISOString(),
|
|
258
|
-
packages: collectionInfo.packages,
|
|
259
|
-
};
|
|
260
|
-
lockfile.generated = new Date().toISOString();
|
|
261
|
-
}
|
|
262
|
-
/**
|
|
263
|
-
* Get collection from lock file
|
|
264
|
-
*/
|
|
265
|
-
function getCollectionFromLockfile(lockfile, collectionKey) {
|
|
266
|
-
if (!lockfile || !lockfile.collections) {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
return lockfile.collections[collectionKey] || null;
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Remove collection from lock file
|
|
273
|
-
*/
|
|
274
|
-
function removeCollectionFromLockfile(lockfile, collectionKey) {
|
|
275
|
-
if (!lockfile.collections) {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
delete lockfile.collections[collectionKey];
|
|
279
|
-
lockfile.generated = new Date().toISOString();
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* List all collections in lock file
|
|
283
|
-
*/
|
|
284
|
-
function listCollectionsFromLockfile(lockfile) {
|
|
285
|
-
if (!lockfile || !lockfile.collections) {
|
|
286
|
-
return [];
|
|
287
|
-
}
|
|
288
|
-
return Object.entries(lockfile.collections).map(([key, collection]) => ({
|
|
289
|
-
key,
|
|
290
|
-
...collection,
|
|
291
|
-
}));
|
|
292
|
-
}
|