@launch77/plugin-runtime 0.1.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,13 +3,30 @@ 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;
9
10
  version: string;
11
+ targets: string[];
10
12
  pluginDependencies?: Record<string, string>;
11
13
  libraryDependencies?: Record<string, string>;
12
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
+ }
13
30
 
14
31
  /**
15
32
  * Base abstract class for all plugin generators.
@@ -48,10 +65,121 @@ declare abstract class StandardGenerator extends Generator {
48
65
  protected installDependencies(): Promise<void>;
49
66
  protected copyTemplates(): Promise<void>;
50
67
  protected injectCode(): Promise<void>;
68
+ protected writePluginManifest(): Promise<void>;
51
69
  protected showNextSteps(): void;
52
70
  }
53
71
 
54
72
  declare function copyRecursive(src: string, dest: string): Promise<void>;
55
73
  declare function pathExists(filePath: string): Promise<boolean>;
56
74
 
57
- export { Generator, type GeneratorContext, type PluginMetadata, StandardGenerator, copyRecursive, pathExists };
75
+ /**
76
+ * Read and parse plugin metadata from plugin.json
77
+ * @param pluginPath - Absolute path to the plugin directory
78
+ * @returns Parsed plugin metadata
79
+ */
80
+ declare function readPluginMetadata(pluginPath: string): Promise<PluginMetadata>;
81
+
82
+ /**
83
+ * Type of location within a Launch77 workspace
84
+ */
85
+ type Launch77LocationType = 'workspace-root' | 'workspace-app' | 'workspace-library' | 'workspace-plugin' | 'workspace-app-template' | 'unknown';
86
+ /**
87
+ * Context information about the current location within a Launch77 workspace
88
+ */
89
+ interface Launch77Context {
90
+ isValid: boolean;
91
+ locationType: Launch77LocationType;
92
+ workspaceRoot: string;
93
+ appsDir: string;
94
+ workspaceVersion: string;
95
+ workspaceName: string;
96
+ appName?: string;
97
+ packageName: string;
98
+ }
99
+
100
+ /**
101
+ * Detect the Launch77 workspace context from the current working directory
102
+ *
103
+ * @param cwd - Current working directory path
104
+ * @returns Context information about the workspace location
105
+ */
106
+ declare function detectLaunch77Context(cwd: string): Promise<Launch77Context>;
107
+
108
+ /**
109
+ * Name Validation Utilities
110
+ *
111
+ * Provides validation for plugin names and npm package names.
112
+ * Used by both CLI and plugin-runtime to ensure consistent naming rules.
113
+ */
114
+ interface ValidationResult {
115
+ isValid: boolean;
116
+ error?: string;
117
+ }
118
+ /**
119
+ * Validate a plugin name
120
+ *
121
+ * Rules:
122
+ * - Must start with a lowercase letter
123
+ * - Can contain lowercase letters, numbers, and hyphens
124
+ * - Cannot contain uppercase, spaces, underscores, or special characters
125
+ *
126
+ * @param name - The plugin name to validate
127
+ * @returns ValidationResult indicating if valid and error message if not
128
+ *
129
+ * @example
130
+ * validatePluginName('my-plugin') // { isValid: true }
131
+ * validatePluginName('MyPlugin') // { isValid: false, error: '...' }
132
+ */
133
+ declare function validatePluginName(name: string): ValidationResult;
134
+ /**
135
+ * Validate an npm package name (scoped or unscoped)
136
+ *
137
+ * Rules for unscoped packages:
138
+ * - Must start with a lowercase letter
139
+ * - Can contain lowercase letters, numbers, and hyphens
140
+ *
141
+ * Rules for scoped packages:
142
+ * - Format: @org/package
143
+ * - Org must contain lowercase letters, numbers, and hyphens
144
+ * - Package must contain lowercase letters, numbers, and hyphens
145
+ *
146
+ * @param name - The npm package name to validate
147
+ * @returns ValidationResult indicating if valid and error message if not
148
+ *
149
+ * @example
150
+ * isValidNpmPackageName('my-package') // { isValid: true }
151
+ * isValidNpmPackageName('@org/my-package') // { isValid: true }
152
+ * isValidNpmPackageName('@release') // { isValid: false, error: '...' }
153
+ */
154
+ declare function isValidNpmPackageName(name: string): ValidationResult;
155
+ /**
156
+ * Parse a plugin name input and determine its type
157
+ *
158
+ * Returns information about whether the input is:
159
+ * - A scoped npm package (e.g., @org/package)
160
+ * - An unscoped name (e.g., my-plugin)
161
+ * - Invalid
162
+ *
163
+ * @param name - The plugin name to parse
164
+ * @returns Object with type and validation info
165
+ *
166
+ * @example
167
+ * parsePluginName('release')
168
+ * // { type: 'unscoped', isValid: true, name: 'release' }
169
+ *
170
+ * parsePluginName('@ibm/analytics')
171
+ * // { type: 'scoped', isValid: true, name: '@ibm/analytics', org: 'ibm', package: 'analytics' }
172
+ *
173
+ * parsePluginName('@release')
174
+ * // { type: 'invalid', isValid: false, error: '...' }
175
+ */
176
+ declare function parsePluginName(name: string): {
177
+ type: 'scoped' | 'unscoped' | 'invalid';
178
+ isValid: boolean;
179
+ name?: string;
180
+ org?: string;
181
+ package?: string;
182
+ error?: string;
183
+ };
184
+
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
@@ -6,8 +6,8 @@ var Generator = class {
6
6
  };
7
7
 
8
8
  // src/standard-generator.ts
9
- import * as path2 from "path";
10
- import * as fs2 from "fs/promises";
9
+ import * as path3 from "path";
10
+ import * as fs3 from "fs/promises";
11
11
  import chalk from "chalk";
12
12
  import { execa } from "execa";
13
13
 
@@ -35,6 +35,15 @@ async function pathExists(filePath) {
35
35
  }
36
36
  }
37
37
 
38
+ // src/utils/metadata.ts
39
+ import * as fs2 from "fs/promises";
40
+ import * as path2 from "path";
41
+ async function readPluginMetadata(pluginPath) {
42
+ const metadataPath = path2.join(pluginPath, "plugin.json");
43
+ const content = await fs2.readFile(metadataPath, "utf-8");
44
+ return JSON.parse(content);
45
+ }
46
+
38
47
  // src/standard-generator.ts
39
48
  var StandardGenerator = class extends Generator {
40
49
  async run() {
@@ -45,28 +54,28 @@ var StandardGenerator = class extends Generator {
45
54
  await this.installDependencies();
46
55
  await this.copyTemplates();
47
56
  await this.injectCode();
57
+ await this.writePluginManifest();
48
58
  console.log(chalk.green(`
49
59
  \u2705 Plugin installed successfully!
50
60
  `));
51
61
  this.showNextSteps();
52
62
  }
53
63
  async updateDependencies() {
54
- const pluginJsonPath = path2.join(this.context.pluginPath, "plugin.json");
64
+ const pluginJsonPath = path3.join(this.context.pluginPath, "plugin.json");
55
65
  if (!await pathExists(pluginJsonPath)) return;
56
66
  try {
57
- const pluginJsonContent = await fs2.readFile(pluginJsonPath, "utf-8");
58
- const pluginMetadata = JSON.parse(pluginJsonContent);
67
+ const pluginMetadata = await readPluginMetadata(this.context.pluginPath);
59
68
  if (!pluginMetadata.libraryDependencies || Object.keys(pluginMetadata.libraryDependencies).length === 0) {
60
69
  return;
61
70
  }
62
- const packageJsonPath = path2.join(this.context.appPath, "package.json");
63
- const packageJsonContent = await fs2.readFile(packageJsonPath, "utf-8");
71
+ const packageJsonPath = path3.join(this.context.appPath, "package.json");
72
+ const packageJsonContent = await fs3.readFile(packageJsonPath, "utf-8");
64
73
  const packageJson = JSON.parse(packageJsonContent);
65
74
  packageJson.dependencies = {
66
75
  ...packageJson.dependencies,
67
76
  ...pluginMetadata.libraryDependencies
68
77
  };
69
- await fs2.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
78
+ await fs3.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
70
79
  console.log(chalk.green(" \u2713 Updated package.json with dependencies"));
71
80
  } catch (error) {
72
81
  console.log(chalk.yellow(` \u26A0\uFE0F Could not update dependencies: ${error}`));
@@ -75,7 +84,7 @@ var StandardGenerator = class extends Generator {
75
84
  async installDependencies() {
76
85
  try {
77
86
  console.log(chalk.cyan(" Installing dependencies..."));
78
- const workspaceRoot = path2.dirname(path2.dirname(this.context.appPath));
87
+ const workspaceRoot = path3.dirname(path3.dirname(this.context.appPath));
79
88
  await execa("npm", ["install"], {
80
89
  cwd: workspaceRoot,
81
90
  stdio: "pipe"
@@ -86,7 +95,7 @@ var StandardGenerator = class extends Generator {
86
95
  }
87
96
  }
88
97
  async copyTemplates() {
89
- const templatesDir = path2.join(this.context.pluginPath, "templates");
98
+ const templatesDir = path3.join(this.context.pluginPath, "templates");
90
99
  if (!await pathExists(templatesDir)) return;
91
100
  try {
92
101
  await copyRecursive(templatesDir, this.context.appPath);
@@ -97,13 +106,251 @@ var StandardGenerator = class extends Generator {
97
106
  }
98
107
  async injectCode() {
99
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
+ }
100
137
  showNextSteps() {
101
138
  }
102
139
  };
140
+
141
+ // src/context/index.ts
142
+ import * as path6 from "path";
143
+
144
+ // src/context/location-parser.ts
145
+ import * as path4 from "path";
146
+ function parseLocationFromPath(cwdPath, workspaceRoot) {
147
+ const relativePath = path4.relative(workspaceRoot, cwdPath);
148
+ if (!relativePath || relativePath === ".") {
149
+ return { locationType: "workspace-root" };
150
+ }
151
+ const parts = relativePath.split(path4.sep);
152
+ if (parts[0] === "apps" && parts.length >= 2) {
153
+ return {
154
+ locationType: "workspace-app",
155
+ appName: parts[1]
156
+ };
157
+ }
158
+ if (parts[0] === "libraries" && parts.length >= 2) {
159
+ return {
160
+ locationType: "workspace-library",
161
+ appName: parts[1]
162
+ };
163
+ }
164
+ if (parts[0] === "plugins" && parts.length >= 2) {
165
+ return {
166
+ locationType: "workspace-plugin",
167
+ appName: parts[1]
168
+ };
169
+ }
170
+ if (parts[0] === "app-templates" && parts.length >= 2) {
171
+ return {
172
+ locationType: "workspace-app-template",
173
+ appName: parts[1]
174
+ };
175
+ }
176
+ return { locationType: "workspace-root" };
177
+ }
178
+
179
+ // src/context/manifest.ts
180
+ import * as path5 from "path";
181
+ import fs4 from "fs-extra";
182
+ var WORKSPACE_MANIFEST = ".launch77/workspace.json";
183
+ async function isWorkspaceRoot(dir) {
184
+ const manifestPath = path5.join(dir, WORKSPACE_MANIFEST);
185
+ return await fs4.pathExists(manifestPath);
186
+ }
187
+ async function readWorkspaceManifest(workspaceRoot) {
188
+ const manifestPath = path5.join(workspaceRoot, WORKSPACE_MANIFEST);
189
+ return await fs4.readJSON(manifestPath);
190
+ }
191
+ async function findWorkspaceRoot(startDir) {
192
+ let currentDir = path5.resolve(startDir);
193
+ const rootDir = path5.parse(currentDir).root;
194
+ while (currentDir !== rootDir) {
195
+ if (await isWorkspaceRoot(currentDir)) {
196
+ return currentDir;
197
+ }
198
+ currentDir = path5.dirname(currentDir);
199
+ }
200
+ return null;
201
+ }
202
+
203
+ // src/context/index.ts
204
+ async function detectLaunch77Context(cwd) {
205
+ const resolvedCwd = path6.resolve(cwd);
206
+ const workspaceRoot = await findWorkspaceRoot(resolvedCwd);
207
+ if (!workspaceRoot) {
208
+ return {
209
+ isValid: false,
210
+ locationType: "unknown",
211
+ workspaceRoot: "",
212
+ appsDir: "",
213
+ workspaceVersion: "",
214
+ workspaceName: "",
215
+ packageName: ""
216
+ };
217
+ }
218
+ let manifest;
219
+ try {
220
+ manifest = await readWorkspaceManifest(workspaceRoot);
221
+ } catch (error) {
222
+ return {
223
+ isValid: false,
224
+ locationType: "unknown",
225
+ workspaceRoot: "",
226
+ appsDir: "",
227
+ workspaceVersion: "",
228
+ workspaceName: "",
229
+ packageName: ""
230
+ };
231
+ }
232
+ const parsed = parseLocationFromPath(resolvedCwd, workspaceRoot);
233
+ const workspaceName = path6.basename(workspaceRoot);
234
+ const appsDir = path6.join(workspaceRoot, "apps");
235
+ const packageName = parsed.appName ? `@${workspaceName}/${parsed.appName}` : "";
236
+ return {
237
+ isValid: true,
238
+ locationType: parsed.locationType,
239
+ workspaceRoot,
240
+ appsDir,
241
+ workspaceVersion: manifest.version,
242
+ workspaceName,
243
+ appName: parsed.appName,
244
+ packageName
245
+ };
246
+ }
247
+
248
+ // src/utils/name-validation.ts
249
+ function validatePluginName(name) {
250
+ if (!name || name.trim().length === 0) {
251
+ return {
252
+ isValid: false,
253
+ error: "Plugin name cannot be empty"
254
+ };
255
+ }
256
+ const trimmedName = name.trim();
257
+ if (!/^[a-z]/.test(trimmedName)) {
258
+ return {
259
+ isValid: false,
260
+ error: "Plugin name must start with a lowercase letter (a-z)"
261
+ };
262
+ }
263
+ if (!/^[a-z][a-z0-9-]*$/.test(trimmedName)) {
264
+ return {
265
+ isValid: false,
266
+ error: "Plugin name can only contain lowercase letters (a-z), numbers (0-9), and hyphens (-)"
267
+ };
268
+ }
269
+ return { isValid: true };
270
+ }
271
+ function isValidNpmPackageName(name) {
272
+ if (!name || name.trim().length === 0) {
273
+ return {
274
+ isValid: false,
275
+ error: "Package name cannot be empty"
276
+ };
277
+ }
278
+ const trimmedName = name.trim();
279
+ if (trimmedName.startsWith("@")) {
280
+ const scopedPattern = /^@([a-z0-9-]+)\/([a-z0-9-]+)$/;
281
+ if (!scopedPattern.test(trimmedName)) {
282
+ return {
283
+ isValid: false,
284
+ error: "Scoped package must be in format @org/package where org and package contain only lowercase letters, numbers, and hyphens"
285
+ };
286
+ }
287
+ return { isValid: true };
288
+ }
289
+ if (!/^[a-z][a-z0-9-]*$/.test(trimmedName)) {
290
+ return {
291
+ isValid: false,
292
+ error: "Package name must start with a lowercase letter and contain only lowercase letters (a-z), numbers (0-9), and hyphens (-)"
293
+ };
294
+ }
295
+ return { isValid: true };
296
+ }
297
+ function parsePluginName(name) {
298
+ if (!name || name.trim().length === 0) {
299
+ return {
300
+ type: "invalid",
301
+ isValid: false,
302
+ error: "Plugin name cannot be empty"
303
+ };
304
+ }
305
+ const trimmedName = name.trim();
306
+ if (trimmedName.startsWith("@")) {
307
+ const validation2 = isValidNpmPackageName(trimmedName);
308
+ if (!validation2.isValid) {
309
+ return {
310
+ type: "invalid",
311
+ isValid: false,
312
+ error: validation2.error
313
+ };
314
+ }
315
+ const match = trimmedName.match(/^@([a-z0-9-]+)\/([a-z0-9-]+)$/);
316
+ if (match) {
317
+ return {
318
+ type: "scoped",
319
+ isValid: true,
320
+ name: trimmedName,
321
+ org: match[1],
322
+ package: match[2]
323
+ };
324
+ }
325
+ return {
326
+ type: "invalid",
327
+ isValid: false,
328
+ error: "Invalid scoped package format"
329
+ };
330
+ }
331
+ const validation = validatePluginName(trimmedName);
332
+ if (!validation.isValid) {
333
+ return {
334
+ type: "invalid",
335
+ isValid: false,
336
+ error: validation.error
337
+ };
338
+ }
339
+ return {
340
+ type: "unscoped",
341
+ isValid: true,
342
+ name: trimmedName
343
+ };
344
+ }
103
345
  export {
104
346
  Generator,
105
347
  StandardGenerator,
106
348
  copyRecursive,
107
- pathExists
349
+ detectLaunch77Context,
350
+ isValidNpmPackageName,
351
+ parsePluginName,
352
+ pathExists,
353
+ readPluginMetadata,
354
+ validatePluginName
108
355
  };
109
356
  //# 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"],"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 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 pluginJsonContent = await fs.readFile(pluginJsonPath, 'utf-8')\n const pluginMetadata: PluginMetadata = JSON.parse(pluginJsonContent)\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"],"mappings":"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","names":["path","fs","stat"]}
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.1.0",
3
+ "version": "0.2.1",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,9 @@
20
20
  "scripts": {
21
21
  "build": "tsup",
22
22
  "typecheck": "tsc --noEmit",
23
- "lint": "eslint src/**/*.ts"
23
+ "lint": "eslint src/**/*.ts",
24
+ "test": "vitest run",
25
+ "test:integration": "vitest run --config vitest.integration.config.ts"
24
26
  },
25
27
  "dependencies": {
26
28
  "chalk": "^5.3.0",
@@ -31,6 +33,7 @@
31
33
  "@types/fs-extra": "^11.0.4",
32
34
  "@types/node": "^20.12.7",
33
35
  "tsup": "^8.0.2",
34
- "typescript": "^5.4.5"
36
+ "typescript": "^5.4.5",
37
+ "vitest": "^4.0.16"
35
38
  }
36
39
  }