@prompd/cli 0.4.11 → 0.5.0-beta.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 +1 -1
- package/dist/commands/compile.d.ts.map +1 -1
- package/dist/commands/compile.js +8 -1
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/package.d.ts.map +1 -1
- package/dist/commands/package.js +182 -9
- package/dist/commands/package.js.map +1 -1
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +58 -4
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +52 -18
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/lib/commandExecutor.d.ts +2 -2
- package/dist/lib/commandExecutor.d.ts.map +1 -1
- package/dist/lib/commandExecutor.js +2 -2
- package/dist/lib/commandExecutor.js.map +1 -1
- package/dist/lib/compiler/file-system.d.ts.map +1 -1
- package/dist/lib/compiler/file-system.js +10 -0
- package/dist/lib/compiler/file-system.js.map +1 -1
- package/dist/lib/compiler/index.d.ts +1 -0
- package/dist/lib/compiler/index.d.ts.map +1 -1
- package/dist/lib/compiler/index.js +9 -1
- package/dist/lib/compiler/index.js.map +1 -1
- package/dist/lib/compiler/package-resolver.d.ts +24 -3
- package/dist/lib/compiler/package-resolver.d.ts.map +1 -1
- package/dist/lib/compiler/package-resolver.js +112 -27
- package/dist/lib/compiler/package-resolver.js.map +1 -1
- package/dist/lib/compiler/stages/semantic.d.ts +7 -0
- package/dist/lib/compiler/stages/semantic.d.ts.map +1 -1
- package/dist/lib/compiler/stages/semantic.js +78 -2
- package/dist/lib/compiler/stages/semantic.js.map +1 -1
- package/dist/lib/compiler/stages/template.d.ts.map +1 -1
- package/dist/lib/compiler/stages/template.js +24 -12
- package/dist/lib/compiler/stages/template.js.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +14 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/nodeTypeRegistry.d.ts +1 -1
- package/dist/lib/nodeTypeRegistry.d.ts.map +1 -1
- package/dist/lib/providers/types.d.ts +4 -0
- package/dist/lib/providers/types.d.ts.map +1 -1
- package/dist/lib/providers/types.js.map +1 -1
- package/dist/lib/registry.d.ts +42 -3
- package/dist/lib/registry.d.ts.map +1 -1
- package/dist/lib/registry.js +448 -84
- package/dist/lib/registry.js.map +1 -1
- package/dist/lib/validation.d.ts.map +1 -1
- package/dist/lib/validation.js +10 -2
- package/dist/lib/validation.js.map +1 -1
- package/dist/lib/workflowExecutor.d.ts +2 -0
- package/dist/lib/workflowExecutor.d.ts.map +1 -1
- package/dist/lib/workflowExecutor.js +75 -67
- package/dist/lib/workflowExecutor.js.map +1 -1
- package/dist/types/index.d.ts +48 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +54 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +127 -124
package/dist/lib/registry.js
CHANGED
|
@@ -45,7 +45,10 @@ const tar = __importStar(require("tar"));
|
|
|
45
45
|
const events_1 = require("events");
|
|
46
46
|
const security_1 = require("./security");
|
|
47
47
|
const config_1 = require("./config");
|
|
48
|
+
const types_1 = require("../types");
|
|
48
49
|
const file_system_1 = require("./compiler/file-system");
|
|
50
|
+
const package_resolver_1 = require("./compiler/package-resolver");
|
|
51
|
+
const validation_1 = require("./validation");
|
|
49
52
|
/**
|
|
50
53
|
* Package Registry Client
|
|
51
54
|
*/
|
|
@@ -75,10 +78,25 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
75
78
|
this.ensureCacheDir();
|
|
76
79
|
}
|
|
77
80
|
get registryUrl() {
|
|
78
|
-
return this.registryConfig.url;
|
|
81
|
+
return this.registryConfig.url.replace(/\/+$/, '');
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Encode a package name for safe URL usage.
|
|
85
|
+
* Preserves the @ prefix and / separator in scoped packages (e.g. @scope/name),
|
|
86
|
+
* but encodes all other special characters in each segment.
|
|
87
|
+
*/
|
|
88
|
+
encodePackageName(packageName) {
|
|
89
|
+
// Handle scoped packages: @scope/name
|
|
90
|
+
if (packageName.startsWith('@') && packageName.includes('/')) {
|
|
91
|
+
const slashIndex = packageName.indexOf('/');
|
|
92
|
+
const scope = packageName.substring(1, slashIndex);
|
|
93
|
+
const name = packageName.substring(slashIndex + 1);
|
|
94
|
+
return `@${encodeURIComponent(scope)}/${encodeURIComponent(name)}`;
|
|
95
|
+
}
|
|
96
|
+
return encodeURIComponent(packageName);
|
|
79
97
|
}
|
|
80
98
|
get authToken() {
|
|
81
|
-
return this.registryConfig.token;
|
|
99
|
+
return this.registryConfig.api_key || this.registryConfig.token;
|
|
82
100
|
}
|
|
83
101
|
get cacheDir() {
|
|
84
102
|
return path.join(require('os').homedir(), '.prompd', 'cache');
|
|
@@ -101,7 +119,17 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
101
119
|
const path = require('path');
|
|
102
120
|
const configPath = path.join(os.homedir(), '.prompd', 'config.yaml');
|
|
103
121
|
// Use env var for registry URL if set (useful for local development)
|
|
104
|
-
const
|
|
122
|
+
const defaultUrl = 'https://registry.prompdhub.ai';
|
|
123
|
+
let registryUrl = defaultUrl;
|
|
124
|
+
const envRegistryUrl = process.env.PROMPD_REGISTRY_URL;
|
|
125
|
+
if (envRegistryUrl) {
|
|
126
|
+
if ((0, validation_1.validateRegistryUrl)(envRegistryUrl)) {
|
|
127
|
+
registryUrl = envRegistryUrl;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.warn(`Warning: PROMPD_REGISTRY_URL value is invalid, falling back to default: ${defaultUrl}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
105
133
|
const defaultConfig = {
|
|
106
134
|
apiKeys: {},
|
|
107
135
|
customProviders: {},
|
|
@@ -121,7 +149,11 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
121
149
|
try {
|
|
122
150
|
if (fs.existsSync(configPath)) {
|
|
123
151
|
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
124
|
-
const fileConfig = yaml.parse(configContent);
|
|
152
|
+
const fileConfig = yaml.parse(configContent, { strict: true, maxAliasCount: 64 });
|
|
153
|
+
// Validate parsed config is a plain object
|
|
154
|
+
if (!fileConfig || typeof fileConfig !== 'object' || Array.isArray(fileConfig)) {
|
|
155
|
+
return defaultConfig;
|
|
156
|
+
}
|
|
125
157
|
// Merge with default config
|
|
126
158
|
const mergedConfig = { ...defaultConfig, ...fileConfig };
|
|
127
159
|
// Ensure registry structure exists
|
|
@@ -234,8 +266,6 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
234
266
|
*/
|
|
235
267
|
async install(packageName, options = {}) {
|
|
236
268
|
this.emit('installStart', { packageName, options });
|
|
237
|
-
console.log('[RegistryClient.install] Starting install:', packageName);
|
|
238
|
-
console.log('[RegistryClient.install] Options:', JSON.stringify(options));
|
|
239
269
|
try {
|
|
240
270
|
// Parse package reference if it includes @version
|
|
241
271
|
// Format: @namespace/package@version
|
|
@@ -248,16 +278,18 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
248
278
|
name = packageName.substring(0, lastAtIndex);
|
|
249
279
|
versionSpec = packageName.substring(lastAtIndex + 1);
|
|
250
280
|
}
|
|
251
|
-
console.log('[RegistryClient.install] Parsed name:', name, 'version:', versionSpec);
|
|
252
281
|
// Resolve version
|
|
253
282
|
const resolvedVersion = await this.resolveVersion(name, versionSpec);
|
|
254
|
-
console.log('[RegistryClient.install] Resolved version:', resolvedVersion);
|
|
255
283
|
// Check cache first
|
|
256
284
|
const cacheKey = `${name}@${resolvedVersion}`;
|
|
257
285
|
if (!options.skipCache && !options.force) {
|
|
258
286
|
const cachedPath = await this.getCachedPackage(cacheKey);
|
|
259
287
|
if (cachedPath) {
|
|
260
288
|
await this.installFromCache(cachedPath, name, resolvedVersion, options);
|
|
289
|
+
if (!options.global) {
|
|
290
|
+
await this.addWorkspaceDependency(name, resolvedVersion, options.workspaceRoot);
|
|
291
|
+
}
|
|
292
|
+
this.emit('installComplete', { name, version: resolvedVersion });
|
|
261
293
|
return;
|
|
262
294
|
}
|
|
263
295
|
}
|
|
@@ -280,16 +312,17 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
280
312
|
}
|
|
281
313
|
}
|
|
282
314
|
// Extract and install package
|
|
283
|
-
console.log('[RegistryClient.install] Extracting package to workspace...');
|
|
284
315
|
await this.extractAndInstallPackage(packageData, name, resolvedVersion, options);
|
|
285
|
-
console.log('[RegistryClient.install] Extraction complete');
|
|
286
316
|
// Cache package
|
|
287
317
|
await this.cachePackage(cacheKey, packageData);
|
|
318
|
+
// Update workspace prompd.json dependencies (skip for global installs)
|
|
319
|
+
if (!options.global) {
|
|
320
|
+
await this.addWorkspaceDependency(name, resolvedVersion, options.workspaceRoot);
|
|
321
|
+
}
|
|
288
322
|
this.emit('installComplete', {
|
|
289
323
|
name: name,
|
|
290
324
|
version: resolvedVersion
|
|
291
325
|
});
|
|
292
|
-
console.log('[RegistryClient.install] Install complete for', name, '@', resolvedVersion);
|
|
293
326
|
}
|
|
294
327
|
catch (error) {
|
|
295
328
|
this.emit('installError', { packageName, error });
|
|
@@ -303,11 +336,13 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
303
336
|
try {
|
|
304
337
|
const searchParams = new URLSearchParams();
|
|
305
338
|
if (query.query)
|
|
306
|
-
searchParams.set('
|
|
339
|
+
searchParams.set('search', query.query);
|
|
307
340
|
if (query.category)
|
|
308
341
|
searchParams.set('category', query.category);
|
|
309
|
-
if (query.type)
|
|
310
|
-
|
|
342
|
+
if (query.type) {
|
|
343
|
+
const typeValue = Array.isArray(query.type) ? query.type.join(',') : query.type;
|
|
344
|
+
searchParams.set('type', typeValue);
|
|
345
|
+
}
|
|
311
346
|
if (query.tags)
|
|
312
347
|
searchParams.set('tags', query.tags.join(','));
|
|
313
348
|
if (query.author)
|
|
@@ -318,8 +353,7 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
318
353
|
searchParams.set('offset', query.offset.toString());
|
|
319
354
|
if (query.sort)
|
|
320
355
|
searchParams.set('sort', query.sort);
|
|
321
|
-
|
|
322
|
-
const response = await fetch(`${this.registryUrl}/packages?search=${encodeURIComponent(query.query || '')}`, {
|
|
356
|
+
const response = await fetch(`${this.registryUrl}/packages?${searchParams.toString()}`, {
|
|
323
357
|
headers: this.getAuthHeaders()
|
|
324
358
|
});
|
|
325
359
|
if (!response.ok) {
|
|
@@ -339,9 +373,10 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
339
373
|
*/
|
|
340
374
|
async getPackageInfo(packageName, version) {
|
|
341
375
|
try {
|
|
376
|
+
const encodedName = this.encodePackageName(packageName);
|
|
342
377
|
const url = version
|
|
343
|
-
? `${this.registryUrl}/packages/${
|
|
344
|
-
: `${this.registryUrl}/packages/${
|
|
378
|
+
? `${this.registryUrl}/packages/${encodedName}/${encodeURIComponent(version)}`
|
|
379
|
+
: `${this.registryUrl}/packages/${encodedName}`;
|
|
345
380
|
const response = await fetch(url, {
|
|
346
381
|
headers: this.getAuthHeaders()
|
|
347
382
|
});
|
|
@@ -363,7 +398,7 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
363
398
|
*/
|
|
364
399
|
async getPackageVersions(packageName) {
|
|
365
400
|
try {
|
|
366
|
-
const response = await fetch(`${this.registryUrl}/packages/${packageName}/versions`, {
|
|
401
|
+
const response = await fetch(`${this.registryUrl}/packages/${this.encodePackageName(packageName)}/versions`, {
|
|
367
402
|
headers: this.getAuthHeaders()
|
|
368
403
|
});
|
|
369
404
|
if (!response.ok) {
|
|
@@ -423,7 +458,7 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
423
458
|
metadata.keywords = metadata.keywords || [];
|
|
424
459
|
metadata.dependencies = metadata.dependencies || {};
|
|
425
460
|
metadata.files = metadata.files || ['**/*'];
|
|
426
|
-
metadata.type = metadata.type || '
|
|
461
|
+
metadata.type = metadata.type || 'package';
|
|
427
462
|
metadata.category = metadata.category || 'general';
|
|
428
463
|
metadata.tags = metadata.tags || [];
|
|
429
464
|
metadata.prmdVersion = '0.2.3';
|
|
@@ -444,7 +479,7 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
444
479
|
}
|
|
445
480
|
// Check for required files
|
|
446
481
|
const requiredFiles = [];
|
|
447
|
-
if (metadata.type === '
|
|
482
|
+
if (metadata.type === 'package' && metadata.main) {
|
|
448
483
|
requiredFiles.push(metadata.main);
|
|
449
484
|
}
|
|
450
485
|
for (const file of requiredFiles) {
|
|
@@ -484,8 +519,8 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
484
519
|
formData.append('metadata', JSON.stringify(metadata));
|
|
485
520
|
formData.append('access', options.access);
|
|
486
521
|
formData.append('tag', options.tag);
|
|
487
|
-
const response = await fetch(`${this.registryUrl}/
|
|
488
|
-
method: '
|
|
522
|
+
const response = await fetch(`${this.registryUrl}/packages/${this.encodePackageName(metadata.name)}`, {
|
|
523
|
+
method: 'PUT',
|
|
489
524
|
headers: this.getAuthHeaders(),
|
|
490
525
|
body: formData
|
|
491
526
|
});
|
|
@@ -523,7 +558,7 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
523
558
|
}
|
|
524
559
|
async downloadPackage(packageName, version) {
|
|
525
560
|
// Registry endpoint format: /packages/@scope/name/download/version
|
|
526
|
-
const response = await fetch(`${this.registryUrl}/packages/${packageName}/download/${version}`, {
|
|
561
|
+
const response = await fetch(`${this.registryUrl}/packages/${this.encodePackageName(packageName)}/download/${encodeURIComponent(version)}`, {
|
|
527
562
|
headers: this.getAuthHeaders()
|
|
528
563
|
});
|
|
529
564
|
if (!response.ok) {
|
|
@@ -533,7 +568,13 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
533
568
|
const tarballBuffer = Buffer.from(arrayBuffer);
|
|
534
569
|
// Extract metadata from the .pdpkg (ZIP) file instead of calling getPackageInfo
|
|
535
570
|
// Try prompd.json first, fall back to manifest.json for older packages
|
|
536
|
-
|
|
571
|
+
let AdmZip;
|
|
572
|
+
try {
|
|
573
|
+
AdmZip = (await Promise.resolve().then(() => __importStar(require('adm-zip')))).default;
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
throw new Error('adm-zip package is required for package installation. Run: npm install adm-zip');
|
|
577
|
+
}
|
|
537
578
|
const zip = new AdmZip(tarballBuffer);
|
|
538
579
|
// Check for prompd.json first (newer format), then manifest.json (legacy)
|
|
539
580
|
let manifestEntry = zip.getEntry('prompd.json');
|
|
@@ -545,6 +586,11 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
545
586
|
}
|
|
546
587
|
const manifestContent = manifestEntry.getData().toString('utf8');
|
|
547
588
|
const metadata = JSON.parse(manifestContent);
|
|
589
|
+
// Prevent prototype pollution from untrusted package manifests
|
|
590
|
+
const metadataObj = metadata;
|
|
591
|
+
delete metadataObj['__proto__'];
|
|
592
|
+
delete metadataObj['constructor'];
|
|
593
|
+
delete metadataObj['prototype'];
|
|
548
594
|
return {
|
|
549
595
|
tarball: tarballBuffer,
|
|
550
596
|
metadata
|
|
@@ -557,36 +603,154 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
557
603
|
}
|
|
558
604
|
}
|
|
559
605
|
async extractAndInstallPackage(packageData, packageName, version, options) {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
606
|
+
const workspaceRoot = options.workspaceRoot || (0, package_resolver_1.findProjectRoot)();
|
|
607
|
+
// Determine and validate package type: manifest > options hint > default 'package'
|
|
608
|
+
const rawType = packageData.metadata?.type || options.type || 'package';
|
|
609
|
+
if (!(0, types_1.isValidPackageType)(rawType)) {
|
|
610
|
+
throw new Error(`Invalid package type '${rawType}' in ${packageName}@${version}. Valid types: package, workflow, skill, node-template`);
|
|
611
|
+
}
|
|
612
|
+
const packageType = rawType;
|
|
613
|
+
const typeDir = (0, types_1.getInstallDirForType)(packageType);
|
|
614
|
+
// Load adm-zip for archive operations
|
|
615
|
+
let AdmZip;
|
|
616
|
+
try {
|
|
617
|
+
AdmZip = (await Promise.resolve().then(() => __importStar(require('adm-zip')))).default;
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
throw new Error('adm-zip package is required for package installation. Run: npm install adm-zip');
|
|
621
|
+
}
|
|
574
622
|
const zip = new AdmZip(packageData.tarball);
|
|
575
|
-
//
|
|
623
|
+
// Validate ZIP entries: file sizes, decompression bomb, path traversal, null bytes, symlinks
|
|
576
624
|
const zipEntries = zip.getEntries();
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
625
|
+
let cumulativeDecompressedSize = 0;
|
|
626
|
+
for (const entry of zipEntries) {
|
|
627
|
+
// Individual file size limit
|
|
628
|
+
if (entry.header.size > RegistryClient.MAX_FILE_SIZE_IN_ZIP) {
|
|
629
|
+
throw new Error(`File too large in package: ${entry.entryName} (${entry.header.size} bytes, max: ${RegistryClient.MAX_FILE_SIZE_IN_ZIP})`);
|
|
630
|
+
}
|
|
631
|
+
// Cumulative decompressed size limit (decompression bomb protection)
|
|
632
|
+
cumulativeDecompressedSize += entry.header.size;
|
|
633
|
+
if (cumulativeDecompressedSize > RegistryClient.MAX_TOTAL_EXTRACTED_SIZE) {
|
|
634
|
+
throw new Error(`Package total decompressed size exceeds limit (${RegistryClient.MAX_TOTAL_EXTRACTED_SIZE} bytes). Possible decompression bomb.`);
|
|
635
|
+
}
|
|
636
|
+
// Compression ratio check per file (decompression bomb detection)
|
|
637
|
+
const compressedSize = entry.header.compressedSize || 1;
|
|
638
|
+
if (compressedSize > 0 && entry.header.size / compressedSize > RegistryClient.MAX_COMPRESSION_RATIO) {
|
|
639
|
+
throw new Error(`Suspicious compression ratio for ${entry.entryName}: ${Math.round(entry.header.size / compressedSize)}:1 (max: ${RegistryClient.MAX_COMPRESSION_RATIO}:1)`);
|
|
640
|
+
}
|
|
641
|
+
// Null byte check in entry names
|
|
642
|
+
if (entry.entryName.includes('\0')) {
|
|
643
|
+
throw new Error(`Security violation: null byte in entry name: ${entry.entryName}`);
|
|
644
|
+
}
|
|
645
|
+
// Symlink check - reject symlink entries
|
|
646
|
+
// In ZIP, external attributes can indicate symlinks (Unix mode with S_IFLNK = 0xA000)
|
|
647
|
+
const externalAttrs = entry.header.attr;
|
|
648
|
+
if (externalAttrs) {
|
|
649
|
+
const unixMode = (externalAttrs >>> 16) & 0xFFFF;
|
|
650
|
+
if ((unixMode & 0xF000) === 0xA000) {
|
|
651
|
+
throw new Error(`Security violation: symlink detected in archive: ${entry.entryName}`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// ZIP slip protection: reject path traversal
|
|
655
|
+
const normalized = path.normalize(entry.entryName);
|
|
656
|
+
if (normalized.includes('..') || path.isAbsolute(entry.entryName)) {
|
|
657
|
+
throw new Error(`Security violation: path traversal detected in ${entry.entryName}`);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
const os = require('os');
|
|
661
|
+
// Node-templates: install as .pdpkg archive (not extracted) so template
|
|
662
|
+
// handlers can scan uniformly for .pdpkg files in the templates directory.
|
|
663
|
+
if (packageType === 'node-template') {
|
|
664
|
+
const templatesDir = options.global
|
|
665
|
+
? path.join(os.homedir(), '.prompd', typeDir)
|
|
666
|
+
: path.join(workspaceRoot, '.prompd', typeDir);
|
|
667
|
+
await fs.ensureDir(templatesDir);
|
|
668
|
+
// Slugify package name for filename: @scope/name -> scope-name
|
|
669
|
+
const slugName = packageName
|
|
670
|
+
.toLowerCase()
|
|
671
|
+
.replace(/[@/]+/g, '-')
|
|
672
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
673
|
+
.replace(/^-+|-+$/g, '');
|
|
674
|
+
const pdpkgFileName = `${slugName}-${version}.pdpkg`;
|
|
675
|
+
await fs.writeFile(path.join(templatesDir, pdpkgFileName), packageData.tarball);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const installDir = options.global
|
|
679
|
+
? path.join(os.homedir(), '.prompd', typeDir, packageName, version)
|
|
680
|
+
: path.join(workspaceRoot, '.prompd', typeDir, packageName, version);
|
|
681
|
+
await fs.ensureDir(installDir);
|
|
581
682
|
zip.extractAllTo(installDir, true);
|
|
582
|
-
console.log('[RegistryClient.extractAndInstallPackage] ZIP extracted to:', installDir);
|
|
583
|
-
// Verify extraction
|
|
584
|
-
const extractedFiles = await fs.readdir(installDir);
|
|
585
|
-
console.log('[RegistryClient.extractAndInstallPackage] Extracted files:', extractedFiles);
|
|
586
683
|
// Write package metadata for cache tracking
|
|
587
684
|
const metadataPath = path.join(installDir, '.prmdmeta');
|
|
588
685
|
await fs.writeJson(metadataPath, packageData.metadata, { spaces: 2 });
|
|
589
|
-
|
|
686
|
+
// Deploy to tool-native directories if --tools was specified
|
|
687
|
+
if (options.tools && options.tools.length > 0) {
|
|
688
|
+
if (packageType !== 'skill') {
|
|
689
|
+
throw new Error(`--tools flag is only valid for skills, but package type is '${packageType}'`);
|
|
690
|
+
}
|
|
691
|
+
// Track successful deployments for rollback on failure
|
|
692
|
+
const deployedDirs = [];
|
|
693
|
+
try {
|
|
694
|
+
for (const toolName of options.tools) {
|
|
695
|
+
const deployedDir = await this.deploySkillToTool(installDir, packageName, toolName);
|
|
696
|
+
deployedDirs.push(deployedDir);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
catch (deployError) {
|
|
700
|
+
// Rollback successful deployments
|
|
701
|
+
for (const dir of deployedDirs) {
|
|
702
|
+
await fs.remove(dir).catch(() => { });
|
|
703
|
+
}
|
|
704
|
+
throw deployError;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Deploy skill files to a tool-native directory (e.g., ~/.claude/skills/).
|
|
710
|
+
* Copies skill files and writes a reference marker back to the prompd source location.
|
|
711
|
+
* Returns the deployed directory path for rollback support.
|
|
712
|
+
*/
|
|
713
|
+
async deploySkillToTool(skillDir, packageName, toolName) {
|
|
714
|
+
const deployDir = (0, types_1.resolveToolDeployDir)(toolName);
|
|
715
|
+
if (!deployDir) {
|
|
716
|
+
throw new Error(`Unknown tool '${toolName}'. Supported tools: ${Object.keys(types_1.TOOL_DEPLOY_DIRS).join(', ')}`);
|
|
717
|
+
}
|
|
718
|
+
// Verify skill source directory exists before copying
|
|
719
|
+
if (!await fs.pathExists(skillDir)) {
|
|
720
|
+
throw new Error(`Skill source directory not found: ${skillDir}`);
|
|
721
|
+
}
|
|
722
|
+
// Create a subdirectory for this skill within the tool's skills directory
|
|
723
|
+
const skillDeployDir = path.join(deployDir, packageName);
|
|
724
|
+
await fs.ensureDir(skillDeployDir);
|
|
725
|
+
// Pre-copy check: reject if source tree contains symlinks (prevent symlink-based attacks)
|
|
726
|
+
await this.rejectSymlinks(skillDir);
|
|
727
|
+
// Copy all files from the install dir to the tool deploy dir (dereference: false to not follow symlinks)
|
|
728
|
+
await fs.copy(skillDir, skillDeployDir, { overwrite: true, dereference: false });
|
|
729
|
+
// Write a reference marker so we know this was deployed by prompd
|
|
730
|
+
const markerPath = path.join(skillDeployDir, '.prompd-source');
|
|
731
|
+
await fs.writeJson(markerPath, {
|
|
732
|
+
source: skillDir,
|
|
733
|
+
deployedAt: new Date().toISOString(),
|
|
734
|
+
tool: toolName,
|
|
735
|
+
}, { spaces: 2 });
|
|
736
|
+
return skillDeployDir;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Recursively walk a directory and reject if any symlinks are found.
|
|
740
|
+
* Prevents symlink-based path traversal attacks during skill deployment.
|
|
741
|
+
*/
|
|
742
|
+
async rejectSymlinks(dir) {
|
|
743
|
+
const entries = await fs.readdir(dir);
|
|
744
|
+
for (const entry of entries) {
|
|
745
|
+
const fullPath = path.join(dir, entry);
|
|
746
|
+
const lstat = await fs.lstat(fullPath);
|
|
747
|
+
if (lstat.isSymbolicLink()) {
|
|
748
|
+
throw new Error(`Security violation: symlink detected in package: ${fullPath}`);
|
|
749
|
+
}
|
|
750
|
+
if (lstat.isDirectory()) {
|
|
751
|
+
await this.rejectSymlinks(fullPath);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
590
754
|
}
|
|
591
755
|
getAuthHeaders() {
|
|
592
756
|
const headers = {
|
|
@@ -622,18 +786,33 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
622
786
|
return { size: totalSize, files: totalFiles };
|
|
623
787
|
}
|
|
624
788
|
matchesFilePatterns(filePath, patterns) {
|
|
625
|
-
// Simple glob matching - in production would use proper glob library
|
|
626
789
|
for (const pattern of patterns) {
|
|
627
790
|
if (pattern === '**/*' || pattern === '*') {
|
|
628
791
|
return true;
|
|
629
792
|
}
|
|
630
793
|
if (pattern.includes('*')) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
794
|
+
// Safely convert glob pattern to regex:
|
|
795
|
+
// 1. Escape all regex metacharacters EXCEPT *
|
|
796
|
+
// 2. Replace ** with a full-path wildcard, and * with single-segment wildcard
|
|
797
|
+
const escaped = pattern.replace(/([.+?^${}()|[\]\\])/g, '\\$1');
|
|
798
|
+
const regexStr = escaped
|
|
799
|
+
.replace(/\*\*/g, '\u0000') // Temporary placeholder for **
|
|
800
|
+
.replace(/\*/g, '[^/]*') // * matches within a single path segment
|
|
801
|
+
.replace(/\u0000/g, '.*'); // ** matches across path segments
|
|
802
|
+
try {
|
|
803
|
+
const regex = new RegExp(`^${regexStr}$`);
|
|
804
|
+
if (regex.test(filePath)) {
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
catch {
|
|
809
|
+
// If regex construction fails, fall back to exact match
|
|
810
|
+
if (filePath === pattern) {
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
634
813
|
}
|
|
635
814
|
}
|
|
636
|
-
else if (filePath === pattern) {
|
|
815
|
+
else if (filePath === pattern || filePath.endsWith('/' + pattern)) {
|
|
637
816
|
return true;
|
|
638
817
|
}
|
|
639
818
|
}
|
|
@@ -644,38 +823,191 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
644
823
|
return await fs.pathExists(cachePath) ? cachePath : null;
|
|
645
824
|
}
|
|
646
825
|
async installFromCache(cachePath, packageName, version, options) {
|
|
647
|
-
console.log('[RegistryClient.installFromCache] Installing from cache:', cachePath);
|
|
648
826
|
this.emit('installingFromCache', { name: packageName, version });
|
|
649
|
-
// Read the cached tarball
|
|
650
827
|
const tarballBuffer = await fs.readFile(cachePath);
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
828
|
+
// Try to read full metadata from the cached .meta sidecar file
|
|
829
|
+
const metadataPath = cachePath + '.meta';
|
|
830
|
+
let metadata = { name: packageName, version };
|
|
831
|
+
if (await fs.pathExists(metadataPath)) {
|
|
832
|
+
try {
|
|
833
|
+
metadata = await fs.readJson(metadataPath);
|
|
834
|
+
}
|
|
835
|
+
catch {
|
|
836
|
+
// Fall back to minimal metadata if .meta file is corrupt
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// If metadata lacks type (old cache entry), extract it from the ZIP's prompd.json/manifest.json
|
|
840
|
+
if (!metadata.type) {
|
|
841
|
+
try {
|
|
842
|
+
let AdmZip;
|
|
843
|
+
AdmZip = (await Promise.resolve().then(() => __importStar(require('adm-zip')))).default;
|
|
844
|
+
const zip = new AdmZip(tarballBuffer);
|
|
845
|
+
const manifestEntry = zip.getEntry('prompd.json') || zip.getEntry('manifest.json');
|
|
846
|
+
if (manifestEntry) {
|
|
847
|
+
const manifest = JSON.parse(manifestEntry.getData().toString('utf8'));
|
|
848
|
+
if (manifest.type) {
|
|
849
|
+
metadata.type = manifest.type;
|
|
850
|
+
}
|
|
851
|
+
// Backfill the .meta sidecar so future installs don't need to re-extract
|
|
852
|
+
await fs.writeJson(metadataPath, { ...metadata, ...manifest }, { spaces: 2 }).catch(() => { });
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
catch {
|
|
856
|
+
// Non-fatal: type will fall back to options.type or 'package'
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
const packageData = { tarball: tarballBuffer, metadata };
|
|
654
860
|
await this.extractAndInstallPackage(packageData, packageName, version, options);
|
|
655
|
-
console.log('[RegistryClient.installFromCache] Extraction complete');
|
|
656
861
|
}
|
|
657
862
|
async cachePackage(cacheKey, packageData) {
|
|
658
863
|
const cachePath = path.join(this.cacheDir, 'packages', cacheKey);
|
|
659
864
|
await fs.ensureDir(path.dirname(cachePath));
|
|
660
865
|
await fs.writeFile(cachePath, packageData.tarball);
|
|
866
|
+
// Save full metadata alongside the tarball so cache installs preserve package type
|
|
867
|
+
await fs.writeJson(cachePath + '.meta', packageData.metadata, { spaces: 2 });
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Add a dependency to the workspace prompd.json file.
|
|
871
|
+
*/
|
|
872
|
+
async addWorkspaceDependency(name, version, workspaceRoot) {
|
|
873
|
+
const root = workspaceRoot || (0, package_resolver_1.findProjectRoot)();
|
|
874
|
+
const prompdJsonPath = path.join(root, 'prompd.json');
|
|
875
|
+
try {
|
|
876
|
+
let prompdJson = {};
|
|
877
|
+
if (await fs.pathExists(prompdJsonPath)) {
|
|
878
|
+
const content = await fs.readFile(prompdJsonPath, 'utf8');
|
|
879
|
+
if (content && content.trim() !== '') {
|
|
880
|
+
prompdJson = JSON.parse(content);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (!prompdJson.dependencies || typeof prompdJson.dependencies !== 'object') {
|
|
884
|
+
prompdJson.dependencies = {};
|
|
885
|
+
}
|
|
886
|
+
prompdJson.dependencies[name] = version;
|
|
887
|
+
await fs.writeFile(prompdJsonPath, JSON.stringify(prompdJson, null, 2) + '\n');
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
// Non-fatal: dependency tracking failure shouldn't block install
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Remove a dependency from the workspace prompd.json file.
|
|
895
|
+
*/
|
|
896
|
+
async removeWorkspaceDependency(name, workspaceRoot) {
|
|
897
|
+
const root = workspaceRoot || (0, package_resolver_1.findProjectRoot)();
|
|
898
|
+
const prompdJsonPath = path.join(root, 'prompd.json');
|
|
899
|
+
try {
|
|
900
|
+
if (!await fs.pathExists(prompdJsonPath))
|
|
901
|
+
return;
|
|
902
|
+
const content = await fs.readFile(prompdJsonPath, 'utf8');
|
|
903
|
+
if (!content || content.trim() === '')
|
|
904
|
+
return;
|
|
905
|
+
const prompdJson = JSON.parse(content);
|
|
906
|
+
if (!prompdJson.dependencies || typeof prompdJson.dependencies !== 'object')
|
|
907
|
+
return;
|
|
908
|
+
delete prompdJson.dependencies[name];
|
|
909
|
+
await fs.writeFile(prompdJsonPath, JSON.stringify(prompdJson, null, 2) + '\n');
|
|
910
|
+
}
|
|
911
|
+
catch {
|
|
912
|
+
// Non-fatal
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Uninstall a package by name, removing installed files and the prompd.json dependency entry.
|
|
917
|
+
* Scans type directories to find the installed location.
|
|
918
|
+
*/
|
|
919
|
+
async uninstall(packageName, options = {}) {
|
|
920
|
+
const workspaceRoot = options.workspaceRoot || (0, package_resolver_1.findProjectRoot)();
|
|
921
|
+
const os = require('os');
|
|
922
|
+
const installBase = options.global
|
|
923
|
+
? path.join(os.homedir(), '.prompd')
|
|
924
|
+
: path.join(workspaceRoot, '.prompd');
|
|
925
|
+
// Parse embedded version from ref (e.g. @scope/name@1.0.0)
|
|
926
|
+
let name = packageName;
|
|
927
|
+
const lastAtIndex = packageName.lastIndexOf('@');
|
|
928
|
+
if (lastAtIndex > 0) {
|
|
929
|
+
name = packageName.substring(0, lastAtIndex);
|
|
930
|
+
}
|
|
931
|
+
let removed = false;
|
|
932
|
+
// Scan all type directories for this package
|
|
933
|
+
for (const [type, dir] of Object.entries(types_1.PACKAGE_TYPE_DIRS)) {
|
|
934
|
+
if (type === 'node-template') {
|
|
935
|
+
// Node-templates are stored as .pdpkg files at the type root
|
|
936
|
+
const templatesDir = path.join(installBase, dir);
|
|
937
|
+
if (!await fs.pathExists(templatesDir))
|
|
938
|
+
continue;
|
|
939
|
+
const entries = await fs.readdir(templatesDir);
|
|
940
|
+
for (const entry of entries) {
|
|
941
|
+
if (!entry.endsWith('.pdpkg'))
|
|
942
|
+
continue;
|
|
943
|
+
// Read manifest from archive to match by name
|
|
944
|
+
const pkgPath = path.join(templatesDir, entry);
|
|
945
|
+
try {
|
|
946
|
+
let AdmZip;
|
|
947
|
+
AdmZip = (await Promise.resolve().then(() => __importStar(require('adm-zip')))).default;
|
|
948
|
+
const zip = new AdmZip(pkgPath);
|
|
949
|
+
const manifestEntry = zip.getEntry('prompd.json') || zip.getEntry('manifest.json');
|
|
950
|
+
if (!manifestEntry)
|
|
951
|
+
continue;
|
|
952
|
+
const manifest = JSON.parse(manifestEntry.getData().toString('utf8'));
|
|
953
|
+
if (manifest.name === name) {
|
|
954
|
+
await fs.remove(pkgPath);
|
|
955
|
+
removed = true;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
catch {
|
|
959
|
+
// Skip unreadable archives
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
// Standard packages: stored in @scope/name/ or name/ directories
|
|
965
|
+
const pkgDir = path.join(installBase, dir, name);
|
|
966
|
+
if (await fs.pathExists(pkgDir)) {
|
|
967
|
+
await fs.remove(pkgDir);
|
|
968
|
+
removed = true;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (!removed) {
|
|
973
|
+
throw new Error(`Package '${name}' is not installed`);
|
|
974
|
+
}
|
|
975
|
+
// Remove from workspace prompd.json dependencies
|
|
976
|
+
if (!options.global) {
|
|
977
|
+
await this.removeWorkspaceDependency(name, workspaceRoot);
|
|
978
|
+
}
|
|
979
|
+
this.emit('uninstallComplete', { name });
|
|
661
980
|
}
|
|
662
981
|
/**
|
|
663
982
|
* Load manifest.json from file system abstraction.
|
|
664
983
|
* Supports both disk-based and in-memory file systems.
|
|
665
984
|
*/
|
|
666
985
|
async loadManifestFromFS(packagePath, fileSystem) {
|
|
986
|
+
// Try prompd.json first (current format), fall back to manifest.json (legacy)
|
|
987
|
+
const prompdJsonPath = fileSystem.join(packagePath, 'prompd.json');
|
|
667
988
|
const manifestPath = fileSystem.join(packagePath, 'manifest.json');
|
|
668
|
-
|
|
669
|
-
if (
|
|
670
|
-
|
|
989
|
+
let resolvedPath;
|
|
990
|
+
if (await Promise.resolve(fileSystem.exists(prompdJsonPath))) {
|
|
991
|
+
resolvedPath = prompdJsonPath;
|
|
992
|
+
}
|
|
993
|
+
else if (await Promise.resolve(fileSystem.exists(manifestPath))) {
|
|
994
|
+
resolvedPath = manifestPath;
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
throw new Error('No prompd.json (or legacy manifest.json) file found');
|
|
671
998
|
}
|
|
672
|
-
const content = await Promise.resolve(fileSystem.readFile(
|
|
999
|
+
const content = await Promise.resolve(fileSystem.readFile(resolvedPath));
|
|
673
1000
|
const manifest = JSON.parse(content);
|
|
1001
|
+
// Prevent prototype pollution from untrusted manifests
|
|
1002
|
+
const manifestObj = manifest;
|
|
1003
|
+
delete manifestObj['__proto__'];
|
|
1004
|
+
delete manifestObj['constructor'];
|
|
1005
|
+
delete manifestObj['prototype'];
|
|
674
1006
|
// Validate required fields
|
|
675
1007
|
const required = ['name', 'version', 'description', 'author'];
|
|
676
1008
|
for (const field of required) {
|
|
677
1009
|
if (!manifest[field]) {
|
|
678
|
-
throw new Error(`Missing required field in
|
|
1010
|
+
throw new Error(`Missing required field in ${path.basename(resolvedPath)}: ${field}`);
|
|
679
1011
|
}
|
|
680
1012
|
}
|
|
681
1013
|
// Validate version format
|
|
@@ -687,7 +1019,7 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
687
1019
|
manifest.keywords = manifest.keywords || [];
|
|
688
1020
|
manifest.dependencies = manifest.dependencies || {};
|
|
689
1021
|
manifest.files = manifest.files || ['**/*.prmd'];
|
|
690
|
-
manifest.type = manifest.type || '
|
|
1022
|
+
manifest.type = manifest.type || 'package';
|
|
691
1023
|
manifest.category = manifest.category || 'general';
|
|
692
1024
|
manifest.tags = manifest.tags || [];
|
|
693
1025
|
manifest.prompdVersion = manifest.prompdVersion || '0.3.3';
|
|
@@ -746,41 +1078,73 @@ class RegistryClient extends events_1.EventEmitter {
|
|
|
746
1078
|
}
|
|
747
1079
|
/**
|
|
748
1080
|
* Upload package Buffer to registry.
|
|
1081
|
+
* Uses form-data's submit() which handles Content-Length, transport, and piping.
|
|
749
1082
|
*/
|
|
750
1083
|
async uploadPackageBuffer(tarballBuffer, metadata, options) {
|
|
751
1084
|
const FormData = require('form-data');
|
|
752
1085
|
const formData = new FormData();
|
|
753
1086
|
formData.append('package', tarballBuffer, {
|
|
754
1087
|
filename: `${metadata.name}-${metadata.version}.pdpkg`,
|
|
755
|
-
contentType: 'application/
|
|
1088
|
+
contentType: 'application/zip'
|
|
756
1089
|
});
|
|
757
1090
|
formData.append('metadata', JSON.stringify(metadata));
|
|
758
1091
|
formData.append('access', options.access);
|
|
759
1092
|
formData.append('tag', options.tag);
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1093
|
+
const token = options.authToken || this.authToken;
|
|
1094
|
+
const url = new URL(`${this.registryUrl}/packages/${this.encodePackageName(metadata.name)}`);
|
|
1095
|
+
const response = await new Promise((resolve, reject) => {
|
|
1096
|
+
formData.submit({
|
|
1097
|
+
protocol: url.protocol,
|
|
1098
|
+
hostname: url.hostname,
|
|
1099
|
+
port: url.port || undefined,
|
|
1100
|
+
path: url.pathname,
|
|
1101
|
+
method: 'PUT',
|
|
1102
|
+
headers: {
|
|
1103
|
+
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
|
1104
|
+
'User-Agent': 'prompd-cli/0.5.0'
|
|
1105
|
+
}
|
|
1106
|
+
}, (err, res) => {
|
|
1107
|
+
if (err)
|
|
1108
|
+
return reject(err);
|
|
1109
|
+
const chunks = [];
|
|
1110
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
1111
|
+
res.on('error', reject);
|
|
1112
|
+
res.on('end', () => {
|
|
1113
|
+
resolve({ statusCode: res.statusCode ?? 0, body: Buffer.concat(chunks).toString('utf-8') });
|
|
1114
|
+
});
|
|
1115
|
+
});
|
|
767
1116
|
});
|
|
768
|
-
if (
|
|
769
|
-
|
|
770
|
-
throw new Error(`Publish failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
1117
|
+
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
1118
|
+
throw new Error(`Publish failed: ${response.statusCode} - ${response.body}`);
|
|
771
1119
|
}
|
|
772
1120
|
}
|
|
773
1121
|
}
|
|
774
1122
|
exports.RegistryClient = RegistryClient;
|
|
1123
|
+
RegistryClient.MAX_FILE_SIZE_IN_ZIP = 10 * 1024 * 1024; // 10MB per file
|
|
1124
|
+
RegistryClient.MAX_TOTAL_EXTRACTED_SIZE = 500 * 1024 * 1024; // 500MB total
|
|
1125
|
+
RegistryClient.MAX_COMPRESSION_RATIO = 100; // 100:1 max ratio per file
|
|
775
1126
|
/**
|
|
776
1127
|
* Default registry configuration
|
|
777
1128
|
*/
|
|
778
|
-
const createDefaultRegistryConfig = () =>
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
1129
|
+
const createDefaultRegistryConfig = () => {
|
|
1130
|
+
const defaultUrl = 'https://registry.prompdhub.ai';
|
|
1131
|
+
const envUrl = process.env.PROMPD_REGISTRY_URL;
|
|
1132
|
+
let registryUrl = defaultUrl;
|
|
1133
|
+
if (envUrl) {
|
|
1134
|
+
if ((0, validation_1.validateRegistryUrl)(envUrl)) {
|
|
1135
|
+
registryUrl = envUrl;
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
console.warn(`Warning: PROMPD_REGISTRY_URL value is invalid, falling back to default: ${defaultUrl}`);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return {
|
|
1142
|
+
registryUrl,
|
|
1143
|
+
authToken: process.env.PROMPD_AUTH_TOKEN,
|
|
1144
|
+
cacheDir: path.join(require('os').homedir(), '.prmd', 'cache'),
|
|
1145
|
+
timeout: 30000,
|
|
1146
|
+
maxPackageSize: 50 * 1024 * 1024 // 50MB
|
|
1147
|
+
};
|
|
1148
|
+
};
|
|
785
1149
|
exports.createDefaultRegistryConfig = createDefaultRegistryConfig;
|
|
786
1150
|
//# sourceMappingURL=registry.js.map
|