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,253 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.telemetry = void 0;
7
- const fs_1 = require("fs");
8
- const path_1 = __importDefault(require("path"));
9
- const os_1 = __importDefault(require("os"));
10
- const posthog_node_1 = require("posthog-node");
11
- const user_config_1 = require("./user-config");
12
- class Telemetry {
13
- constructor() {
14
- this.events = [];
15
- this.maxEvents = 100; // Keep only last 100 events locally
16
- this.posthog = null;
17
- this.userConfigChecked = false;
18
- this.userTelemetryEnabled = true; // Default to true until checked
19
- this.configPath = path_1.default.join(os_1.default.homedir(), '.prpm', 'telemetry.json');
20
- this.config = this.loadConfig();
21
- }
22
- async checkUserConfig() {
23
- if (this.userConfigChecked)
24
- return;
25
- try {
26
- const userConfig = await (0, user_config_1.getConfig)();
27
- this.userTelemetryEnabled = userConfig.telemetryEnabled ?? true;
28
- }
29
- catch (error) {
30
- // If we can't load user config, default to enabled
31
- this.userTelemetryEnabled = true;
32
- }
33
- this.userConfigChecked = true;
34
- }
35
- async initializePostHog() {
36
- // Check user config first
37
- await this.checkUserConfig();
38
- // Only initialize if telemetry is enabled in user config
39
- if (!this.userTelemetryEnabled) {
40
- this.posthog = null;
41
- return;
42
- }
43
- try {
44
- this.posthog = new posthog_node_1.PostHog('phc_aO5lXLILeylHfb1ynszVwKbQKSzO91UGdXNhN5Q0Snl', {
45
- host: 'https://app.posthog.com',
46
- flushAt: 1, // Send events immediately
47
- flushInterval: 0, // No batching
48
- });
49
- }
50
- catch (error) {
51
- this.posthog = null;
52
- }
53
- }
54
- loadConfig() {
55
- try {
56
- const data = require(this.configPath);
57
- return {
58
- enabled: data.enabled ?? true, // Default to enabled
59
- userId: data.userId,
60
- sessionId: data.sessionId || this.generateSessionId(),
61
- };
62
- }
63
- catch {
64
- return {
65
- enabled: true,
66
- sessionId: this.generateSessionId(),
67
- };
68
- }
69
- }
70
- /**
71
- * Load userId from user config and update telemetry config
72
- */
73
- async loadUserIdFromConfig() {
74
- try {
75
- const userConfig = await (0, user_config_1.getConfig)();
76
- if (userConfig.userId && userConfig.userId !== this.config.userId) {
77
- this.config.userId = userConfig.userId;
78
- await this.saveConfig();
79
- }
80
- }
81
- catch {
82
- // Silently fail - telemetry shouldn't break the CLI
83
- }
84
- }
85
- generateSessionId() {
86
- return Math.random().toString(36).substring(2, 15) +
87
- Math.random().toString(36).substring(2, 15);
88
- }
89
- async saveConfig() {
90
- try {
91
- await fs_1.promises.mkdir(path_1.default.dirname(this.configPath), { recursive: true });
92
- await fs_1.promises.writeFile(this.configPath, JSON.stringify(this.config, null, 2));
93
- }
94
- catch (error) {
95
- // Silently fail - telemetry shouldn't break the CLI
96
- }
97
- }
98
- async track(event) {
99
- // Check user config first
100
- await this.checkUserConfig();
101
- // Return early if telemetry is disabled in user config
102
- if (!this.userTelemetryEnabled)
103
- return;
104
- if (!this.config.enabled)
105
- return;
106
- // Load userId from user config before tracking
107
- await this.loadUserIdFromConfig();
108
- const fullEvent = {
109
- ...event,
110
- timestamp: new Date().toISOString(),
111
- version: process.env.npm_package_version || '0.1.0',
112
- platform: os_1.default.platform(),
113
- arch: os_1.default.arch(),
114
- nodeVersion: process.version,
115
- };
116
- this.events.push(fullEvent);
117
- // Keep only the last maxEvents
118
- if (this.events.length > this.maxEvents) {
119
- this.events = this.events.slice(-this.maxEvents);
120
- }
121
- // Save events locally
122
- await this.saveEvents();
123
- // Send to analytics service (async, non-blocking)
124
- this.sendToAnalytics(fullEvent).catch(() => {
125
- // Silently fail - don't break the CLI
126
- });
127
- }
128
- async saveEvents() {
129
- try {
130
- const eventsPath = path_1.default.join(os_1.default.homedir(), '.prpm', 'events.json');
131
- await fs_1.promises.mkdir(path_1.default.dirname(eventsPath), { recursive: true });
132
- await fs_1.promises.writeFile(eventsPath, JSON.stringify(this.events, null, 2));
133
- }
134
- catch (error) {
135
- // Silently fail
136
- }
137
- }
138
- async sendToAnalytics(event) {
139
- // Initialize PostHog if needed (this will check user config)
140
- if (!this.posthog && this.userTelemetryEnabled) {
141
- await this.initializePostHog();
142
- }
143
- // Send to PostHog
144
- await this.sendToPostHog(event);
145
- }
146
- async enable() {
147
- this.config.enabled = true;
148
- await this.saveConfig();
149
- }
150
- async disable() {
151
- this.config.enabled = false;
152
- await this.saveConfig();
153
- }
154
- async isEnabled() {
155
- await this.checkUserConfig();
156
- return this.userTelemetryEnabled && this.config.enabled;
157
- }
158
- async getStats() {
159
- try {
160
- const eventsPath = path_1.default.join(os_1.default.homedir(), '.prpm', 'events.json');
161
- const data = await fs_1.promises.readFile(eventsPath, 'utf8');
162
- const savedEvents = JSON.parse(data);
163
- return {
164
- totalEvents: savedEvents.length,
165
- lastEvent: savedEvents[savedEvents.length - 1]?.timestamp,
166
- };
167
- }
168
- catch (error) {
169
- return {
170
- totalEvents: this.events.length,
171
- lastEvent: this.events[this.events.length - 1]?.timestamp,
172
- };
173
- }
174
- }
175
- async shutdown() {
176
- if (this.posthog) {
177
- try {
178
- // Flush any pending events before shutdown
179
- await this.posthog.flush();
180
- await this.posthog.shutdown();
181
- }
182
- catch (error) {
183
- // Silently fail
184
- }
185
- finally {
186
- this.posthog = null;
187
- }
188
- }
189
- }
190
- /**
191
- * Identify user in PostHog with user properties
192
- * Called after successful login to set user attributes
193
- */
194
- async identifyUser(userId, traits) {
195
- if (!this.posthog || !this.config.enabled)
196
- return;
197
- try {
198
- // Update local config with userId
199
- this.config.userId = userId;
200
- await this.saveConfig();
201
- // Send $identify event to PostHog
202
- this.posthog.identify({
203
- distinctId: userId,
204
- properties: traits,
205
- });
206
- // Also capture the $identify event explicitly
207
- this.posthog.capture({
208
- distinctId: userId,
209
- event: '$identify',
210
- properties: traits,
211
- });
212
- // Flush immediately to ensure identification happens
213
- await this.posthog.flush();
214
- }
215
- catch (error) {
216
- // Silently fail - telemetry shouldn't break the CLI
217
- }
218
- }
219
- // Send to PostHog
220
- async sendToPostHog(event) {
221
- if (!this.posthog)
222
- return;
223
- try {
224
- const distinctId = this.config.userId || this.config.sessionId || 'anonymous';
225
- this.posthog.capture({
226
- distinctId,
227
- event: `prpm_${event.command}`,
228
- properties: {
229
- // Core event data
230
- command: event.command,
231
- success: event.success,
232
- duration: event.duration,
233
- error: event.error,
234
- // System information
235
- version: event.version,
236
- platform: event.platform,
237
- arch: event.arch,
238
- nodeVersion: event.nodeVersion,
239
- // Command-specific data
240
- ...event.data,
241
- // Metadata
242
- timestamp: event.timestamp,
243
- sessionId: this.config.sessionId,
244
- },
245
- });
246
- // Event sent to PostHog
247
- }
248
- catch (error) {
249
- // Silently fail - don't break the CLI
250
- }
251
- }
252
- }
253
- exports.telemetry = new Telemetry();
@@ -1,147 +0,0 @@
1
- "use strict";
2
- /**
3
- * User configuration management for ~/.prpmrc and .prpmrc
4
- * Stores global settings like registry URL and authentication token
5
- * Supports both user-level (~/.prpmrc) and repository-level (.prpmrc) config
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.getConfig = getConfig;
9
- exports.saveConfig = saveConfig;
10
- exports.saveRepoConfig = saveRepoConfig;
11
- exports.getRepoConfig = getRepoConfig;
12
- exports.getUserConfig = getUserConfig;
13
- exports.updateConfig = updateConfig;
14
- exports.clearAuth = clearAuth;
15
- exports.getRegistryUrl = getRegistryUrl;
16
- const fs_1 = require("fs");
17
- const path_1 = require("path");
18
- const os_1 = require("os");
19
- const USER_CONFIG_FILE = (0, path_1.join)((0, os_1.homedir)(), '.prpmrc');
20
- const REPO_CONFIG_FILE = '.prpmrc';
21
- const DEFAULT_REGISTRY_URL = 'https://registry.prpm.dev';
22
- /**
23
- * Load configuration from a file
24
- */
25
- async function loadConfigFile(filePath) {
26
- try {
27
- const data = await fs_1.promises.readFile(filePath, 'utf-8');
28
- return JSON.parse(data);
29
- }
30
- catch (error) {
31
- if (error.code === 'ENOENT') {
32
- return null;
33
- }
34
- throw new Error(`Failed to read config from ${filePath}: ${error}`);
35
- }
36
- }
37
- /**
38
- * Get merged configuration from user and repository levels
39
- * Priority: CLI flags > environment > repo config > user config > defaults
40
- */
41
- async function getConfig() {
42
- // Load user-level config (~/.prpmrc)
43
- const userConfig = await loadConfigFile(USER_CONFIG_FILE);
44
- // Load repository-level config (./prpmrc)
45
- const repoConfigPath = (0, path_1.join)(process.cwd(), REPO_CONFIG_FILE);
46
- const repoConfig = await loadConfigFile(repoConfigPath);
47
- // Merge configs (repo overrides user)
48
- const config = {
49
- ...userConfig,
50
- ...repoConfig,
51
- };
52
- // Deep merge nested objects
53
- if (userConfig?.cursor || repoConfig?.cursor) {
54
- config.cursor = {
55
- ...userConfig?.cursor,
56
- ...repoConfig?.cursor,
57
- };
58
- }
59
- if (userConfig?.claude || repoConfig?.claude) {
60
- config.claude = {
61
- ...userConfig?.claude,
62
- ...repoConfig?.claude,
63
- };
64
- }
65
- if (userConfig?.collections || repoConfig?.collections) {
66
- config.collections = {
67
- ...userConfig?.collections,
68
- ...repoConfig?.collections,
69
- };
70
- }
71
- // Allow environment variable to override registry URL
72
- if (process.env.PRPM_REGISTRY_URL) {
73
- config.registryUrl = process.env.PRPM_REGISTRY_URL;
74
- }
75
- else if (!config.registryUrl) {
76
- config.registryUrl = DEFAULT_REGISTRY_URL;
77
- }
78
- // Set defaults
79
- if (config.telemetryEnabled === undefined) {
80
- config.telemetryEnabled = true;
81
- }
82
- return config;
83
- }
84
- /**
85
- * Save user configuration to ~/.prpmrc
86
- */
87
- async function saveConfig(config) {
88
- try {
89
- const data = JSON.stringify(config, null, 2);
90
- await fs_1.promises.writeFile(USER_CONFIG_FILE, data, 'utf-8');
91
- }
92
- catch (error) {
93
- throw new Error(`Failed to save user config: ${error}`);
94
- }
95
- }
96
- /**
97
- * Save repository configuration to ./.prpmrc
98
- */
99
- async function saveRepoConfig(config) {
100
- try {
101
- const repoConfigPath = (0, path_1.join)(process.cwd(), REPO_CONFIG_FILE);
102
- const data = JSON.stringify(config, null, 2);
103
- await fs_1.promises.writeFile(repoConfigPath, data, 'utf-8');
104
- }
105
- catch (error) {
106
- throw new Error(`Failed to save repository config: ${error}`);
107
- }
108
- }
109
- /**
110
- * Get repository-level configuration only
111
- */
112
- async function getRepoConfig() {
113
- const repoConfigPath = (0, path_1.join)(process.cwd(), REPO_CONFIG_FILE);
114
- return await loadConfigFile(repoConfigPath);
115
- }
116
- /**
117
- * Get user-level configuration only
118
- */
119
- async function getUserConfig() {
120
- return await loadConfigFile(USER_CONFIG_FILE);
121
- }
122
- /**
123
- * Update specific config values
124
- */
125
- async function updateConfig(updates) {
126
- const config = await getConfig();
127
- const newConfig = { ...config, ...updates };
128
- await saveConfig(newConfig);
129
- }
130
- /**
131
- * Clear authentication (logout)
132
- */
133
- async function clearAuth() {
134
- const config = await getConfig();
135
- delete config.token;
136
- delete config.username;
137
- delete config.userId;
138
- delete config.email;
139
- await saveConfig(config);
140
- }
141
- /**
142
- * Get registry URL (with fallback to default)
143
- */
144
- async function getRegistryUrl() {
145
- const config = await getConfig();
146
- return config.registryUrl || DEFAULT_REGISTRY_URL;
147
- }
@@ -1,12 +0,0 @@
1
- "use strict";
2
- /**
3
- * Registry API types for CLI
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.isMultiPackageManifest = isMultiPackageManifest;
7
- /**
8
- * Type guard to check if manifest is multi-package
9
- */
10
- function isMultiPackageManifest(manifest) {
11
- return 'packages' in manifest && Array.isArray(manifest.packages);
12
- }
package/dist/types.js DELETED
@@ -1,9 +0,0 @@
1
- "use strict";
2
- /**
3
- * Core types for the Prompt Package Manager
4
- */
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SUBTYPES = exports.FORMATS = void 0;
7
- var types_1 = require("@pr-pm/types");
8
- Object.defineProperty(exports, "FORMATS", { enumerable: true, get: function () { return types_1.FORMATS; } });
9
- Object.defineProperty(exports, "SUBTYPES", { enumerable: true, get: function () { return types_1.SUBTYPES; } });
@@ -1,122 +0,0 @@
1
- "use strict";
2
- /**
3
- * License extraction utilities
4
- * Extracts license information from LICENSE files for proper attribution
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.extractLicenseInfo = extractLicenseInfo;
8
- exports.validateLicenseInfo = validateLicenseInfo;
9
- const promises_1 = require("fs/promises");
10
- const path_1 = require("path");
11
- const fs_1 = require("fs");
12
- /**
13
- * Common license file names to search for
14
- */
15
- const LICENSE_FILE_PATTERNS = [
16
- 'LICENSE',
17
- 'LICENSE.md',
18
- 'LICENSE.txt',
19
- 'LICENCE',
20
- 'LICENCE.md',
21
- 'LICENCE.txt',
22
- 'LICENSE-MIT',
23
- 'LICENSE-APACHE',
24
- 'COPYING',
25
- 'COPYING.txt',
26
- ];
27
- /**
28
- * License type detection patterns
29
- * Ordered by specificity - more specific patterns first
30
- */
31
- const LICENSE_PATTERNS = [
32
- { pattern: /MIT License/i, type: 'MIT' },
33
- { pattern: /Apache License[\s\S]*Version 2\.0/i, type: 'Apache-2.0' },
34
- { pattern: /GNU GENERAL PUBLIC LICENSE[\s\S]*Version 3/i, type: 'GPL-3.0' },
35
- { pattern: /GNU GENERAL PUBLIC LICENSE[\s\S]*Version 2/i, type: 'GPL-2.0' },
36
- { pattern: /GNU LESSER GENERAL PUBLIC LICENSE[\s\S]*Version 3/i, type: 'LGPL-3.0' },
37
- { pattern: /GNU LESSER GENERAL PUBLIC LICENSE[\s\S]*Version 2/i, type: 'LGPL-2.1' },
38
- { pattern: /BSD 3-Clause License/i, type: 'BSD-3-Clause' },
39
- { pattern: /BSD 2-Clause License/i, type: 'BSD-2-Clause' },
40
- { pattern: /Mozilla Public License[\s\S]*Version 2\.0/i, type: 'MPL-2.0' },
41
- { pattern: /ISC License/i, type: 'ISC' },
42
- { pattern: /The Unlicense/i, type: 'Unlicense' },
43
- { pattern: /Creative Commons Zero[\s\S]*1\.0/i, type: 'CC0-1.0' },
44
- ];
45
- /**
46
- * Detect license type from license text
47
- */
48
- function detectLicenseType(text) {
49
- for (const { pattern, type } of LICENSE_PATTERNS) {
50
- if (pattern.test(text)) {
51
- return type;
52
- }
53
- }
54
- return null;
55
- }
56
- /**
57
- * Generate GitHub raw URL for license file
58
- */
59
- function generateLicenseUrl(repositoryUrl, fileName) {
60
- if (!repositoryUrl) {
61
- return null;
62
- }
63
- // Extract owner/repo from GitHub URL
64
- const githubMatch = repositoryUrl.match(/github\.com[/:]([\w-]+)\/([\w-]+)/);
65
- if (!githubMatch) {
66
- return null;
67
- }
68
- const [, owner, repo] = githubMatch;
69
- // Use raw.githubusercontent.com for direct file access
70
- return `https://raw.githubusercontent.com/${owner}/${repo}/main/${fileName}`;
71
- }
72
- /**
73
- * Find and extract license information from the current directory
74
- */
75
- async function extractLicenseInfo(repositoryUrl) {
76
- const cwd = process.cwd();
77
- // Try each license file pattern
78
- for (const fileName of LICENSE_FILE_PATTERNS) {
79
- const filePath = (0, path_1.join)(cwd, fileName);
80
- try {
81
- // Check if file exists
82
- await (0, promises_1.access)(filePath, fs_1.constants.R_OK);
83
- // Read license file
84
- const text = await (0, promises_1.readFile)(filePath, 'utf-8');
85
- // Detect license type
86
- const type = detectLicenseType(text);
87
- // Generate license URL if repository is provided
88
- const url = generateLicenseUrl(repositoryUrl, fileName);
89
- return {
90
- type,
91
- text,
92
- url,
93
- fileName,
94
- };
95
- }
96
- catch {
97
- // File doesn't exist or can't be read, try next pattern
98
- continue;
99
- }
100
- }
101
- // No license file found
102
- return {
103
- type: null,
104
- text: null,
105
- url: null,
106
- fileName: null,
107
- };
108
- }
109
- /**
110
- * Display license information if found
111
- */
112
- function validateLicenseInfo(licenseInfo, packageName) {
113
- if (licenseInfo.text && licenseInfo.type) {
114
- console.log(` License: ${licenseInfo.type} (${licenseInfo.fileName})`);
115
- }
116
- else if (licenseInfo.text && !licenseInfo.type) {
117
- console.log(` License: Found (${licenseInfo.fileName})`);
118
- }
119
- else {
120
- console.log(` License: Not found (package will be published without license)`);
121
- }
122
- }
@@ -1,117 +0,0 @@
1
- "use strict";
2
- /**
3
- * Multi-package manifest utilities
4
- * Handles merging root-level fields with package-level fields
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.mergePackageFields = mergePackageFields;
8
- exports.getPackagesWithInheritance = getPackagesWithInheritance;
9
- exports.validateMultiPackageManifest = validateMultiPackageManifest;
10
- exports.filterPackages = filterPackages;
11
- /**
12
- * Fields that can be inherited from root to packages
13
- */
14
- const INHERITABLE_FIELDS = [
15
- 'author',
16
- 'license',
17
- 'repository',
18
- 'homepage',
19
- 'documentation',
20
- 'organization',
21
- 'keywords',
22
- 'tags',
23
- ];
24
- /**
25
- * Merge root-level fields into a package manifest
26
- * Package-level fields take precedence over root-level fields
27
- */
28
- function mergePackageFields(root, pkg) {
29
- const merged = { ...pkg };
30
- // Inherit each inheritable field if not defined in package
31
- for (const field of INHERITABLE_FIELDS) {
32
- if (merged[field] === undefined && root[field] !== undefined) {
33
- // @ts-ignore - dynamic field access
34
- merged[field] = root[field];
35
- }
36
- }
37
- return merged;
38
- }
39
- /**
40
- * Get all packages from a multi-package manifest with inherited fields
41
- */
42
- function getPackagesWithInheritance(manifest) {
43
- return manifest.packages.map(pkg => mergePackageFields(manifest, pkg));
44
- }
45
- /**
46
- * Validate multi-package manifest
47
- */
48
- function validateMultiPackageManifest(manifest) {
49
- const errors = [];
50
- // Check packages array exists and is not empty
51
- if (!manifest.packages || !Array.isArray(manifest.packages)) {
52
- errors.push('packages field must be an array');
53
- return { valid: false, errors };
54
- }
55
- if (manifest.packages.length === 0) {
56
- errors.push('packages array must contain at least one package');
57
- return { valid: false, errors };
58
- }
59
- // Check for duplicate package names
60
- const names = new Set();
61
- for (let i = 0; i < manifest.packages.length; i++) {
62
- const pkg = manifest.packages[i];
63
- if (names.has(pkg.name)) {
64
- errors.push(`Duplicate package name: ${pkg.name}`);
65
- }
66
- names.add(pkg.name);
67
- }
68
- // Validate each package has required fields
69
- for (let i = 0; i < manifest.packages.length; i++) {
70
- const pkg = manifest.packages[i];
71
- const pkgPrefix = `Package ${i} (${pkg.name || 'unnamed'})`;
72
- if (!pkg.name) {
73
- errors.push(`${pkgPrefix}: missing required field 'name'`);
74
- }
75
- if (!pkg.version) {
76
- errors.push(`${pkgPrefix}: missing required field 'version'`);
77
- }
78
- if (!pkg.description) {
79
- errors.push(`${pkgPrefix}: missing required field 'description'`);
80
- }
81
- if (!pkg.format) {
82
- errors.push(`${pkgPrefix}: missing required field 'format'`);
83
- }
84
- if (!pkg.files || pkg.files.length === 0) {
85
- errors.push(`${pkgPrefix}: must have at least one file in 'files' array`);
86
- }
87
- }
88
- return {
89
- valid: errors.length === 0,
90
- errors,
91
- };
92
- }
93
- /**
94
- * Filter packages by name or pattern
95
- */
96
- function filterPackages(packages, filter) {
97
- // If filter is a number, treat as index
98
- if (typeof filter === 'number') {
99
- if (filter < 0 || filter >= packages.length) {
100
- throw new Error(`Package index ${filter} out of range (0-${packages.length - 1})`);
101
- }
102
- return [packages[filter]];
103
- }
104
- // If exact match, return that package
105
- const exactMatch = packages.find(pkg => pkg.name === filter);
106
- if (exactMatch) {
107
- return [exactMatch];
108
- }
109
- // Try as glob pattern
110
- const pattern = filter.replace(/\*/g, '.*');
111
- const regex = new RegExp(`^${pattern}$`);
112
- const matches = packages.filter(pkg => regex.test(pkg.name));
113
- if (matches.length === 0) {
114
- throw new Error(`No packages match filter: ${filter}`);
115
- }
116
- return matches;
117
- }