@launch77/plugin-runtime 0.2.0 → 0.2.1

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
@@ -3,6 +3,7 @@ interface GeneratorContext {
3
3
  appName: string;
4
4
  workspaceName: string;
5
5
  pluginPath: string;
6
+ pluginInstallationInfo?: PluginInstallationInfo;
6
7
  }
7
8
  interface PluginMetadata {
8
9
  name: string;
@@ -11,6 +12,21 @@ interface PluginMetadata {
11
12
  pluginDependencies?: Record<string, string>;
12
13
  libraryDependencies?: Record<string, string>;
13
14
  }
15
+ interface PluginInstallationInfo {
16
+ pluginName: string;
17
+ packageName: string;
18
+ version: string;
19
+ source: 'local' | 'npm';
20
+ }
21
+ interface InstalledPluginMetadata {
22
+ package: string;
23
+ version: string;
24
+ installedAt: string;
25
+ source: 'local' | 'npm';
26
+ }
27
+ interface Launch77PackageManifest {
28
+ installedPlugins?: Record<string, InstalledPluginMetadata>;
29
+ }
14
30
 
15
31
  /**
16
32
  * Base abstract class for all plugin generators.
@@ -49,6 +65,7 @@ declare abstract class StandardGenerator extends Generator {
49
65
  protected installDependencies(): Promise<void>;
50
66
  protected copyTemplates(): Promise<void>;
51
67
  protected injectCode(): Promise<void>;
68
+ protected writePluginManifest(): Promise<void>;
52
69
  protected showNextSteps(): void;
53
70
  }
54
71
 
@@ -165,4 +182,4 @@ declare function parsePluginName(name: string): {
165
182
  error?: string;
166
183
  };
167
184
 
168
- export { Generator, type GeneratorContext, type Launch77Context, type Launch77LocationType, type PluginMetadata, StandardGenerator, type ValidationResult, copyRecursive, detectLaunch77Context, isValidNpmPackageName, parsePluginName, pathExists, readPluginMetadata, validatePluginName };
185
+ export { Generator, type GeneratorContext, type InstalledPluginMetadata, type Launch77Context, type Launch77LocationType, type Launch77PackageManifest, type PluginInstallationInfo, type PluginMetadata, StandardGenerator, type ValidationResult, copyRecursive, detectLaunch77Context, isValidNpmPackageName, parsePluginName, pathExists, readPluginMetadata, validatePluginName };
package/dist/index.js CHANGED
@@ -54,6 +54,7 @@ var StandardGenerator = class extends Generator {
54
54
  await this.installDependencies();
55
55
  await this.copyTemplates();
56
56
  await this.injectCode();
57
+ await this.writePluginManifest();
57
58
  console.log(chalk.green(`
58
59
  \u2705 Plugin installed successfully!
59
60
  `));
@@ -105,6 +106,34 @@ var StandardGenerator = class extends Generator {
105
106
  }
106
107
  async injectCode() {
107
108
  }
109
+ async writePluginManifest() {
110
+ if (!this.context.pluginInstallationInfo) {
111
+ console.log(chalk.yellow(" \u26A0\uFE0F No plugin installation info provided, skipping manifest"));
112
+ return;
113
+ }
114
+ try {
115
+ const packageJsonPath = path3.join(this.context.appPath, "package.json");
116
+ const packageJsonContent = await fs3.readFile(packageJsonPath, "utf-8");
117
+ const packageJson = JSON.parse(packageJsonContent);
118
+ if (!packageJson.launch77) {
119
+ packageJson.launch77 = {};
120
+ }
121
+ if (!packageJson.launch77.installedPlugins) {
122
+ packageJson.launch77.installedPlugins = {};
123
+ }
124
+ const manifest = packageJson.launch77;
125
+ manifest.installedPlugins[this.context.pluginInstallationInfo.pluginName] = {
126
+ package: this.context.pluginInstallationInfo.packageName,
127
+ version: this.context.pluginInstallationInfo.version,
128
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
129
+ source: this.context.pluginInstallationInfo.source
130
+ };
131
+ await fs3.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
132
+ console.log(chalk.green(" \u2713 Updated plugin manifest"));
133
+ } catch (error) {
134
+ console.log(chalk.yellow(` \u26A0\uFE0F Could not update plugin manifest: ${error}`));
135
+ }
136
+ }
108
137
  showNextSteps() {
109
138
  }
110
139
  };
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'\nimport chalk from 'chalk'\nimport { execa } from 'execa'\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 } from '../types.js'\n\n/**\n * Read and parse plugin metadata from plugin.json\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 return JSON.parse(content) as PluginMetadata\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"],"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, Launch77PackageManifest } 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 await this.writePluginManifest()\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 async writePluginManifest(): Promise<void> {\n if (!this.context.pluginInstallationInfo) {\n console.log(chalk.yellow(' ⚠️ No plugin installation info provided, skipping manifest'))\n return\n }\n\n try {\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 // Initialize launch77 field if it doesn't exist\n if (!packageJson.launch77) {\n packageJson.launch77 = {}\n }\n\n // Initialize installedPlugins if it doesn't exist\n if (!packageJson.launch77.installedPlugins) {\n packageJson.launch77.installedPlugins = {}\n }\n\n const manifest = packageJson.launch77 as Launch77PackageManifest\n\n // Add plugin installation record\n manifest.installedPlugins![this.context.pluginInstallationInfo.pluginName] = {\n package: this.context.pluginInstallationInfo.packageName,\n version: this.context.pluginInstallationInfo.version,\n installedAt: new Date().toISOString(),\n source: this.context.pluginInstallationInfo.source,\n }\n\n await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\\n', 'utf-8')\n console.log(chalk.green(' ✓ Updated plugin manifest'))\n } catch (error) {\n console.log(chalk.yellow(` ⚠️ Could not update plugin manifest: ${error}`))\n }\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 } from '../types.js'\n\n/**\n * Read and parse plugin metadata from plugin.json\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 return JSON.parse(content) as PluginMetadata\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launch77/plugin-runtime",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",