@launch77/plugin-runtime 0.3.1 → 0.3.3

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.d.ts CHANGED
@@ -1,3 +1,151 @@
1
+ import fs from 'fs-extra';
2
+
3
+ /**
4
+ * Type of location within a Launch77 workspace
5
+ */
6
+ type Launch77LocationType = 'workspace-root' | 'workspace-app' | 'workspace-library' | 'workspace-plugin' | 'workspace-app-template' | 'unknown';
7
+ /**
8
+ * Context information about the current location within a Launch77 workspace
9
+ */
10
+ interface Launch77Context {
11
+ isValid: boolean;
12
+ locationType: Launch77LocationType;
13
+ workspaceRoot: string;
14
+ appsDir: string;
15
+ workspaceVersion: string;
16
+ workspaceName: string;
17
+ appName?: string;
18
+ packageName: string;
19
+ }
20
+ /**
21
+ * Workspace manifest structure (.launch77/workspace.json)
22
+ */
23
+ interface WorkspaceManifest {
24
+ version: string;
25
+ }
26
+ /**
27
+ * Parsed location information from a path
28
+ */
29
+ interface ParsedLocation {
30
+ locationType: Launch77LocationType;
31
+ appName?: string;
32
+ }
33
+
34
+ /**
35
+ * Service responsible for workspace manifest file operations.
36
+ * Handles reading and writing the .launch77/workspace.json file.
37
+ */
38
+ declare class WorkspaceManifestService {
39
+ private static readonly WORKSPACE_MANIFEST;
40
+ /**
41
+ * Check if a workspace manifest exists at the given root
42
+ */
43
+ exists(workspaceRoot: string): Promise<boolean>;
44
+ /**
45
+ * Read the workspace manifest
46
+ */
47
+ readWorkspaceManifest(workspaceRoot: string): Promise<WorkspaceManifest | null>;
48
+ /**
49
+ * Write the workspace manifest
50
+ */
51
+ writeWorkspaceManifest(workspaceRoot: string, manifest: WorkspaceManifest): Promise<void>;
52
+ /**
53
+ * Get the manifest file path for a workspace
54
+ */
55
+ getManifestPath(workspaceRoot: string): string;
56
+ }
57
+
58
+ /**
59
+ * Validation result type
60
+ */
61
+ interface ValidationResult$2 {
62
+ isValid: boolean;
63
+ errors?: string[];
64
+ }
65
+ /**
66
+ * Service responsible for workspace-related operations.
67
+ * Handles workspace detection and workspace queries.
68
+ */
69
+ declare class WorkspaceService {
70
+ private workspaceManifestService;
71
+ constructor(workspaceManifestService?: WorkspaceManifestService);
72
+ /**
73
+ * Check if a directory is a Launch77 workspace root
74
+ */
75
+ isWorkspaceRoot(dir: string): Promise<boolean>;
76
+ /**
77
+ * Get the workspace root directory from the current directory
78
+ */
79
+ findWorkspaceRoot(startDir: string): Promise<string | null>;
80
+ /**
81
+ * Validate that we're in a Launch77 workspace context
82
+ */
83
+ validateWorkspaceContext(context: Launch77Context): Promise<{
84
+ valid: boolean;
85
+ errorMessage?: string;
86
+ }>;
87
+ /**
88
+ * Validate a workspace context using ValidationResult format
89
+ */
90
+ validateContext(context: Launch77Context): ValidationResult$2;
91
+ /**
92
+ * Validate an app name
93
+ */
94
+ validateAppName(name: string): ValidationResult$2;
95
+ /**
96
+ * Detect the Launch77 workspace context from the current working directory
97
+ *
98
+ * @param cwd - Current working directory path
99
+ * @returns Context information about the workspace location
100
+ */
101
+ detectLaunch77Context(cwd: string): Promise<Launch77Context>;
102
+ }
103
+ /**
104
+ * Standalone function for backward compatibility
105
+ * Creates a new WorkspaceService instance and calls detectLaunch77Context
106
+ */
107
+ declare function detectLaunch77Context(cwd: string): Promise<Launch77Context>;
108
+
109
+ /**
110
+ * Parse the directory structure to determine location context
111
+ *
112
+ * Based on patterns:
113
+ * - apps/[name] → workspace-app
114
+ * - libraries/[name] → workspace-library
115
+ * - plugins/[name] → workspace-plugin
116
+ * - app-templates/[name] → workspace-app-template
117
+ * - (empty or root) → workspace-root
118
+ *
119
+ * @param cwdPath - Current working directory path
120
+ * @param workspaceRoot - Root path of the workspace
121
+ * @returns Parsed location information
122
+ */
123
+ declare function parseLocationFromPath(cwdPath: string, workspaceRoot: string): ParsedLocation;
124
+
125
+ interface InstallPluginRequest {
126
+ pluginName: string;
127
+ }
128
+ interface InstallPluginResult {
129
+ pluginName: string;
130
+ filesInstalled: boolean;
131
+ packageJsonUpdated: boolean;
132
+ dependenciesInstalled: boolean;
133
+ }
134
+ interface HookResult {
135
+ templateVariables?: Record<string, string>;
136
+ hookContext?: Record<string, string>;
137
+ }
138
+ interface DeletePluginRequest {
139
+ pluginName: string;
140
+ }
141
+ interface DeletePluginResult {
142
+ pluginName: string;
143
+ }
144
+ type Target = 'app' | 'library' | 'plugin' | 'app-template';
145
+
146
+ /**
147
+ * Plugin Module Types
148
+ */
1
149
  interface GeneratorContext {
2
150
  appPath: string;
3
151
  appName: string;
@@ -8,25 +156,10 @@ interface PluginMetadata {
8
156
  targets: string[];
9
157
  pluginDependencies?: Record<string, string>;
10
158
  libraryDependencies?: Record<string, string>;
11
- }
12
- interface InstalledPluginMetadata {
13
- package: string;
14
- version: string;
15
- installedAt: string;
16
- source: 'local' | 'npm';
159
+ showcaseUrl?: string;
17
160
  }
18
161
  interface TemplateMetadata {
19
162
  }
20
- interface TemplateReference {
21
- package: string;
22
- version: string;
23
- source: 'local' | 'npm';
24
- createdAt: string;
25
- }
26
- interface Launch77PackageManifest {
27
- installedPlugins?: Record<string, InstalledPluginMetadata>;
28
- template?: TemplateReference;
29
- }
30
163
 
31
164
  /**
32
165
  * Base abstract class for all plugin generators.
@@ -47,6 +180,106 @@ declare abstract class Generator {
47
180
  abstract run(): Promise<void>;
48
181
  }
49
182
 
183
+ /**
184
+ * Service responsible for file system operations.
185
+ * Provides an abstraction layer over fs operations with proper error handling.
186
+ */
187
+ declare class FilesystemService {
188
+ /**
189
+ * Check if a path exists
190
+ */
191
+ pathExists(filePath: string): Promise<boolean>;
192
+ /**
193
+ * Copy a file or directory recursively
194
+ */
195
+ copyRecursive(source: string, destination: string, options?: {
196
+ overwrite?: boolean;
197
+ }): Promise<void>;
198
+ /**
199
+ * Read a JSON file
200
+ */
201
+ readJSON<T = unknown>(filePath: string): Promise<T>;
202
+ /**
203
+ * Write a JSON file
204
+ */
205
+ writeJSON(filePath: string, data: unknown, options?: {
206
+ spaces?: number;
207
+ }): Promise<void>;
208
+ /**
209
+ * Read a text file
210
+ */
211
+ readFile(filePath: string, encoding?: BufferEncoding): Promise<string>;
212
+ /**
213
+ * Write a text file
214
+ */
215
+ writeFile(filePath: string, content: string, encoding?: BufferEncoding): Promise<void>;
216
+ /**
217
+ * Create a directory (including parent directories)
218
+ */
219
+ ensureDir(dirPath: string): Promise<void>;
220
+ /**
221
+ * Remove a file or directory
222
+ */
223
+ remove(filePath: string): Promise<void>;
224
+ /**
225
+ * List files in a directory
226
+ */
227
+ readDir(dirPath: string): Promise<string[]>;
228
+ /**
229
+ * Get file or directory stats
230
+ */
231
+ getStats(filePath: string): Promise<fs.Stats>;
232
+ /**
233
+ * Check if a path is a directory
234
+ */
235
+ isDirectory(filePath: string): Promise<boolean>;
236
+ /**
237
+ * Check if a path is a file
238
+ */
239
+ isFile(filePath: string): Promise<boolean>;
240
+ /**
241
+ * Move a file or directory
242
+ */
243
+ move(source: string, destination: string, options?: {
244
+ overwrite?: boolean;
245
+ }): Promise<void>;
246
+ /**
247
+ * Create a temporary directory
248
+ */
249
+ createTempDir(prefix: string): Promise<string>;
250
+ }
251
+
252
+ /**
253
+ * Service responsible for reading and managing metadata for plugins and templates.
254
+ * Handles JSON file reading and parsing without mixing validation concerns.
255
+ */
256
+ declare class MetadataService {
257
+ /**
258
+ * Read plugin metadata from a plugin directory
259
+ */
260
+ readPluginMetadata(pluginPath: string): Promise<PluginMetadata>;
261
+ /**
262
+ * Read template metadata from a template directory
263
+ */
264
+ readTemplateMetadata(templatePath: string): Promise<TemplateMetadata>;
265
+ /**
266
+ * Check if a plugin metadata file exists
267
+ */
268
+ hasPluginMetadata(pluginPath: string): Promise<boolean>;
269
+ /**
270
+ * Check if a template metadata file exists
271
+ */
272
+ hasTemplateMetadata(templatePath: string): Promise<boolean>;
273
+ /**
274
+ * Write plugin metadata to a directory
275
+ */
276
+ writePluginMetadata(pluginPath: string, metadata: PluginMetadata): Promise<void>;
277
+ /**
278
+ * Write template metadata to a directory
279
+ */
280
+ writeTemplateMetadata(templatePath: string, metadata: TemplateMetadata): Promise<void>;
281
+ }
282
+
50
283
  /**
51
284
  * Standard generator with convention-over-configuration approach.
52
285
  *
@@ -60,6 +293,9 @@ declare abstract class Generator {
60
293
  * For full control, extend Generator instead.
61
294
  */
62
295
  declare abstract class StandardGenerator extends Generator {
296
+ protected filesystemService: FilesystemService;
297
+ protected metadataService: MetadataService;
298
+ constructor(context: GeneratorContext);
63
299
  run(): Promise<void>;
64
300
  protected updateDependencies(): Promise<void>;
65
301
  protected installDependencies(): Promise<void>;
@@ -68,168 +304,523 @@ declare abstract class StandardGenerator extends Generator {
68
304
  protected showNextSteps(): void;
69
305
  }
70
306
 
71
- declare function copyRecursive(src: string, dest: string): Promise<void>;
72
- declare function pathExists(filePath: string): Promise<boolean>;
73
-
74
307
  /**
75
- * Read and parse plugin metadata from plugin.json
76
- *
77
- * Only reads plugin-specific configuration fields:
78
- * - targets: where the plugin can be installed
79
- * - pluginDependencies: other plugins this plugin requires
80
- * - libraryDependencies: npm packages to inject into target
81
- *
82
- * Name and version are read from package.json during resolution.
83
- *
84
- * @param pluginPath - Absolute path to the plugin directory
85
- * @returns Parsed plugin metadata
308
+ * Validation result type for plugin operations
86
309
  */
87
- declare function readPluginMetadata(pluginPath: string): Promise<PluginMetadata>;
310
+ interface ValidationResult$1 {
311
+ isValid: boolean;
312
+ errors?: string[];
313
+ }
88
314
  /**
89
- * Read and parse template metadata from template.json
90
- *
91
- * Currently template.json contains no fields - it exists as a placeholder
92
- * for future template-specific configuration (e.g., deployment metadata).
93
- *
94
- * Template name and version are read from package.json during resolution.
95
- *
96
- * @param templatePath - Absolute path to the template directory
97
- * @returns Template metadata (currently empty)
315
+ * Plugin consistency error type
98
316
  */
99
- declare function readTemplateMetadata(templatePath: string): Promise<TemplateMetadata>;
317
+ interface PluginConsistencyError {
318
+ library: string;
319
+ packageJsonVersion: string | 'MISSING';
320
+ pluginJsonVersion: string;
321
+ }
322
+ declare class PluginService {
323
+ private pluginResolver;
324
+ private metadataService;
325
+ private npmService;
326
+ constructor();
327
+ /**
328
+ * Validate a plugin name
329
+ */
330
+ validatePluginName(name: string): ValidationResult$1;
331
+ /**
332
+ * Validate plugin metadata
333
+ */
334
+ validatePluginMetadata(metadata: PluginMetadata): ValidationResult$1;
335
+ /**
336
+ * Validate a showcase URL
337
+ * Note: This is primarily used by the webapp template
338
+ */
339
+ validateShowcaseUrl(url: string | undefined): ValidationResult$1;
340
+ /**
341
+ * Validate plugin consistency between plugin.json and package.json
342
+ */
343
+ validatePluginConsistency(pluginPath: string): Promise<ValidationResult$1>;
344
+ /**
345
+ * Check if a package name part is valid
346
+ */
347
+ private isValidPackageNamePart;
348
+ /**
349
+ * Validate plugin name, resolve its location, and download if needed
350
+ */
351
+ private validateAndResolvePlugin;
352
+ /**
353
+ * Read plugin metadata and validate it supports the current target
354
+ */
355
+ private validatePluginTargets;
356
+ /**
357
+ * Check if plugin is already installed and return early-exit result if so
358
+ */
359
+ private checkExistingInstallation;
360
+ /**
361
+ * Install a plugin to the current package
362
+ */
363
+ installPlugin(request: InstallPluginRequest, context: Launch77Context, logger?: (message: string) => void): Promise<InstallPluginResult>;
364
+ /**
365
+ * Download and install an npm plugin package
366
+ */
367
+ private downloadNpmPlugin;
368
+ private getPackagePath;
369
+ private runGenerator;
370
+ private isPluginInstalled;
371
+ /**
372
+ * Write plugin installation metadata to the target package's package.json
373
+ */
374
+ private writePluginManifest;
375
+ /**
376
+ * Delete a plugin from the workspace
377
+ */
378
+ deletePlugin(request: DeletePluginRequest, context: Launch77Context): Promise<DeletePluginResult>;
379
+ }
100
380
 
101
381
  /**
102
- * Type of location within a Launch77 workspace
382
+ * Metadata about an installed plugin
103
383
  */
104
- type Launch77LocationType = 'workspace-root' | 'workspace-app' | 'workspace-library' | 'workspace-plugin' | 'workspace-app-template' | 'unknown';
384
+ interface InstalledPluginMetadata {
385
+ package: string;
386
+ version: string;
387
+ installedAt: string;
388
+ source: 'local' | 'npm';
389
+ }
105
390
  /**
106
- * Context information about the current location within a Launch77 workspace
391
+ * Reference to the template used to create this package
107
392
  */
108
- interface Launch77Context {
109
- isValid: boolean;
110
- locationType: Launch77LocationType;
111
- workspaceRoot: string;
112
- appsDir: string;
113
- workspaceVersion: string;
114
- workspaceName: string;
115
- appName?: string;
116
- packageName: string;
393
+ interface TemplateReference {
394
+ package: string;
395
+ version: string;
396
+ source: 'local' | 'npm';
397
+ createdAt: string;
398
+ }
399
+ /**
400
+ * The launch77 section within package.json
401
+ */
402
+ interface PackageManifest {
403
+ installedPlugins?: Record<string, InstalledPluginMetadata>;
404
+ template?: TemplateReference;
405
+ }
406
+ /**
407
+ * Service responsible for package manifest operations.
408
+ * Handles reading and writing launch77 metadata in package.json files.
409
+ * The "package manifest" refers to the launch77 section within package.json.
410
+ */
411
+ declare class PackageManifestService {
412
+ private filesystemService;
413
+ constructor(filesystemService?: FilesystemService);
414
+ /**
415
+ * Read the launch77 section (package manifest) from a package.json
416
+ */
417
+ readPackageManifest(packagePath: string): Promise<PackageManifest | null>;
418
+ /**
419
+ * Write the launch77 section (package manifest) to a package.json
420
+ */
421
+ writePackageManifest(packagePath: string, manifest: PackageManifest): Promise<void>;
422
+ /**
423
+ * Add an installed plugin to the manifest
424
+ */
425
+ addInstalledPlugin(packagePath: string, pluginKey: string, metadata: InstalledPluginMetadata): Promise<void>;
426
+ /**
427
+ * Remove an installed plugin from the manifest
428
+ */
429
+ removeInstalledPlugin(packagePath: string, pluginKey: string): Promise<void>;
430
+ /**
431
+ * Get installed plugins from the manifest
432
+ */
433
+ getInstalledPlugins(packagePath: string): Promise<Record<string, InstalledPluginMetadata>>;
434
+ /**
435
+ * Check if a plugin is installed
436
+ */
437
+ isPluginInstalled(packagePath: string, pluginKey: string): Promise<boolean>;
438
+ /**
439
+ * Get a specific installed plugin metadata
440
+ */
441
+ getInstalledPlugin(packagePath: string, pluginKey: string): Promise<InstalledPluginMetadata | null>;
442
+ /**
443
+ * Update package.json dependencies
444
+ */
445
+ addDependency(packagePath: string, packageName: string, version: string, isDev?: boolean): Promise<void>;
446
+ /**
447
+ * Remove a dependency from package.json
448
+ */
449
+ removeDependency(packagePath: string, packageName: string): Promise<void>;
450
+ /**
451
+ * Get the package.json content
452
+ */
453
+ readPackageJson(packagePath: string): Promise<unknown>;
454
+ /**
455
+ * Write the package.json content
456
+ */
457
+ writePackageJson(packagePath: string, packageJson: unknown): Promise<void>;
117
458
  }
118
459
 
119
460
  /**
120
- * Detect the Launch77 workspace context from the current working directory
121
- *
122
- * @param cwd - Current working directory path
123
- * @returns Context information about the workspace location
461
+ * Resolved plugin information for installation
124
462
  */
125
- declare function detectLaunch77Context(cwd: string): Promise<Launch77Context>;
463
+ interface ResolvedPlugin {
464
+ name: string;
465
+ version: string;
466
+ path: string;
467
+ source: 'local' | 'npm';
468
+ metadata?: PluginMetadata;
469
+ }
470
+ /**
471
+ * Service responsible for plugin installation workflow.
472
+ * Handles generator execution, dependency management, and installation tracking.
473
+ */
474
+ declare class PluginInstaller {
475
+ private filesystemService;
476
+ constructor(filesystemService?: FilesystemService);
477
+ /**
478
+ * Install a plugin into the target location
479
+ */
480
+ install(plugin: ResolvedPlugin, target: Target, targetPath: string, context: Launch77Context, logger?: (message: string) => void): Promise<void>;
481
+ /**
482
+ * Run a plugin's generator
483
+ */
484
+ runGenerator(plugin: ResolvedPlugin, _target: Target, targetPath: string, launch77Context: Launch77Context, logger?: (message: string) => void): Promise<void>;
485
+ /**
486
+ * Track plugin installation in package.json
487
+ */
488
+ trackInstallation(plugin: ResolvedPlugin, targetPath: string): Promise<void>;
489
+ /**
490
+ * Check if a plugin is already installed
491
+ */
492
+ isInstalled(pluginName: string, targetPath: string): Promise<boolean>;
493
+ /**
494
+ * Get installed plugin metadata
495
+ */
496
+ getInstalledPlugin(pluginName: string, targetPath: string): Promise<InstalledPluginMetadata | null>;
497
+ /**
498
+ * Uninstall a plugin
499
+ */
500
+ uninstall(pluginName: string, targetPath: string): Promise<void>;
501
+ /**
502
+ * Install npm dependencies
503
+ */
504
+ installDependencies(targetPath: string): Promise<void>;
505
+ /**
506
+ * Extract plugin key from package name
507
+ */
508
+ private extractPluginKey;
509
+ }
126
510
 
127
511
  /**
128
- * Name Validation Utilities
129
- *
130
- * Provides validation for plugin names and npm package names.
131
- * Used by both CLI and plugin-runtime to ensure consistent naming rules.
512
+ * Result of downloading an npm package
132
513
  */
133
- interface ValidationResult$1 {
514
+ interface DownloadPackageResult {
515
+ path: string;
516
+ version: string;
517
+ name: string;
518
+ }
519
+ /**
520
+ * Options for downloading an npm package
521
+ */
522
+ interface DownloadPackageOptions {
523
+ packageName: string;
524
+ }
525
+ /**
526
+ * Validation result for npm package names
527
+ */
528
+ interface ValidationResult {
134
529
  isValid: boolean;
135
530
  error?: string;
136
531
  }
137
532
  /**
138
- * Validate a plugin name
139
- *
140
- * Rules:
141
- * - Must start with a lowercase letter
142
- * - Can contain lowercase letters, numbers, and hyphens
143
- * - Cannot contain uppercase, spaces, underscores, or special characters
144
- *
145
- * @param name - The plugin name to validate
146
- * @returns ValidationResult indicating if valid and error message if not
147
- *
148
- * @example
149
- * validatePluginName('my-plugin') // { isValid: true }
150
- * validatePluginName('MyPlugin') // { isValid: false, error: '...' }
533
+ * Service responsible for NPM operations.
534
+ * Provides a clean abstraction over npm commands and package management.
151
535
  */
152
- declare function validatePluginName(name: string): ValidationResult$1;
536
+ declare class NpmService {
537
+ /**
538
+ * Validate an npm package name (scoped or unscoped)
539
+ *
540
+ * Rules for unscoped packages:
541
+ * - Must start with a lowercase letter
542
+ * - Can contain lowercase letters, numbers, hyphens, periods, underscores
543
+ *
544
+ * Rules for scoped packages:
545
+ * - Format: @org/package
546
+ * - Both org and package follow npm naming rules
547
+ *
548
+ * @param name - The npm package name to validate
549
+ * @returns ValidationResult indicating if valid and error message if not
550
+ */
551
+ validatePackageName(name: string): ValidationResult;
552
+ /**
553
+ * Parse a package name input and determine its type
554
+ *
555
+ * Returns information about whether the input is:
556
+ * - A scoped npm package (e.g., @org/package)
557
+ * - An unscoped name (e.g., my-package)
558
+ * - Invalid
559
+ */
560
+ parsePackageName(name: string): {
561
+ type: 'scoped' | 'unscoped' | 'invalid';
562
+ isValid: boolean;
563
+ name?: string;
564
+ org?: string;
565
+ package?: string;
566
+ error?: string;
567
+ };
568
+ /**
569
+ * Install npm dependencies in a directory
570
+ */
571
+ install(cwd: string): Promise<void>;
572
+ /**
573
+ * Install npm dependencies with legacy peer deps flag
574
+ */
575
+ installWithLegacyPeerDeps(cwd: string): Promise<void>;
576
+ /**
577
+ * Download an npm package to a temporary directory
578
+ */
579
+ downloadPackage(options: DownloadPackageOptions): Promise<DownloadPackageResult>;
580
+ /**
581
+ * Get information about an npm package
582
+ */
583
+ getPackageInfo(packageName: string): Promise<unknown>;
584
+ /**
585
+ * Check if a package exists on npm
586
+ */
587
+ packageExists(packageName: string): Promise<boolean>;
588
+ /**
589
+ * Install a specific package as a dependency
590
+ */
591
+ installPackage(packageName: string, cwd: string, isDev?: boolean): Promise<void>;
592
+ /**
593
+ * Uninstall a package
594
+ */
595
+ uninstallPackage(packageName: string, cwd: string): Promise<void>;
596
+ /**
597
+ * Clean up a temporary download directory
598
+ */
599
+ cleanupDownload(downloadPath: string): Promise<void>;
600
+ }
601
+
153
602
  /**
154
- * Validate an npm package name (scoped or unscoped)
155
- *
156
- * Rules for unscoped packages:
157
- * - Must start with a lowercase letter
158
- * - Can contain lowercase letters, numbers, and hyphens
159
- *
160
- * Rules for scoped packages:
161
- * - Format: @org/package
162
- * - Org must contain lowercase letters, numbers, and hyphens
163
- * - Package must contain lowercase letters, numbers, and hyphens
164
- *
165
- * @param name - The npm package name to validate
166
- * @returns ValidationResult indicating if valid and error message if not
167
- *
168
- * @example
169
- * isValidNpmPackageName('my-package') // { isValid: true }
170
- * isValidNpmPackageName('@org/my-package') // { isValid: true }
171
- * isValidNpmPackageName('@release') // { isValid: false, error: '...' }
603
+ * Base interface for package resolution results
172
604
  */
173
- declare function isValidNpmPackageName(name: string): ValidationResult$1;
605
+ interface PackageResolution {
606
+ /** The source of the package */
607
+ source: 'local' | 'npm';
608
+ /** The resolved name/package to use */
609
+ resolvedName: string;
610
+ /** The local path if source is 'local' */
611
+ localPath?: string;
612
+ /** The npm package name if source is 'npm' */
613
+ npmPackage?: string;
614
+ /** The version from package.json (required for local packages, undefined for npm until installed) */
615
+ version?: string;
616
+ }
174
617
  /**
175
- * Parse a plugin name input and determine its type
176
- *
177
- * Returns information about whether the input is:
178
- * - A scoped npm package (e.g., @org/package)
179
- * - An unscoped name (e.g., my-plugin)
180
- * - Invalid
618
+ * Abstract base class for resolving Launch77 packages
181
619
  *
182
- * @param name - The plugin name to parse
183
- * @returns Object with type and validation info
620
+ * Provides generic resolution logic for finding packages in:
621
+ * 1. Local workspace directory (e.g., plugins/, app-templates/)
622
+ * 2. npm packages with configured prefix (e.g., @launch77-shared/plugin-*, @launch77-shared/app-template-*)
184
623
  *
185
- * @example
186
- * parsePluginName('release')
187
- * // { type: 'unscoped', isValid: true, name: 'release' }
188
- *
189
- * parsePluginName('@ibm/analytics')
190
- * // { type: 'scoped', isValid: true, name: '@ibm/analytics', org: 'ibm', package: 'analytics' }
624
+ * Concrete implementations must provide:
625
+ * - Folder name for local resolution
626
+ * - Package prefix for npm resolution
627
+ * - Verification logic to validate local packages
628
+ */
629
+ declare abstract class PackageResolver {
630
+ protected npmService: NpmService;
631
+ constructor();
632
+ /**
633
+ * Get the local folder name where packages of this type are stored
634
+ * @example 'plugins' or 'app-templates'
635
+ */
636
+ protected abstract getFolderName(): string;
637
+ /**
638
+ * Get the npm package prefix for unscoped packages
639
+ * @example '@launch77-shared/plugin-' or '@launch77-shared/app-template-'
640
+ */
641
+ protected abstract getPackagePrefix(): string;
642
+ /**
643
+ * Verify that a local package is valid and complete
644
+ * @param localPath - The local directory path to verify
645
+ * @returns true if the package is valid, false otherwise
646
+ */
647
+ protected abstract verify(localPath: string): Promise<boolean>;
648
+ /**
649
+ * Validate package input name
650
+ *
651
+ * Accepts:
652
+ * - Unscoped names (e.g., "release", "my-package")
653
+ * - Scoped npm packages (e.g., "@ibm/package-name")
654
+ *
655
+ * Rejects:
656
+ * - Invalid formats
657
+ * - Empty strings
658
+ * - Names with invalid characters
659
+ *
660
+ * @param name - The package name to validate
661
+ * @returns ValidationResult with isValid and optional error message
662
+ *
663
+ * @example
664
+ * validateInput('release') // { isValid: true }
665
+ * validateInput('@ibm/analytics') // { isValid: true }
666
+ * validateInput('@invalid') // { isValid: false, error: '...' }
667
+ */
668
+ validateInput(name: string): ValidationResult;
669
+ /**
670
+ * Convert an unscoped package name to an npm package name
671
+ *
672
+ * Rules:
673
+ * - Unscoped names: prefix with configured package prefix
674
+ * - Scoped names: use as-is
675
+ *
676
+ * @param name - The package name (must be validated first)
677
+ * @returns The npm package name
678
+ *
679
+ * @example
680
+ * toNpmPackageName('release') // '@launch77-shared/plugin-release' (for PluginResolver)
681
+ * toNpmPackageName('@ibm/analytics') // '@ibm/analytics'
682
+ */
683
+ toNpmPackageName(name: string): string;
684
+ /**
685
+ * Read version from package.json
686
+ * @param packagePath - The path to the package directory
687
+ * @returns The version string from package.json
688
+ * @throws If package.json doesn't exist, can't be read, or is missing the version field
689
+ */
690
+ private readVersion;
691
+ /**
692
+ * Resolve package location from name
693
+ *
694
+ * Resolution order:
695
+ * 1. Check local workspace directory (configured by getFolderName())
696
+ * 2. Verify local package is valid (using verify())
697
+ * 3. Fall back to npm package name (with configured prefix)
698
+ * 4. Read version from package.json (if available)
699
+ *
700
+ * @param name - The package name to resolve
701
+ * @param workspaceRoot - The workspace root directory
702
+ * @returns PackageResolution with source, resolved location, and version
703
+ *
704
+ * @example
705
+ * // Local package found
706
+ * await resolveLocation('my-package', '/workspace')
707
+ * // { source: 'local', resolvedName: 'my-package', localPath: '/workspace/plugins/my-package', version: '1.0.0' }
708
+ *
709
+ * // Not found locally, resolve to npm
710
+ * await resolveLocation('release', '/workspace')
711
+ * // { source: 'npm', resolvedName: 'release', npmPackage: '@launch77-shared/plugin-release' }
712
+ *
713
+ * // Scoped package always resolves to npm
714
+ * await resolveLocation('@ibm/analytics', '/workspace')
715
+ * // { source: 'npm', resolvedName: '@ibm/analytics', npmPackage: '@ibm/analytics' }
716
+ */
717
+ resolveLocation(name: string, workspaceRoot: string): Promise<PackageResolution>;
718
+ }
719
+
720
+ /**
721
+ * Plugin resolver implementation
191
722
  *
192
- * parsePluginName('@release')
193
- * // { type: 'invalid', isValid: false, error: '...' }
723
+ * Resolves plugins from:
724
+ * - Local workspace plugins/ directory
725
+ * - npm packages with @launch77-shared/plugin- prefix
194
726
  */
195
- declare function parsePluginName(name: string): {
196
- type: 'scoped' | 'unscoped' | 'invalid';
197
- isValid: boolean;
198
- name?: string;
199
- org?: string;
200
- package?: string;
201
- error?: string;
202
- };
727
+ declare class PluginResolver extends PackageResolver {
728
+ protected getFolderName(): string;
729
+ protected getPackagePrefix(): string;
730
+ protected verify(localPath: string): Promise<boolean>;
731
+ }
203
732
 
204
- interface PluginConsistencyError {
205
- library: string;
206
- packageJsonVersion: string;
207
- pluginJsonVersion: string;
733
+ declare class PluginNotFoundError extends Error {
734
+ constructor(pluginName: string);
208
735
  }
209
- interface ValidationResult {
210
- valid: boolean;
211
- errors: PluginConsistencyError[];
736
+ declare class InvalidPluginContextError extends Error {
737
+ constructor(message: string);
212
738
  }
213
739
  /**
214
- * Validate that library versions in plugin.json match those in package.json
215
- *
216
- * This ensures that plugin templates (which use package.json versions during development)
217
- * install the same library versions for end users (which use plugin.json versions).
218
- *
219
- * @param pluginPath - Absolute path to the plugin directory
220
- * @returns Validation result with any errors found
221
- * @throws Error if package.json or plugin.json cannot be read
740
+ * Factory function to create standardized InvalidPluginContextError
741
+ * for when plugin:install is run outside of a package directory
222
742
  */
223
- declare function validatePluginConsistency(pluginPath: string): Promise<ValidationResult>;
743
+ declare function createInvalidContextError(currentLocation: string): InvalidPluginContextError;
744
+ /**
745
+ * Error when plugin.json is missing the required 'targets' field
746
+ */
747
+ declare class MissingPluginTargetsError extends Error {
748
+ constructor(pluginName: string);
749
+ }
224
750
  /**
225
- * Validate plugin consistency and throw a formatted error if validation fails
751
+ * Factory function to create error when plugin targets don't match current location
752
+ */
753
+ declare function createInvalidTargetError(pluginName: string, currentTarget: string, allowedTargets: string[]): InvalidPluginContextError;
754
+ declare class PluginInstallationError extends Error {
755
+ readonly cause?: Error | undefined;
756
+ constructor(message: string, cause?: Error | undefined);
757
+ }
758
+ /**
759
+ * Error when plugin resolution fails
760
+ */
761
+ declare class PluginResolutionError extends Error {
762
+ constructor(pluginName: string, reason: string);
763
+ }
764
+ /**
765
+ * Error when npm package installation fails
766
+ */
767
+ declare class NpmInstallationError extends Error {
768
+ readonly cause?: Error | undefined;
769
+ constructor(packageName: string, cause?: Error | undefined);
770
+ }
771
+ /**
772
+ * Error when plugin directory is not found for deletion
773
+ */
774
+ declare class PluginDirectoryNotFoundError extends Error {
775
+ constructor(pluginName: string, expectedPath: string);
776
+ }
777
+ /**
778
+ * Error when plugin name validation fails
779
+ */
780
+ declare class InvalidPluginNameError extends Error {
781
+ constructor(message: string);
782
+ }
783
+
784
+ /**
785
+ * Options for downloading an npm package
786
+ */
787
+ interface DownloadNpmPackageOptions {
788
+ /** The npm package name to download */
789
+ packageName: string;
790
+ /** The workspace root directory where node_modules will be */
791
+ workspaceRoot: string;
792
+ /** Optional logger function for status messages */
793
+ logger?: (message: string) => void;
794
+ }
795
+ /**
796
+ * Result of downloading an npm package
797
+ */
798
+ interface DownloadNpmPackageResult {
799
+ /** Full path to the downloaded package in node_modules */
800
+ packagePath: string;
801
+ /** The package name that was downloaded */
802
+ packageName: string;
803
+ }
804
+ /**
805
+ * Download and install an npm package to workspace node_modules
226
806
  *
227
- * This is a convenience function for use in build scripts that should
228
- * fail fast with a clear error message.
807
+ * This function:
808
+ * - Runs `npm install {packageName} --save-dev` in the workspace root
809
+ * - Adds the package to workspace package.json devDependencies
810
+ * - Returns the path to the installed package in node_modules
229
811
  *
230
- * @param pluginPath - Absolute path to the plugin directory
231
- * @throws Error with formatted message if validation fails
812
+ * @param options - Download options
813
+ * @returns The path to the installed package
814
+ * @throws NpmInstallationError if the download fails
815
+ *
816
+ * @example
817
+ * const result = await downloadNpmPackage({
818
+ * packageName: '@launch77-shared/plugin-release',
819
+ * workspaceRoot: '/path/to/workspace',
820
+ * logger: (msg) => console.log(msg)
821
+ * })
822
+ * // result.packagePath: '/path/to/workspace/node_modules/@launch77-shared/plugin-release'
232
823
  */
233
- declare function validatePluginConsistencyOrThrow(pluginPath: string): Promise<void>;
824
+ declare function downloadNpmPackage(options: DownloadNpmPackageOptions): Promise<DownloadNpmPackageResult>;
234
825
 
235
- export { Generator, type GeneratorContext, type InstalledPluginMetadata, type Launch77Context, type Launch77LocationType, type Launch77PackageManifest, type PluginConsistencyError, type PluginMetadata, type ValidationResult as PluginValidationResult, StandardGenerator, type TemplateMetadata, type TemplateReference, type ValidationResult$1 as ValidationResult, copyRecursive, detectLaunch77Context, isValidNpmPackageName, parsePluginName, pathExists, readPluginMetadata, readTemplateMetadata, validatePluginConsistency, validatePluginConsistencyOrThrow, validatePluginName };
826
+ export { type DeletePluginRequest, type DeletePluginResult, type DownloadNpmPackageOptions, type DownloadNpmPackageResult, type DownloadPackageResult, FilesystemService, Generator, type GeneratorContext, type HookResult, type InstallPluginRequest, type InstallPluginResult, type InstalledPluginMetadata, InvalidPluginContextError, InvalidPluginNameError, type Launch77Context, type Launch77LocationType, MetadataService, MissingPluginTargetsError, NpmInstallationError, NpmService, type PackageManifest, PackageManifestService, type PackageResolution, PackageResolver, type ParsedLocation, type PluginConsistencyError, PluginDirectoryNotFoundError, PluginInstallationError, PluginInstaller, type PluginMetadata, PluginNotFoundError, PluginResolutionError, PluginResolver, PluginService, type ValidationResult$1 as PluginValidationResult, StandardGenerator, type Target, type TemplateMetadata, type TemplateReference, type ValidationResult$2 as ValidationResult, type WorkspaceManifest, WorkspaceManifestService, WorkspaceService, createInvalidContextError, createInvalidTargetError, detectLaunch77Context, downloadNpmPackage, parseLocationFromPath };