@launch77/plugin-runtime 0.3.0 → 0.3.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/dist/index.d.ts +35 -4
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -130,7 +130,7 @@ declare function detectLaunch77Context(cwd: string): Promise<Launch77Context>;
|
|
|
130
130
|
* Provides validation for plugin names and npm package names.
|
|
131
131
|
* Used by both CLI and plugin-runtime to ensure consistent naming rules.
|
|
132
132
|
*/
|
|
133
|
-
interface ValidationResult {
|
|
133
|
+
interface ValidationResult$1 {
|
|
134
134
|
isValid: boolean;
|
|
135
135
|
error?: string;
|
|
136
136
|
}
|
|
@@ -149,7 +149,7 @@ interface ValidationResult {
|
|
|
149
149
|
* validatePluginName('my-plugin') // { isValid: true }
|
|
150
150
|
* validatePluginName('MyPlugin') // { isValid: false, error: '...' }
|
|
151
151
|
*/
|
|
152
|
-
declare function validatePluginName(name: string): ValidationResult;
|
|
152
|
+
declare function validatePluginName(name: string): ValidationResult$1;
|
|
153
153
|
/**
|
|
154
154
|
* Validate an npm package name (scoped or unscoped)
|
|
155
155
|
*
|
|
@@ -170,7 +170,7 @@ declare function validatePluginName(name: string): ValidationResult;
|
|
|
170
170
|
* isValidNpmPackageName('@org/my-package') // { isValid: true }
|
|
171
171
|
* isValidNpmPackageName('@release') // { isValid: false, error: '...' }
|
|
172
172
|
*/
|
|
173
|
-
declare function isValidNpmPackageName(name: string): ValidationResult;
|
|
173
|
+
declare function isValidNpmPackageName(name: string): ValidationResult$1;
|
|
174
174
|
/**
|
|
175
175
|
* Parse a plugin name input and determine its type
|
|
176
176
|
*
|
|
@@ -201,4 +201,35 @@ declare function parsePluginName(name: string): {
|
|
|
201
201
|
error?: string;
|
|
202
202
|
};
|
|
203
203
|
|
|
204
|
-
|
|
204
|
+
interface PluginConsistencyError {
|
|
205
|
+
library: string;
|
|
206
|
+
packageJsonVersion: string | 'MISSING';
|
|
207
|
+
pluginJsonVersion: string;
|
|
208
|
+
}
|
|
209
|
+
interface ValidationResult {
|
|
210
|
+
valid: boolean;
|
|
211
|
+
errors: PluginConsistencyError[];
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
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
|
|
222
|
+
*/
|
|
223
|
+
declare function validatePluginConsistency(pluginPath: string): Promise<ValidationResult>;
|
|
224
|
+
/**
|
|
225
|
+
* Validate plugin consistency and throw a formatted error if validation fails
|
|
226
|
+
*
|
|
227
|
+
* This is a convenience function for use in build scripts that should
|
|
228
|
+
* fail fast with a clear error message.
|
|
229
|
+
*
|
|
230
|
+
* @param pluginPath - Absolute path to the plugin directory
|
|
231
|
+
* @throws Error with formatted message if validation fails
|
|
232
|
+
*/
|
|
233
|
+
declare function validatePluginConsistencyOrThrow(pluginPath: string): Promise<void>;
|
|
234
|
+
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -327,6 +327,72 @@ function parsePluginName(name) {
|
|
|
327
327
|
name: trimmedName
|
|
328
328
|
};
|
|
329
329
|
}
|
|
330
|
+
|
|
331
|
+
// src/utils/validate-plugin-consistency.ts
|
|
332
|
+
import * as fs5 from "fs/promises";
|
|
333
|
+
import * as path7 from "path";
|
|
334
|
+
import chalk2 from "chalk";
|
|
335
|
+
async function validatePluginConsistency(pluginPath) {
|
|
336
|
+
const packageJsonPath = path7.join(pluginPath, "package.json");
|
|
337
|
+
const packageJsonContent = await fs5.readFile(packageJsonPath, "utf-8");
|
|
338
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
339
|
+
const pluginJsonPath = path7.join(pluginPath, "plugin.json");
|
|
340
|
+
const pluginJsonContent = await fs5.readFile(pluginJsonPath, "utf-8");
|
|
341
|
+
const pluginJson = JSON.parse(pluginJsonContent);
|
|
342
|
+
const errors = [];
|
|
343
|
+
if (pluginJson.libraryDependencies) {
|
|
344
|
+
for (const [library, pluginJsonVersion] of Object.entries(pluginJson.libraryDependencies)) {
|
|
345
|
+
const packageJsonVersion = packageJson.dependencies?.[library];
|
|
346
|
+
if (!packageJsonVersion) {
|
|
347
|
+
errors.push({
|
|
348
|
+
library,
|
|
349
|
+
packageJsonVersion: "MISSING",
|
|
350
|
+
pluginJsonVersion
|
|
351
|
+
});
|
|
352
|
+
} else if (packageJsonVersion !== pluginJsonVersion) {
|
|
353
|
+
errors.push({
|
|
354
|
+
library,
|
|
355
|
+
packageJsonVersion,
|
|
356
|
+
pluginJsonVersion
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
valid: errors.length === 0,
|
|
363
|
+
errors
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
async function validatePluginConsistencyOrThrow(pluginPath) {
|
|
367
|
+
const result = await validatePluginConsistency(pluginPath);
|
|
368
|
+
if (!result.valid) {
|
|
369
|
+
const packageJsonPath = path7.join(pluginPath, "package.json");
|
|
370
|
+
const packageJsonContent = await fs5.readFile(packageJsonPath, "utf-8");
|
|
371
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
372
|
+
const pluginName = packageJson.name || "unknown";
|
|
373
|
+
console.error(chalk2.red("\n\u274C Plugin consistency validation failed!\n"));
|
|
374
|
+
console.error(chalk2.yellow(`Plugin: ${pluginName}
|
|
375
|
+
`));
|
|
376
|
+
console.error(chalk2.white("Library versions in package.json must match plugin.json:\n"));
|
|
377
|
+
for (const error of result.errors) {
|
|
378
|
+
console.error(chalk2.white(` ${error.library}:`));
|
|
379
|
+
if (error.packageJsonVersion === "MISSING") {
|
|
380
|
+
console.error(chalk2.red(` \u2717 package.json: MISSING (library not in dependencies)`));
|
|
381
|
+
console.error(chalk2.red(` \u2717 plugin.json: ${error.pluginJsonVersion}`));
|
|
382
|
+
} else {
|
|
383
|
+
console.error(chalk2.red(` \u2717 package.json: ${error.packageJsonVersion}`));
|
|
384
|
+
console.error(chalk2.red(` \u2717 plugin.json: ${error.pluginJsonVersion}`));
|
|
385
|
+
}
|
|
386
|
+
console.error();
|
|
387
|
+
}
|
|
388
|
+
console.error(chalk2.white("Why this matters:"));
|
|
389
|
+
console.error(chalk2.white(" - Plugin templates use package.json versions during development"));
|
|
390
|
+
console.error(chalk2.white(" - End users get plugin.json versions when they install the plugin"));
|
|
391
|
+
console.error(chalk2.white(" - Mismatched versions cause template code to break for users\n"));
|
|
392
|
+
console.error(chalk2.cyan("Fix: Update both files to use the same version.\n"));
|
|
393
|
+
throw new Error("Plugin validation failed - library versions do not match");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
330
396
|
export {
|
|
331
397
|
Generator,
|
|
332
398
|
StandardGenerator,
|
|
@@ -337,6 +403,8 @@ export {
|
|
|
337
403
|
pathExists,
|
|
338
404
|
readPluginMetadata,
|
|
339
405
|
readTemplateMetadata,
|
|
406
|
+
validatePluginConsistency,
|
|
407
|
+
validatePluginConsistencyOrThrow,
|
|
340
408
|
validatePluginName
|
|
341
409
|
};
|
|
342
410
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generator.ts","../src/standard-generator.ts","../src/utils/file-operations.ts","../src/utils/metadata.ts","../src/context/index.ts","../src/context/location-parser.ts","../src/context/manifest.ts","../src/utils/name-validation.ts"],"sourcesContent":["import type { GeneratorContext } from './types.js'\n\n/**\n * Base abstract class for all plugin generators.\n *\n * The only requirement for a generator is to implement the run() method.\n * This method is called by the CLI when a plugin is installed.\n *\n * Use this base class when you need full control over the installation process.\n * For convention-based installation, extend StandardGenerator instead.\n */\nexport abstract class Generator {\n constructor(protected context: GeneratorContext) {}\n\n /**\n * Main entry point for plugin installation.\n * This method is called by the CLI and must be implemented by all generators.\n */\n abstract run(): Promise<void>\n}\n","import * as path from 'path'\nimport * as fs from 'fs/promises'\n\nimport chalk from 'chalk'\nimport { execa } from 'execa'\n\nimport { Generator } from './generator.js'\nimport { copyRecursive, pathExists } from './utils/file-operations.js'\nimport { readPluginMetadata } from './utils/metadata.js'\nimport type { PluginMetadata } from './types.js'\n\n/**\n * Standard generator with convention-over-configuration approach.\n *\n * Provides a structured lifecycle with smart defaults:\n * 1. updateDependencies() - Reads plugin.json, merges into package.json\n * 2. installDependencies() - Runs npm install\n * 3. copyTemplates() - Copies templates/ folder to app\n * 4. injectCode() - Override this for surgical code edits\n *\n * Most plugins only need to implement injectCode().\n * For full control, extend Generator instead.\n */\nexport abstract class StandardGenerator extends Generator {\n async run(): Promise<void> {\n console.log(chalk.green(`\\n✅ Installing plugin...\\n`))\n\n await this.updateDependencies()\n await this.installDependencies()\n await this.copyTemplates()\n await this.injectCode()\n\n console.log(chalk.green(`\\n✅ Plugin installed successfully!\\n`))\n this.showNextSteps()\n }\n\n protected async updateDependencies(): Promise<void> {\n const pluginJsonPath = path.join(this.context.pluginPath, 'plugin.json')\n\n if (!(await pathExists(pluginJsonPath))) return\n\n try {\n const pluginMetadata: PluginMetadata = await readPluginMetadata(this.context.pluginPath)\n\n if (!pluginMetadata.libraryDependencies || Object.keys(pluginMetadata.libraryDependencies).length === 0) {\n return\n }\n\n const packageJsonPath = path.join(this.context.appPath, 'package.json')\n const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8')\n const packageJson = JSON.parse(packageJsonContent)\n\n packageJson.dependencies = {\n ...packageJson.dependencies,\n ...pluginMetadata.libraryDependencies,\n }\n\n await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n', 'utf-8')\n console.log(chalk.green(' ✓ Updated package.json with dependencies'))\n } catch (error) {\n console.log(chalk.yellow(` ⚠️ Could not update dependencies: ${error}`))\n }\n }\n\n protected async installDependencies(): Promise<void> {\n try {\n console.log(chalk.cyan(' Installing dependencies...'))\n const workspaceRoot = path.dirname(path.dirname(this.context.appPath))\n\n await execa('npm', ['install'], {\n cwd: workspaceRoot,\n stdio: 'pipe',\n })\n\n console.log(chalk.green(' ✓ Dependencies installed'))\n } catch (error) {\n console.log(chalk.yellow(` ⚠️ Could not install dependencies: ${error}`))\n }\n }\n\n protected async copyTemplates(): Promise<void> {\n const templatesDir = path.join(this.context.pluginPath, 'templates')\n\n if (!(await pathExists(templatesDir))) return\n\n try {\n await copyRecursive(templatesDir, this.context.appPath)\n console.log(chalk.green(' ✓ Copied template files'))\n } catch (error) {\n console.log(chalk.yellow(` ⚠️ Could not copy template files: ${error}`))\n }\n }\n\n protected async injectCode(): Promise<void> {\n // No-op by default - plugins override this for custom code injection\n }\n\n protected showNextSteps(): void {\n // No-op by default - plugins can override to show custom next steps\n }\n}\n","import * as fs from 'fs/promises'\nimport * as path from 'path'\n\nexport async function copyRecursive(src: string, dest: string): Promise<void> {\n const stat = await fs.stat(src)\n\n if (stat.isDirectory()) {\n await fs.mkdir(dest, { recursive: true })\n const entries = await fs.readdir(src)\n\n for (const entry of entries) {\n await copyRecursive(path.join(src, entry), path.join(dest, entry))\n }\n } else {\n await fs.copyFile(src, dest)\n }\n}\n\nexport async function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath)\n return true\n } catch {\n return false\n }\n}\n","import * as fs from 'fs/promises'\nimport * as path from 'path'\n\nimport type { PluginMetadata, TemplateMetadata } from '../types.js'\n\n/**\n * Read and parse plugin metadata from plugin.json\n *\n * Only reads plugin-specific configuration fields:\n * - targets: where the plugin can be installed\n * - pluginDependencies: other plugins this plugin requires\n * - libraryDependencies: npm packages to inject into target\n *\n * Name and version are read from package.json during resolution.\n *\n * @param pluginPath - Absolute path to the plugin directory\n * @returns Parsed plugin metadata\n */\nexport async function readPluginMetadata(pluginPath: string): Promise<PluginMetadata> {\n const metadataPath = path.join(pluginPath, 'plugin.json')\n const content = await fs.readFile(metadataPath, 'utf-8')\n const parsed = JSON.parse(content)\n\n // Extract only plugin-specific fields, ignoring name/version/description\n return {\n targets: parsed.targets || [],\n pluginDependencies: parsed.pluginDependencies,\n libraryDependencies: parsed.libraryDependencies,\n }\n}\n\n/**\n * Read and parse template metadata from template.json\n *\n * Currently template.json contains no fields - it exists as a placeholder\n * for future template-specific configuration (e.g., deployment metadata).\n *\n * Template name and version are read from package.json during resolution.\n *\n * @param templatePath - Absolute path to the template directory\n * @returns Template metadata (currently empty)\n */\nexport async function readTemplateMetadata(templatePath: string): Promise<TemplateMetadata> {\n const templateJsonPath = path.join(templatePath, 'template.json')\n try {\n const content = await fs.readFile(templateJsonPath, 'utf-8')\n return JSON.parse(content) as TemplateMetadata\n } catch {\n // File doesn't exist or can't be read - return empty metadata\n return {}\n }\n}\n","import * as path from 'path'\n\nimport { parseLocationFromPath } from './location-parser.js'\nimport { findWorkspaceRoot, readWorkspaceManifest } from './manifest.js'\n\nimport type { Launch77Context } from './types.js'\n\n/**\n * Detect the Launch77 workspace context from the current working directory\n *\n * @param cwd - Current working directory path\n * @returns Context information about the workspace location\n */\nexport async function detectLaunch77Context(cwd: string): Promise<Launch77Context> {\n const resolvedCwd = path.resolve(cwd)\n\n // Find the workspace root\n const workspaceRoot = await findWorkspaceRoot(resolvedCwd)\n\n if (!workspaceRoot) {\n return {\n isValid: false,\n locationType: 'unknown',\n workspaceRoot: '',\n appsDir: '',\n workspaceVersion: '',\n workspaceName: '',\n packageName: '',\n }\n }\n\n // Read workspace manifest\n let manifest\n try {\n manifest = await readWorkspaceManifest(workspaceRoot)\n } catch (error) {\n // Manifest exists (we found it) but couldn't read it\n return {\n isValid: false,\n locationType: 'unknown',\n workspaceRoot: '',\n appsDir: '',\n workspaceVersion: '',\n workspaceName: '',\n packageName: '',\n }\n }\n\n // Parse the directory structure to determine location\n const parsed = parseLocationFromPath(resolvedCwd, workspaceRoot)\n\n // Workspace name is the directory name\n const workspaceName = path.basename(workspaceRoot)\n\n // Apps directory is always {workspaceRoot}/apps\n const appsDir = path.join(workspaceRoot, 'apps')\n\n // Package name: @{workspaceName}/{appName} (if in app)\n const packageName = parsed.appName ? `@${workspaceName}/${parsed.appName}` : ''\n\n return {\n isValid: true,\n locationType: parsed.locationType,\n workspaceRoot,\n appsDir,\n workspaceVersion: manifest.version,\n workspaceName,\n appName: parsed.appName,\n packageName,\n }\n}\n\n// Re-export types for convenience\nexport type { Launch77Context, Launch77LocationType, WorkspaceManifest, ParsedLocation } from './types.js'\n","import * as path from 'path'\n\nimport type { ParsedLocation } from './types.js'\n\n/**\n * Parse the directory structure to determine location context\n *\n * Based on patterns:\n * - apps/[name] → workspace-app\n * - libraries/[name] → workspace-library\n * - plugins/[name] → workspace-plugin\n * - app-templates/[name] → workspace-app-template\n * - (empty or root) → workspace-root\n *\n * @param cwdPath - Current working directory path\n * @param workspaceRoot - Root path of the workspace\n * @returns Parsed location information\n */\nexport function parseLocationFromPath(cwdPath: string, workspaceRoot: string): ParsedLocation {\n const relativePath = path.relative(workspaceRoot, cwdPath)\n\n // At workspace root\n if (!relativePath || relativePath === '.') {\n return { locationType: 'workspace-root' }\n }\n\n const parts = relativePath.split(path.sep)\n\n // apps/[app-name]/...\n if (parts[0] === 'apps' && parts.length >= 2) {\n return {\n locationType: 'workspace-app',\n appName: parts[1],\n }\n }\n\n // libraries/[lib-name]/...\n if (parts[0] === 'libraries' && parts.length >= 2) {\n return {\n locationType: 'workspace-library',\n appName: parts[1],\n }\n }\n\n // plugins/[plugin-name]/...\n if (parts[0] === 'plugins' && parts.length >= 2) {\n return {\n locationType: 'workspace-plugin',\n appName: parts[1],\n }\n }\n\n // app-templates/[template-name]/...\n if (parts[0] === 'app-templates' && parts.length >= 2) {\n return {\n locationType: 'workspace-app-template',\n appName: parts[1],\n }\n }\n\n // Somewhere else in workspace\n return { locationType: 'workspace-root' }\n}\n","import * as path from 'path'\n\nimport fs from 'fs-extra'\n\nimport type { WorkspaceManifest } from './types.js'\n\nconst WORKSPACE_MANIFEST = '.launch77/workspace.json'\n\n/**\n * Check if a directory contains a Launch77 workspace manifest\n */\nexport async function isWorkspaceRoot(dir: string): Promise<boolean> {\n const manifestPath = path.join(dir, WORKSPACE_MANIFEST)\n return await fs.pathExists(manifestPath)\n}\n\n/**\n * Read workspace manifest from a workspace root directory\n */\nexport async function readWorkspaceManifest(workspaceRoot: string): Promise<WorkspaceManifest> {\n const manifestPath = path.join(workspaceRoot, WORKSPACE_MANIFEST)\n return await fs.readJSON(manifestPath)\n}\n\n/**\n * Find the workspace root by traversing up from a starting directory\n * Looks for .launch77/workspace.json manifest file\n *\n * @param startDir - Directory to start searching from\n * @returns Path to workspace root, or null if not found\n */\nexport async function findWorkspaceRoot(startDir: string): Promise<string | null> {\n let currentDir = path.resolve(startDir)\n const rootDir = path.parse(currentDir).root\n\n while (currentDir !== rootDir) {\n if (await isWorkspaceRoot(currentDir)) {\n return currentDir\n }\n currentDir = path.dirname(currentDir)\n }\n\n return null\n}\n","/**\n * Name Validation Utilities\n *\n * Provides validation for plugin names and npm package names.\n * Used by both CLI and plugin-runtime to ensure consistent naming rules.\n */\n\nexport interface ValidationResult {\n isValid: boolean\n error?: string\n}\n\n/**\n * Validate a plugin name\n *\n * Rules:\n * - Must start with a lowercase letter\n * - Can contain lowercase letters, numbers, and hyphens\n * - Cannot contain uppercase, spaces, underscores, or special characters\n *\n * @param name - The plugin name to validate\n * @returns ValidationResult indicating if valid and error message if not\n *\n * @example\n * validatePluginName('my-plugin') // { isValid: true }\n * validatePluginName('MyPlugin') // { isValid: false, error: '...' }\n */\nexport function validatePluginName(name: string): ValidationResult {\n if (!name || name.trim().length === 0) {\n return {\n isValid: false,\n error: 'Plugin name cannot be empty',\n }\n }\n\n const trimmedName = name.trim()\n\n // Must start with a lowercase letter\n if (!/^[a-z]/.test(trimmedName)) {\n return {\n isValid: false,\n error: 'Plugin name must start with a lowercase letter (a-z)',\n }\n }\n\n // Can only contain lowercase letters, numbers, and hyphens\n if (!/^[a-z][a-z0-9-]*$/.test(trimmedName)) {\n return {\n isValid: false,\n error: 'Plugin name can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-)',\n }\n }\n\n return { isValid: true }\n}\n\n/**\n * Validate an npm package name (scoped or unscoped)\n *\n * Rules for unscoped packages:\n * - Must start with a lowercase letter\n * - Can contain lowercase letters, numbers, and hyphens\n *\n * Rules for scoped packages:\n * - Format: @org/package\n * - Org must contain lowercase letters, numbers, and hyphens\n * - Package must contain lowercase letters, numbers, and hyphens\n *\n * @param name - The npm package name to validate\n * @returns ValidationResult indicating if valid and error message if not\n *\n * @example\n * isValidNpmPackageName('my-package') // { isValid: true }\n * isValidNpmPackageName('@org/my-package') // { isValid: true }\n * isValidNpmPackageName('@release') // { isValid: false, error: '...' }\n */\nexport function isValidNpmPackageName(name: string): ValidationResult {\n if (!name || name.trim().length === 0) {\n return {\n isValid: false,\n error: 'Package name cannot be empty',\n }\n }\n\n const trimmedName = name.trim()\n\n // Check if it's a scoped package\n if (trimmedName.startsWith('@')) {\n // Scoped package format: @org/package\n const scopedPattern = /^@([a-z0-9-]+)\\/([a-z0-9-]+)$/\n\n if (!scopedPattern.test(trimmedName)) {\n return {\n isValid: false,\n error: 'Scoped package must be in format @org/package where org and package contain only lowercase letters, numbers, and hyphens',\n }\n }\n\n return { isValid: true }\n }\n\n // Unscoped package - same rules as plugin/workspace names\n if (!/^[a-z][a-z0-9-]*$/.test(trimmedName)) {\n return {\n isValid: false,\n error: 'Package name must start with a lowercase letter and contain only lowercase letters (a-z), numbers (0-9), and hyphens (-)',\n }\n }\n\n return { isValid: true }\n}\n\n/**\n * Parse a plugin name input and determine its type\n *\n * Returns information about whether the input is:\n * - A scoped npm package (e.g., @org/package)\n * - An unscoped name (e.g., my-plugin)\n * - Invalid\n *\n * @param name - The plugin name to parse\n * @returns Object with type and validation info\n *\n * @example\n * parsePluginName('release')\n * // { type: 'unscoped', isValid: true, name: 'release' }\n *\n * parsePluginName('@ibm/analytics')\n * // { type: 'scoped', isValid: true, name: '@ibm/analytics', org: 'ibm', package: 'analytics' }\n *\n * parsePluginName('@release')\n * // { type: 'invalid', isValid: false, error: '...' }\n */\nexport function parsePluginName(name: string): {\n type: 'scoped' | 'unscoped' | 'invalid'\n isValid: boolean\n name?: string\n org?: string\n package?: string\n error?: string\n} {\n if (!name || name.trim().length === 0) {\n return {\n type: 'invalid',\n isValid: false,\n error: 'Plugin name cannot be empty',\n }\n }\n\n const trimmedName = name.trim()\n\n // Check if scoped\n if (trimmedName.startsWith('@')) {\n const validation = isValidNpmPackageName(trimmedName)\n\n if (!validation.isValid) {\n return {\n type: 'invalid',\n isValid: false,\n error: validation.error,\n }\n }\n\n // Extract org and package from @org/package\n const match = trimmedName.match(/^@([a-z0-9-]+)\\/([a-z0-9-]+)$/)\n if (match) {\n return {\n type: 'scoped',\n isValid: true,\n name: trimmedName,\n org: match[1],\n package: match[2],\n }\n }\n\n return {\n type: 'invalid',\n isValid: false,\n error: 'Invalid scoped package format',\n }\n }\n\n // Unscoped - validate as plugin name\n const validation = validatePluginName(trimmedName)\n\n if (!validation.isValid) {\n return {\n type: 'invalid',\n isValid: false,\n error: validation.error,\n }\n }\n\n return {\n type: 'unscoped',\n isValid: true,\n name: trimmedName,\n }\n}\n"],"mappings":"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","names":["path","fs","stat","fs","path","path","path","path","fs","validation"]}
|
|
1
|
+
{"version":3,"sources":["../src/generator.ts","../src/standard-generator.ts","../src/utils/file-operations.ts","../src/utils/metadata.ts","../src/context/index.ts","../src/context/location-parser.ts","../src/context/manifest.ts","../src/utils/name-validation.ts","../src/utils/validate-plugin-consistency.ts"],"sourcesContent":["import type { GeneratorContext } from './types.js'\n\n/**\n * Base abstract class for all plugin generators.\n *\n * The only requirement for a generator is to implement the run() method.\n * This method is called by the CLI when a plugin is installed.\n *\n * Use this base class when you need full control over the installation process.\n * For convention-based installation, extend StandardGenerator instead.\n */\nexport abstract class Generator {\n constructor(protected context: GeneratorContext) {}\n\n /**\n * Main entry point for plugin installation.\n * This method is called by the CLI and must be implemented by all generators.\n */\n abstract run(): Promise<void>\n}\n","import * as path from 'path'\nimport * as fs from 'fs/promises'\n\nimport chalk from 'chalk'\nimport { execa } from 'execa'\n\nimport { Generator } from './generator.js'\nimport { copyRecursive, pathExists } from './utils/file-operations.js'\nimport { readPluginMetadata } from './utils/metadata.js'\nimport type { PluginMetadata } from './types.js'\n\n/**\n * Standard generator with convention-over-configuration approach.\n *\n * Provides a structured lifecycle with smart defaults:\n * 1. updateDependencies() - Reads plugin.json, merges into package.json\n * 2. installDependencies() - Runs npm install\n * 3. copyTemplates() - Copies templates/ folder to app\n * 4. injectCode() - Override this for surgical code edits\n *\n * Most plugins only need to implement injectCode().\n * For full control, extend Generator instead.\n */\nexport abstract class StandardGenerator extends Generator {\n async run(): Promise<void> {\n console.log(chalk.green(`\\n✅ Installing plugin...\\n`))\n\n await this.updateDependencies()\n await this.installDependencies()\n await this.copyTemplates()\n await this.injectCode()\n\n console.log(chalk.green(`\\n✅ Plugin installed successfully!\\n`))\n this.showNextSteps()\n }\n\n protected async updateDependencies(): Promise<void> {\n const pluginJsonPath = path.join(this.context.pluginPath, 'plugin.json')\n\n if (!(await pathExists(pluginJsonPath))) return\n\n try {\n const pluginMetadata: PluginMetadata = await readPluginMetadata(this.context.pluginPath)\n\n if (!pluginMetadata.libraryDependencies || Object.keys(pluginMetadata.libraryDependencies).length === 0) {\n return\n }\n\n const packageJsonPath = path.join(this.context.appPath, 'package.json')\n const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8')\n const packageJson = JSON.parse(packageJsonContent)\n\n packageJson.dependencies = {\n ...packageJson.dependencies,\n ...pluginMetadata.libraryDependencies,\n }\n\n await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n', 'utf-8')\n console.log(chalk.green(' ✓ Updated package.json with dependencies'))\n } catch (error) {\n console.log(chalk.yellow(` ⚠️ Could not update dependencies: ${error}`))\n }\n }\n\n protected async installDependencies(): Promise<void> {\n try {\n console.log(chalk.cyan(' Installing dependencies...'))\n const workspaceRoot = path.dirname(path.dirname(this.context.appPath))\n\n await execa('npm', ['install'], {\n cwd: workspaceRoot,\n stdio: 'pipe',\n })\n\n console.log(chalk.green(' ✓ Dependencies installed'))\n } catch (error) {\n console.log(chalk.yellow(` ⚠️ Could not install dependencies: ${error}`))\n }\n }\n\n protected async copyTemplates(): Promise<void> {\n const templatesDir = path.join(this.context.pluginPath, 'templates')\n\n if (!(await pathExists(templatesDir))) return\n\n try {\n await copyRecursive(templatesDir, this.context.appPath)\n console.log(chalk.green(' ✓ Copied template files'))\n } catch (error) {\n console.log(chalk.yellow(` ⚠️ Could not copy template files: ${error}`))\n }\n }\n\n protected async injectCode(): Promise<void> {\n // No-op by default - plugins override this for custom code injection\n }\n\n protected showNextSteps(): void {\n // No-op by default - plugins can override to show custom next steps\n }\n}\n","import * as fs from 'fs/promises'\nimport * as path from 'path'\n\nexport async function copyRecursive(src: string, dest: string): Promise<void> {\n const stat = await fs.stat(src)\n\n if (stat.isDirectory()) {\n await fs.mkdir(dest, { recursive: true })\n const entries = await fs.readdir(src)\n\n for (const entry of entries) {\n await copyRecursive(path.join(src, entry), path.join(dest, entry))\n }\n } else {\n await fs.copyFile(src, dest)\n }\n}\n\nexport async function pathExists(filePath: string): Promise<boolean> {\n try {\n await fs.access(filePath)\n return true\n } catch {\n return false\n }\n}\n","import * as fs from 'fs/promises'\nimport * as path from 'path'\n\nimport type { PluginMetadata, TemplateMetadata } from '../types.js'\n\n/**\n * Read and parse plugin metadata from plugin.json\n *\n * Only reads plugin-specific configuration fields:\n * - targets: where the plugin can be installed\n * - pluginDependencies: other plugins this plugin requires\n * - libraryDependencies: npm packages to inject into target\n *\n * Name and version are read from package.json during resolution.\n *\n * @param pluginPath - Absolute path to the plugin directory\n * @returns Parsed plugin metadata\n */\nexport async function readPluginMetadata(pluginPath: string): Promise<PluginMetadata> {\n const metadataPath = path.join(pluginPath, 'plugin.json')\n const content = await fs.readFile(metadataPath, 'utf-8')\n const parsed = JSON.parse(content)\n\n // Extract only plugin-specific fields, ignoring name/version/description\n return {\n targets: parsed.targets || [],\n pluginDependencies: parsed.pluginDependencies,\n libraryDependencies: parsed.libraryDependencies,\n }\n}\n\n/**\n * Read and parse template metadata from template.json\n *\n * Currently template.json contains no fields - it exists as a placeholder\n * for future template-specific configuration (e.g., deployment metadata).\n *\n * Template name and version are read from package.json during resolution.\n *\n * @param templatePath - Absolute path to the template directory\n * @returns Template metadata (currently empty)\n */\nexport async function readTemplateMetadata(templatePath: string): Promise<TemplateMetadata> {\n const templateJsonPath = path.join(templatePath, 'template.json')\n try {\n const content = await fs.readFile(templateJsonPath, 'utf-8')\n return JSON.parse(content) as TemplateMetadata\n } catch {\n // File doesn't exist or can't be read - return empty metadata\n return {}\n }\n}\n","import * as path from 'path'\n\nimport { parseLocationFromPath } from './location-parser.js'\nimport { findWorkspaceRoot, readWorkspaceManifest } from './manifest.js'\n\nimport type { Launch77Context } from './types.js'\n\n/**\n * Detect the Launch77 workspace context from the current working directory\n *\n * @param cwd - Current working directory path\n * @returns Context information about the workspace location\n */\nexport async function detectLaunch77Context(cwd: string): Promise<Launch77Context> {\n const resolvedCwd = path.resolve(cwd)\n\n // Find the workspace root\n const workspaceRoot = await findWorkspaceRoot(resolvedCwd)\n\n if (!workspaceRoot) {\n return {\n isValid: false,\n locationType: 'unknown',\n workspaceRoot: '',\n appsDir: '',\n workspaceVersion: '',\n workspaceName: '',\n packageName: '',\n }\n }\n\n // Read workspace manifest\n let manifest\n try {\n manifest = await readWorkspaceManifest(workspaceRoot)\n } catch (error) {\n // Manifest exists (we found it) but couldn't read it\n return {\n isValid: false,\n locationType: 'unknown',\n workspaceRoot: '',\n appsDir: '',\n workspaceVersion: '',\n workspaceName: '',\n packageName: '',\n }\n }\n\n // Parse the directory structure to determine location\n const parsed = parseLocationFromPath(resolvedCwd, workspaceRoot)\n\n // Workspace name is the directory name\n const workspaceName = path.basename(workspaceRoot)\n\n // Apps directory is always {workspaceRoot}/apps\n const appsDir = path.join(workspaceRoot, 'apps')\n\n // Package name: @{workspaceName}/{appName} (if in app)\n const packageName = parsed.appName ? `@${workspaceName}/${parsed.appName}` : ''\n\n return {\n isValid: true,\n locationType: parsed.locationType,\n workspaceRoot,\n appsDir,\n workspaceVersion: manifest.version,\n workspaceName,\n appName: parsed.appName,\n packageName,\n }\n}\n\n// Re-export types for convenience\nexport type { Launch77Context, Launch77LocationType, WorkspaceManifest, ParsedLocation } from './types.js'\n","import * as path from 'path'\n\nimport type { ParsedLocation } from './types.js'\n\n/**\n * Parse the directory structure to determine location context\n *\n * Based on patterns:\n * - apps/[name] → workspace-app\n * - libraries/[name] → workspace-library\n * - plugins/[name] → workspace-plugin\n * - app-templates/[name] → workspace-app-template\n * - (empty or root) → workspace-root\n *\n * @param cwdPath - Current working directory path\n * @param workspaceRoot - Root path of the workspace\n * @returns Parsed location information\n */\nexport function parseLocationFromPath(cwdPath: string, workspaceRoot: string): ParsedLocation {\n const relativePath = path.relative(workspaceRoot, cwdPath)\n\n // At workspace root\n if (!relativePath || relativePath === '.') {\n return { locationType: 'workspace-root' }\n }\n\n const parts = relativePath.split(path.sep)\n\n // apps/[app-name]/...\n if (parts[0] === 'apps' && parts.length >= 2) {\n return {\n locationType: 'workspace-app',\n appName: parts[1],\n }\n }\n\n // libraries/[lib-name]/...\n if (parts[0] === 'libraries' && parts.length >= 2) {\n return {\n locationType: 'workspace-library',\n appName: parts[1],\n }\n }\n\n // plugins/[plugin-name]/...\n if (parts[0] === 'plugins' && parts.length >= 2) {\n return {\n locationType: 'workspace-plugin',\n appName: parts[1],\n }\n }\n\n // app-templates/[template-name]/...\n if (parts[0] === 'app-templates' && parts.length >= 2) {\n return {\n locationType: 'workspace-app-template',\n appName: parts[1],\n }\n }\n\n // Somewhere else in workspace\n return { locationType: 'workspace-root' }\n}\n","import * as path from 'path'\n\nimport fs from 'fs-extra'\n\nimport type { WorkspaceManifest } from './types.js'\n\nconst WORKSPACE_MANIFEST = '.launch77/workspace.json'\n\n/**\n * Check if a directory contains a Launch77 workspace manifest\n */\nexport async function isWorkspaceRoot(dir: string): Promise<boolean> {\n const manifestPath = path.join(dir, WORKSPACE_MANIFEST)\n return await fs.pathExists(manifestPath)\n}\n\n/**\n * Read workspace manifest from a workspace root directory\n */\nexport async function readWorkspaceManifest(workspaceRoot: string): Promise<WorkspaceManifest> {\n const manifestPath = path.join(workspaceRoot, WORKSPACE_MANIFEST)\n return await fs.readJSON(manifestPath)\n}\n\n/**\n * Find the workspace root by traversing up from a starting directory\n * Looks for .launch77/workspace.json manifest file\n *\n * @param startDir - Directory to start searching from\n * @returns Path to workspace root, or null if not found\n */\nexport async function findWorkspaceRoot(startDir: string): Promise<string | null> {\n let currentDir = path.resolve(startDir)\n const rootDir = path.parse(currentDir).root\n\n while (currentDir !== rootDir) {\n if (await isWorkspaceRoot(currentDir)) {\n return currentDir\n }\n currentDir = path.dirname(currentDir)\n }\n\n return null\n}\n","/**\n * Name Validation Utilities\n *\n * Provides validation for plugin names and npm package names.\n * Used by both CLI and plugin-runtime to ensure consistent naming rules.\n */\n\nexport interface ValidationResult {\n isValid: boolean\n error?: string\n}\n\n/**\n * Validate a plugin name\n *\n * Rules:\n * - Must start with a lowercase letter\n * - Can contain lowercase letters, numbers, and hyphens\n * - Cannot contain uppercase, spaces, underscores, or special characters\n *\n * @param name - The plugin name to validate\n * @returns ValidationResult indicating if valid and error message if not\n *\n * @example\n * validatePluginName('my-plugin') // { isValid: true }\n * validatePluginName('MyPlugin') // { isValid: false, error: '...' }\n */\nexport function validatePluginName(name: string): ValidationResult {\n if (!name || name.trim().length === 0) {\n return {\n isValid: false,\n error: 'Plugin name cannot be empty',\n }\n }\n\n const trimmedName = name.trim()\n\n // Must start with a lowercase letter\n if (!/^[a-z]/.test(trimmedName)) {\n return {\n isValid: false,\n error: 'Plugin name must start with a lowercase letter (a-z)',\n }\n }\n\n // Can only contain lowercase letters, numbers, and hyphens\n if (!/^[a-z][a-z0-9-]*$/.test(trimmedName)) {\n return {\n isValid: false,\n error: 'Plugin name can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-)',\n }\n }\n\n return { isValid: true }\n}\n\n/**\n * Validate an npm package name (scoped or unscoped)\n *\n * Rules for unscoped packages:\n * - Must start with a lowercase letter\n * - Can contain lowercase letters, numbers, and hyphens\n *\n * Rules for scoped packages:\n * - Format: @org/package\n * - Org must contain lowercase letters, numbers, and hyphens\n * - Package must contain lowercase letters, numbers, and hyphens\n *\n * @param name - The npm package name to validate\n * @returns ValidationResult indicating if valid and error message if not\n *\n * @example\n * isValidNpmPackageName('my-package') // { isValid: true }\n * isValidNpmPackageName('@org/my-package') // { isValid: true }\n * isValidNpmPackageName('@release') // { isValid: false, error: '...' }\n */\nexport function isValidNpmPackageName(name: string): ValidationResult {\n if (!name || name.trim().length === 0) {\n return {\n isValid: false,\n error: 'Package name cannot be empty',\n }\n }\n\n const trimmedName = name.trim()\n\n // Check if it's a scoped package\n if (trimmedName.startsWith('@')) {\n // Scoped package format: @org/package\n const scopedPattern = /^@([a-z0-9-]+)\\/([a-z0-9-]+)$/\n\n if (!scopedPattern.test(trimmedName)) {\n return {\n isValid: false,\n error: 'Scoped package must be in format @org/package where org and package contain only lowercase letters, numbers, and hyphens',\n }\n }\n\n return { isValid: true }\n }\n\n // Unscoped package - same rules as plugin/workspace names\n if (!/^[a-z][a-z0-9-]*$/.test(trimmedName)) {\n return {\n isValid: false,\n error: 'Package name must start with a lowercase letter and contain only lowercase letters (a-z), numbers (0-9), and hyphens (-)',\n }\n }\n\n return { isValid: true }\n}\n\n/**\n * Parse a plugin name input and determine its type\n *\n * Returns information about whether the input is:\n * - A scoped npm package (e.g., @org/package)\n * - An unscoped name (e.g., my-plugin)\n * - Invalid\n *\n * @param name - The plugin name to parse\n * @returns Object with type and validation info\n *\n * @example\n * parsePluginName('release')\n * // { type: 'unscoped', isValid: true, name: 'release' }\n *\n * parsePluginName('@ibm/analytics')\n * // { type: 'scoped', isValid: true, name: '@ibm/analytics', org: 'ibm', package: 'analytics' }\n *\n * parsePluginName('@release')\n * // { type: 'invalid', isValid: false, error: '...' }\n */\nexport function parsePluginName(name: string): {\n type: 'scoped' | 'unscoped' | 'invalid'\n isValid: boolean\n name?: string\n org?: string\n package?: string\n error?: string\n} {\n if (!name || name.trim().length === 0) {\n return {\n type: 'invalid',\n isValid: false,\n error: 'Plugin name cannot be empty',\n }\n }\n\n const trimmedName = name.trim()\n\n // Check if scoped\n if (trimmedName.startsWith('@')) {\n const validation = isValidNpmPackageName(trimmedName)\n\n if (!validation.isValid) {\n return {\n type: 'invalid',\n isValid: false,\n error: validation.error,\n }\n }\n\n // Extract org and package from @org/package\n const match = trimmedName.match(/^@([a-z0-9-]+)\\/([a-z0-9-]+)$/)\n if (match) {\n return {\n type: 'scoped',\n isValid: true,\n name: trimmedName,\n org: match[1],\n package: match[2],\n }\n }\n\n return {\n type: 'invalid',\n isValid: false,\n error: 'Invalid scoped package format',\n }\n }\n\n // Unscoped - validate as plugin name\n const validation = validatePluginName(trimmedName)\n\n if (!validation.isValid) {\n return {\n type: 'invalid',\n isValid: false,\n error: validation.error,\n }\n }\n\n return {\n type: 'unscoped',\n isValid: true,\n name: trimmedName,\n }\n}\n","import * as fs from 'fs/promises'\nimport * as path from 'path'\n\nimport chalk from 'chalk'\n\ninterface PackageJson {\n name?: string\n dependencies?: Record<string, string>\n}\n\ninterface PluginJson {\n libraryDependencies?: Record<string, string>\n}\n\nexport interface PluginConsistencyError {\n library: string\n packageJsonVersion: string | 'MISSING'\n pluginJsonVersion: string\n}\n\nexport interface ValidationResult {\n valid: boolean\n errors: PluginConsistencyError[]\n}\n\n/**\n * Validate that library versions in plugin.json match those in package.json\n *\n * This ensures that plugin templates (which use package.json versions during development)\n * install the same library versions for end users (which use plugin.json versions).\n *\n * @param pluginPath - Absolute path to the plugin directory\n * @returns Validation result with any errors found\n * @throws Error if package.json or plugin.json cannot be read\n */\nexport async function validatePluginConsistency(pluginPath: string): Promise<ValidationResult> {\n // Read package.json\n const packageJsonPath = path.join(pluginPath, 'package.json')\n const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8')\n const packageJson: PackageJson = JSON.parse(packageJsonContent)\n\n // Read plugin.json\n const pluginJsonPath = path.join(pluginPath, 'plugin.json')\n const pluginJsonContent = await fs.readFile(pluginJsonPath, 'utf-8')\n const pluginJson: PluginJson = JSON.parse(pluginJsonContent)\n\n const errors: PluginConsistencyError[] = []\n\n // Check each library dependency in plugin.json\n if (pluginJson.libraryDependencies) {\n for (const [library, pluginJsonVersion] of Object.entries(pluginJson.libraryDependencies)) {\n const packageJsonVersion = packageJson.dependencies?.[library]\n\n if (!packageJsonVersion) {\n // Library is in plugin.json but NOT in package.json\n errors.push({\n library,\n packageJsonVersion: 'MISSING',\n pluginJsonVersion,\n })\n } else if (packageJsonVersion !== pluginJsonVersion) {\n // Library exists in both but versions don't match\n errors.push({\n library,\n packageJsonVersion,\n pluginJsonVersion,\n })\n }\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n }\n}\n\n/**\n * Validate plugin consistency and throw a formatted error if validation fails\n *\n * This is a convenience function for use in build scripts that should\n * fail fast with a clear error message.\n *\n * @param pluginPath - Absolute path to the plugin directory\n * @throws Error with formatted message if validation fails\n */\nexport async function validatePluginConsistencyOrThrow(pluginPath: string): Promise<void> {\n const result = await validatePluginConsistency(pluginPath)\n\n if (!result.valid) {\n // Read plugin name for error message\n const packageJsonPath = path.join(pluginPath, 'package.json')\n const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8')\n const packageJson: PackageJson = JSON.parse(packageJsonContent)\n const pluginName = packageJson.name || 'unknown'\n\n console.error(chalk.red('\\n❌ Plugin consistency validation failed!\\n'))\n console.error(chalk.yellow(`Plugin: ${pluginName}\\n`))\n console.error(chalk.white('Library versions in package.json must match plugin.json:\\n'))\n\n for (const error of result.errors) {\n console.error(chalk.white(` ${error.library}:`))\n if (error.packageJsonVersion === 'MISSING') {\n console.error(chalk.red(` ✗ package.json: MISSING (library not in dependencies)`))\n console.error(chalk.red(` ✗ plugin.json: ${error.pluginJsonVersion}`))\n } else {\n console.error(chalk.red(` ✗ package.json: ${error.packageJsonVersion}`))\n console.error(chalk.red(` ✗ plugin.json: ${error.pluginJsonVersion}`))\n }\n console.error()\n }\n\n console.error(chalk.white('Why this matters:'))\n console.error(chalk.white(' - Plugin templates use package.json versions during development'))\n console.error(chalk.white(' - End users get plugin.json versions when they install the plugin'))\n console.error(chalk.white(' - Mismatched versions cause template code to break for users\\n'))\n\n console.error(chalk.cyan('Fix: Update both files to use the same version.\\n'))\n\n throw new Error('Plugin validation failed - library versions do not match')\n }\n}\n"],"mappings":"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","names":["path","fs","stat","fs","path","path","path","path","fs","validation","fs","path","chalk"]}
|