@launch77/cli 1.4.0 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # @launch77/cli
2
2
 
3
+ ## 1.4.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 511fd8f: Bump
8
+
9
+ ## 1.4.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 0fc3f6c: Fixed package issue with typescript
14
+ - ded96b7: Fixed packaging issue in cli
15
+
16
+ ## 1.4.1
17
+
18
+ ### Patch Changes
19
+
20
+ - 535ae04: Moved manifest writing from generatros to CLI
21
+ - Updated dependencies [535ae04]
22
+ - @launch77/plugin-runtime@0.2.2
23
+
3
24
  ## 1.4.0
4
25
 
5
26
  ### Minor Changes
@@ -1,6 +1,22 @@
1
1
  import type { Launch77Context } from '@launch77/plugin-runtime';
2
2
  import type { InstallPluginRequest, InstallPluginResult } from '../types/plugin-types.js';
3
3
  export declare class PluginService {
4
+ /**
5
+ * Validate that we're in a valid package directory and return the target type
6
+ */
7
+ private validateContext;
8
+ /**
9
+ * Validate plugin name, resolve its location, and download if needed
10
+ */
11
+ private validateAndResolvePlugin;
12
+ /**
13
+ * Read plugin metadata and validate it supports the current target
14
+ */
15
+ private validatePluginTargets;
16
+ /**
17
+ * Check if plugin is already installed and return early-exit result if so
18
+ */
19
+ private checkExistingInstallation;
4
20
  /**
5
21
  * Install a plugin to the current package
6
22
  */
@@ -11,5 +27,10 @@ export declare class PluginService {
11
27
  private downloadNpmPlugin;
12
28
  private getPackagePath;
13
29
  private runGenerator;
30
+ private isPluginInstalled;
31
+ /**
32
+ * Write plugin installation metadata to the target package's package.json
33
+ */
34
+ private writePluginManifest;
14
35
  }
15
36
  //# sourceMappingURL=plugin-svc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-svc.d.ts","sourceRoot":"","sources":["../../../../src/modules/plugin/services/plugin-svc.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAwB,MAAM,0BAA0B,CAAA;AACrF,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAoBzF,qBAAa,aAAa;IACxB;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA8D3J;;OAEG;YACW,iBAAiB;IAkB/B,OAAO,CAAC,cAAc;YAgBR,YAAY;CAY3B"}
1
+ {"version":3,"file":"plugin-svc.d.ts","sourceRoot":"","sources":["../../../../src/modules/plugin/services/plugin-svc.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAA0F,MAAM,0BAA0B,CAAA;AACvJ,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAoBzF,qBAAa,aAAa;IACxB;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;OAEG;YACW,wBAAwB;IAkCtC;;OAEG;YACW,qBAAqB;IAcnC;;OAEG;YACW,yBAAyB;IAmBvC;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,oBAAoB,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA6B3J;;OAEG;YACW,iBAAiB;IAkB/B,OAAO,CAAC,cAAc;YAgBR,YAAY;YAeZ,iBAAiB;IAmB/B;;OAEG;YACW,mBAAmB;CA4BlC"}
@@ -1,4 +1,5 @@
1
1
  import * as path from 'path';
2
+ import * as fs from 'fs/promises';
2
3
  import chalk from 'chalk';
3
4
  import { execa } from 'execa';
4
5
  import { readPluginMetadata } from '@launch77/plugin-runtime';
@@ -23,17 +24,20 @@ function locationTypeToTarget(locationType) {
23
24
  }
24
25
  export class PluginService {
25
26
  /**
26
- * Install a plugin to the current package
27
+ * Validate that we're in a valid package directory and return the target type
27
28
  */
28
- async installPlugin(request, context, logger = console.log) {
29
- const { pluginName } = request;
30
- // Must be in a package directory (app, library, plugin, or app-template)
29
+ validateContext(context) {
31
30
  const currentTarget = locationTypeToTarget(context.locationType);
32
31
  if (!currentTarget)
33
32
  throw createInvalidContextError(context.locationType);
34
33
  if (!context.appName)
35
34
  throw new InvalidPluginContextError('Could not determine package name. This is a bug. Please report it.');
36
- // Step 1: Validate plugin input
35
+ return currentTarget;
36
+ }
37
+ /**
38
+ * Validate plugin name, resolve its location, and download if needed
39
+ */
40
+ async validateAndResolvePlugin(pluginName, workspaceRoot, logger) {
37
41
  logger(chalk.blue(`\nšŸ” Resolving plugin "${pluginName}"...`));
38
42
  logger(` ā”œā”€ Validating plugin name...`);
39
43
  const validation = validatePluginInput(pluginName);
@@ -41,9 +45,8 @@ export class PluginService {
41
45
  throw new PluginResolutionError(pluginName, validation.error || 'Invalid plugin name');
42
46
  }
43
47
  logger(` │ └─ ${chalk.green('āœ“')} Valid plugin name`);
44
- // Step 2: Resolve plugin location
45
48
  logger(` ā”œā”€ Checking local workspace: ${chalk.dim(`plugins/${pluginName}`)}`);
46
- const resolution = await resolvePluginLocation(pluginName, context.workspaceRoot);
49
+ const resolution = await resolvePluginLocation(pluginName, workspaceRoot);
47
50
  let pluginPath;
48
51
  if (resolution.source === 'local') {
49
52
  logger(` │ └─ ${chalk.green('āœ“')} Found local plugin`);
@@ -52,11 +55,19 @@ export class PluginService {
52
55
  else {
53
56
  logger(` │ └─ ${chalk.dim('Not found locally')}`);
54
57
  logger(` ā”œā”€ Resolving to npm package: ${chalk.cyan(resolution.npmPackage)}`);
55
- // Download npm plugin
56
- pluginPath = await this.downloadNpmPlugin(resolution.npmPackage, context.workspaceRoot, logger);
58
+ pluginPath = await this.downloadNpmPlugin(resolution.npmPackage, workspaceRoot, logger);
57
59
  }
58
60
  logger(` └─ ${chalk.green('āœ“')} Plugin resolved\n`);
59
- // Step 3: Read plugin metadata and validate targets
61
+ return {
62
+ pluginPath,
63
+ source: resolution.source,
64
+ npmPackage: resolution.npmPackage,
65
+ };
66
+ }
67
+ /**
68
+ * Read plugin metadata and validate it supports the current target
69
+ */
70
+ async validatePluginTargets(pluginPath, pluginName, currentTarget) {
60
71
  const metadata = await readPluginMetadata(pluginPath);
61
72
  if (!metadata.targets || metadata.targets.length === 0) {
62
73
  throw new MissingPluginTargetsError(pluginName);
@@ -64,10 +75,49 @@ export class PluginService {
64
75
  if (!metadata.targets.includes(currentTarget)) {
65
76
  throw createInvalidTargetError(pluginName, currentTarget, metadata.targets);
66
77
  }
67
- // Step 4: Get package directory path
78
+ return metadata;
79
+ }
80
+ /**
81
+ * Check if plugin is already installed and return early-exit result if so
82
+ */
83
+ async checkExistingInstallation(pluginName, packagePath, logger) {
84
+ const existingInstallation = await this.isPluginInstalled(pluginName, packagePath);
85
+ if (existingInstallation) {
86
+ logger(chalk.yellow(`\nā„¹ļø Plugin '${pluginName}' is already installed in this package.\n`));
87
+ logger(`Package: ${chalk.cyan(existingInstallation.package)} (${existingInstallation.source})`);
88
+ logger(`Version: ${existingInstallation.version}`);
89
+ logger(`Installed: ${existingInstallation.installedAt}\n`);
90
+ logger(chalk.gray('To reinstall: Remove from package.json launch77.installedPlugins'));
91
+ logger(chalk.gray('(plugin:remove command coming soon)\n'));
92
+ return {
93
+ pluginName,
94
+ filesInstalled: false,
95
+ packageJsonUpdated: false,
96
+ dependenciesInstalled: false,
97
+ };
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * Install a plugin to the current package
103
+ */
104
+ async installPlugin(request, context, logger = console.log) {
105
+ const { pluginName } = request;
106
+ const currentTarget = this.validateContext(context);
107
+ const { pluginPath, source, npmPackage } = await this.validateAndResolvePlugin(pluginName, context.workspaceRoot, logger);
108
+ const metadata = await this.validatePluginTargets(pluginPath, pluginName, currentTarget);
68
109
  const packagePath = this.getPackagePath(context);
69
- // Step 5: Run generator
110
+ const earlyExit = await this.checkExistingInstallation(pluginName, packagePath, logger);
111
+ if (earlyExit)
112
+ return earlyExit;
70
113
  await this.runGenerator(pluginPath, packagePath, context);
114
+ const packageName = source === 'npm' ? npmPackage : pluginName;
115
+ await this.writePluginManifest(packagePath, {
116
+ pluginName,
117
+ packageName,
118
+ version: metadata.version,
119
+ source,
120
+ });
71
121
  return {
72
122
  pluginName,
73
123
  filesInstalled: true,
@@ -112,7 +162,8 @@ export class PluginService {
112
162
  async runGenerator(pluginPath, appPath, context) {
113
163
  try {
114
164
  const generatorPath = path.join(pluginPath, 'dist/generator.js');
115
- await execa('node', [generatorPath, `--appPath=${appPath}`, `--appName=${context.appName}`, `--workspaceName=${context.workspaceName}`, `--pluginPath=${pluginPath}`], {
165
+ const args = [generatorPath, `--appPath=${appPath}`, `--appName=${context.appName}`, `--workspaceName=${context.workspaceName}`, `--pluginPath=${pluginPath}`];
166
+ await execa('node', args, {
116
167
  cwd: pluginPath,
117
168
  stdio: 'inherit',
118
169
  });
@@ -121,5 +172,48 @@ export class PluginService {
121
172
  throw new PluginInstallationError(`Generator failed: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
122
173
  }
123
174
  }
175
+ async isPluginInstalled(pluginName, packagePath) {
176
+ try {
177
+ const packageJsonPath = path.join(packagePath, 'package.json');
178
+ const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
179
+ const packageJson = JSON.parse(packageJsonContent);
180
+ const manifest = packageJson.launch77;
181
+ if (manifest?.installedPlugins?.[pluginName]) {
182
+ return manifest.installedPlugins[pluginName];
183
+ }
184
+ return null;
185
+ }
186
+ catch (error) {
187
+ // If package.json doesn't exist or can't be read, assume not installed
188
+ return null;
189
+ }
190
+ }
191
+ /**
192
+ * Write plugin installation metadata to the target package's package.json
193
+ */
194
+ async writePluginManifest(packagePath, installationInfo) {
195
+ try {
196
+ const packageJsonPath = path.join(packagePath, 'package.json');
197
+ const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8');
198
+ const packageJson = JSON.parse(packageJsonContent);
199
+ if (!packageJson.launch77) {
200
+ packageJson.launch77 = {};
201
+ }
202
+ if (!packageJson.launch77.installedPlugins) {
203
+ packageJson.launch77.installedPlugins = {};
204
+ }
205
+ const manifest = packageJson.launch77;
206
+ manifest.installedPlugins[installationInfo.pluginName] = {
207
+ package: installationInfo.packageName,
208
+ version: installationInfo.version,
209
+ installedAt: new Date().toISOString(),
210
+ source: installationInfo.source,
211
+ };
212
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
213
+ }
214
+ catch (error) {
215
+ throw new PluginInstallationError(`Failed to write plugin manifest: ${error instanceof Error ? error.message : String(error)}`, error instanceof Error ? error : undefined);
216
+ }
217
+ }
124
218
  }
125
219
  //# sourceMappingURL=plugin-svc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-svc.js","sourceRoot":"","sources":["../../../../src/modules/plugin/services/plugin-svc.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin-svc.js","sourceRoot":"","sources":["../../../../src/modules/plugin/services/plugin-svc.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plugin-svc.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-svc.test.d.ts","sourceRoot":"","sources":["../../../../src/modules/plugin/services/plugin-svc.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,362 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from 'vitest';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import fs from 'fs-extra';
5
+ import { PluginService } from './plugin-svc.js';
6
+ describe('PluginService', () => {
7
+ let tempDir;
8
+ let service;
9
+ beforeEach(async () => {
10
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'plugin-svc-test-'));
11
+ service = new PluginService();
12
+ });
13
+ afterEach(async () => {
14
+ await fs.remove(tempDir);
15
+ });
16
+ describe('validateContext', () => {
17
+ // Valid contexts - test return values
18
+ test('should return "app" for workspace-app location', () => {
19
+ const context = {
20
+ isValid: true,
21
+ locationType: 'workspace-app',
22
+ workspaceRoot: tempDir,
23
+ workspaceName: 'test-workspace',
24
+ workspaceVersion: '1.0.0',
25
+ appsDir: path.join(tempDir, 'apps'),
26
+ packageName: 'test-app',
27
+ appName: 'test-app',
28
+ };
29
+ const result = service.validateContext(context);
30
+ expect(result).toBe('app');
31
+ });
32
+ test('should return "library" for workspace-library location', () => {
33
+ const context = {
34
+ isValid: true,
35
+ locationType: 'workspace-library',
36
+ workspaceRoot: tempDir,
37
+ workspaceName: 'test-workspace',
38
+ workspaceVersion: '1.0.0',
39
+ appsDir: path.join(tempDir, 'apps'),
40
+ packageName: 'test-library',
41
+ appName: 'test-library',
42
+ };
43
+ const result = service.validateContext(context);
44
+ expect(result).toBe('library');
45
+ });
46
+ test('should return "plugin" for workspace-plugin location', () => {
47
+ const context = {
48
+ isValid: true,
49
+ locationType: 'workspace-plugin',
50
+ workspaceRoot: tempDir,
51
+ workspaceName: 'test-workspace',
52
+ workspaceVersion: '1.0.0',
53
+ appsDir: path.join(tempDir, 'apps'),
54
+ packageName: 'test-plugin',
55
+ appName: 'test-plugin',
56
+ };
57
+ const result = service.validateContext(context);
58
+ expect(result).toBe('plugin');
59
+ });
60
+ test('should return "app-template" for workspace-app-template location', () => {
61
+ const context = {
62
+ isValid: true,
63
+ locationType: 'workspace-app-template',
64
+ workspaceRoot: tempDir,
65
+ workspaceName: 'test-workspace',
66
+ workspaceVersion: '1.0.0',
67
+ appsDir: path.join(tempDir, 'apps'),
68
+ packageName: 'test-template',
69
+ appName: 'test-template',
70
+ };
71
+ const result = service.validateContext(context);
72
+ expect(result).toBe('app-template');
73
+ });
74
+ // Invalid contexts - test exact error messages
75
+ test('should throw InvalidPluginContextError for workspace-root', () => {
76
+ const context = {
77
+ isValid: true,
78
+ locationType: 'workspace-root',
79
+ workspaceRoot: tempDir,
80
+ workspaceName: 'test-workspace',
81
+ workspaceVersion: '1.0.0',
82
+ appsDir: path.join(tempDir, 'apps'),
83
+ packageName: 'workspace',
84
+ appName: undefined,
85
+ };
86
+ expect(() => service.validateContext(context)).toThrow('plugin:install must be run from within a package directory.');
87
+ });
88
+ test('should throw InvalidPluginContextError for non-workspace', () => {
89
+ const context = {
90
+ isValid: false,
91
+ locationType: 'non-workspace',
92
+ workspaceRoot: tempDir,
93
+ workspaceName: 'test-workspace',
94
+ workspaceVersion: '1.0.0',
95
+ appsDir: path.join(tempDir, 'apps'),
96
+ packageName: 'unknown',
97
+ appName: undefined,
98
+ };
99
+ expect(() => service.validateContext(context)).toThrow('plugin:install must be run from within a package directory.');
100
+ });
101
+ test('should throw InvalidPluginContextError when appName is missing', () => {
102
+ const context = {
103
+ isValid: true,
104
+ locationType: 'workspace-app',
105
+ workspaceRoot: tempDir,
106
+ workspaceName: 'test-workspace',
107
+ workspaceVersion: '1.0.0',
108
+ appsDir: path.join(tempDir, 'apps'),
109
+ packageName: 'test-app',
110
+ appName: undefined,
111
+ };
112
+ expect(() => service.validateContext(context)).toThrow('Could not determine package name. This is a bug. Please report it.');
113
+ });
114
+ test('should throw InvalidPluginContextError when appName is empty string', () => {
115
+ const context = {
116
+ isValid: true,
117
+ locationType: 'workspace-app',
118
+ workspaceRoot: tempDir,
119
+ workspaceName: 'test-workspace',
120
+ workspaceVersion: '1.0.0',
121
+ appsDir: path.join(tempDir, 'apps'),
122
+ packageName: 'test-app',
123
+ appName: '',
124
+ };
125
+ expect(() => service.validateContext(context)).toThrow('Could not determine package name. This is a bug. Please report it.');
126
+ });
127
+ });
128
+ describe('validatePluginTargets', () => {
129
+ // Valid scenarios - verify metadata returned
130
+ test('should return metadata when plugin targets current package type', async () => {
131
+ const pluginDir = path.join(tempDir, 'test-plugin');
132
+ await fs.ensureDir(pluginDir);
133
+ await fs.writeJson(path.join(pluginDir, 'plugin.json'), {
134
+ name: 'test-plugin',
135
+ version: '1.0.0',
136
+ targets: ['app', 'library'],
137
+ });
138
+ const result = await service.validatePluginTargets(pluginDir, 'test-plugin', 'app');
139
+ expect(result).toEqual({
140
+ name: 'test-plugin',
141
+ version: '1.0.0',
142
+ targets: ['app', 'library'],
143
+ });
144
+ });
145
+ test('should accept plugin with multiple targets including current', async () => {
146
+ const pluginDir = path.join(tempDir, 'multi-target-plugin');
147
+ await fs.ensureDir(pluginDir);
148
+ await fs.writeJson(path.join(pluginDir, 'plugin.json'), {
149
+ name: 'multi-target-plugin',
150
+ version: '2.0.0',
151
+ targets: ['app', 'library', 'plugin', 'app-template'],
152
+ });
153
+ const result = await service.validatePluginTargets(pluginDir, 'multi-target-plugin', 'library');
154
+ expect(result.targets).toContain('library');
155
+ expect(result.targets).toHaveLength(4);
156
+ });
157
+ test('should handle plugin.json with optional fields (pluginDependencies, libraryDependencies)', async () => {
158
+ const pluginDir = path.join(tempDir, 'full-plugin');
159
+ await fs.ensureDir(pluginDir);
160
+ await fs.writeJson(path.join(pluginDir, 'plugin.json'), {
161
+ name: 'full-plugin',
162
+ version: '1.5.0',
163
+ targets: ['app'],
164
+ pluginDependencies: { 'other-plugin': '^1.0.0' },
165
+ libraryDependencies: { react: '^18.0.0' },
166
+ });
167
+ const result = await service.validatePluginTargets(pluginDir, 'full-plugin', 'app');
168
+ expect(result.pluginDependencies).toEqual({ 'other-plugin': '^1.0.0' });
169
+ expect(result.libraryDependencies).toEqual({ react: '^18.0.0' });
170
+ });
171
+ // Error scenarios - test exact error messages
172
+ test('should throw MissingPluginTargetsError when targets is undefined', async () => {
173
+ const pluginDir = path.join(tempDir, 'no-targets-plugin');
174
+ await fs.ensureDir(pluginDir);
175
+ await fs.writeJson(path.join(pluginDir, 'plugin.json'), {
176
+ name: 'no-targets-plugin',
177
+ version: '1.0.0',
178
+ });
179
+ await expect(service.validatePluginTargets(pluginDir, 'no-targets-plugin', 'app')).rejects.toThrow("Plugin 'no-targets-plugin' is missing the required 'targets' field in plugin.json.");
180
+ });
181
+ test('should throw MissingPluginTargetsError when targets is empty array', async () => {
182
+ const pluginDir = path.join(tempDir, 'empty-targets-plugin');
183
+ await fs.ensureDir(pluginDir);
184
+ await fs.writeJson(path.join(pluginDir, 'plugin.json'), {
185
+ name: 'empty-targets-plugin',
186
+ version: '1.0.0',
187
+ targets: [],
188
+ });
189
+ await expect(service.validatePluginTargets(pluginDir, 'empty-targets-plugin', 'app')).rejects.toThrow("Plugin 'empty-targets-plugin' is missing the required 'targets' field in plugin.json.");
190
+ });
191
+ test('should throw error when targets does not include current target', async () => {
192
+ const pluginDir = path.join(tempDir, 'incompatible-plugin');
193
+ await fs.ensureDir(pluginDir);
194
+ await fs.writeJson(path.join(pluginDir, 'plugin.json'), {
195
+ name: 'incompatible-plugin',
196
+ version: '1.0.0',
197
+ targets: ['library', 'plugin'],
198
+ });
199
+ await expect(service.validatePluginTargets(pluginDir, 'incompatible-plugin', 'app')).rejects.toThrow("Plugin 'incompatible-plugin' cannot be installed in a 'app' package.");
200
+ });
201
+ test('should handle missing plugin.json file gracefully', async () => {
202
+ const pluginDir = path.join(tempDir, 'no-plugin-json');
203
+ await fs.ensureDir(pluginDir);
204
+ await expect(service.validatePluginTargets(pluginDir, 'no-plugin-json', 'app')).rejects.toThrow();
205
+ });
206
+ });
207
+ describe('checkExistingInstallation', () => {
208
+ const mockLogger = (message) => {
209
+ /* capture logs */
210
+ };
211
+ // Not installed - return null
212
+ test('should return null when plugin is not installed', async () => {
213
+ const packageDir = path.join(tempDir, 'app1');
214
+ await fs.ensureDir(packageDir);
215
+ await fs.writeJson(path.join(packageDir, 'package.json'), {
216
+ name: 'app1',
217
+ version: '1.0.0',
218
+ launch77: {
219
+ installedPlugins: {},
220
+ },
221
+ });
222
+ const result = await service.checkExistingInstallation('test-plugin', packageDir, mockLogger);
223
+ expect(result).toBeNull();
224
+ });
225
+ test('should return null when package.json does not exist', async () => {
226
+ const packageDir = path.join(tempDir, 'nonexistent');
227
+ await fs.ensureDir(packageDir);
228
+ const result = await service.checkExistingInstallation('test-plugin', packageDir, mockLogger);
229
+ expect(result).toBeNull();
230
+ });
231
+ test('should return null when launch77 field is missing', async () => {
232
+ const packageDir = path.join(tempDir, 'app2');
233
+ await fs.ensureDir(packageDir);
234
+ await fs.writeJson(path.join(packageDir, 'package.json'), {
235
+ name: 'app2',
236
+ version: '1.0.0',
237
+ });
238
+ const result = await service.checkExistingInstallation('test-plugin', packageDir, mockLogger);
239
+ expect(result).toBeNull();
240
+ });
241
+ test('should return null when installedPlugins is missing', async () => {
242
+ const packageDir = path.join(tempDir, 'app3');
243
+ await fs.ensureDir(packageDir);
244
+ await fs.writeJson(path.join(packageDir, 'package.json'), {
245
+ name: 'app3',
246
+ version: '1.0.0',
247
+ launch77: {},
248
+ });
249
+ const result = await service.checkExistingInstallation('test-plugin', packageDir, mockLogger);
250
+ expect(result).toBeNull();
251
+ });
252
+ test('should return null when installedPlugins exists but plugin not in it', async () => {
253
+ const packageDir = path.join(tempDir, 'app4');
254
+ await fs.ensureDir(packageDir);
255
+ await fs.writeJson(path.join(packageDir, 'package.json'), {
256
+ name: 'app4',
257
+ version: '1.0.0',
258
+ launch77: {
259
+ installedPlugins: {
260
+ 'other-plugin': {
261
+ package: 'other-plugin',
262
+ version: '1.0.0',
263
+ installedAt: '2024-01-01T00:00:00.000Z',
264
+ source: 'local',
265
+ },
266
+ },
267
+ },
268
+ });
269
+ const result = await service.checkExistingInstallation('test-plugin', packageDir, mockLogger);
270
+ expect(result).toBeNull();
271
+ });
272
+ // Already installed - return result object
273
+ test('should return early-exit result with correct structure when plugin is installed', async () => {
274
+ const packageDir = path.join(tempDir, 'app5');
275
+ await fs.ensureDir(packageDir);
276
+ await fs.writeJson(path.join(packageDir, 'package.json'), {
277
+ name: 'app5',
278
+ version: '1.0.0',
279
+ launch77: {
280
+ installedPlugins: {
281
+ 'test-plugin': {
282
+ package: 'test-plugin',
283
+ version: '1.5.0',
284
+ installedAt: '2024-01-15T10:30:00.000Z',
285
+ source: 'local',
286
+ },
287
+ },
288
+ },
289
+ });
290
+ const result = await service.checkExistingInstallation('test-plugin', packageDir, mockLogger);
291
+ expect(result).toEqual({
292
+ pluginName: 'test-plugin',
293
+ filesInstalled: false,
294
+ packageJsonUpdated: false,
295
+ dependenciesInstalled: false,
296
+ });
297
+ });
298
+ test('should log correct message for local plugin (package name matches plugin name)', async () => {
299
+ const packageDir = path.join(tempDir, 'app6');
300
+ await fs.ensureDir(packageDir);
301
+ await fs.writeJson(path.join(packageDir, 'package.json'), {
302
+ name: 'app6',
303
+ version: '1.0.0',
304
+ launch77: {
305
+ installedPlugins: {
306
+ release: {
307
+ package: 'release',
308
+ version: '2.0.0',
309
+ installedAt: '2024-02-01T12:00:00.000Z',
310
+ source: 'local',
311
+ },
312
+ },
313
+ },
314
+ });
315
+ const logs = [];
316
+ const captureLogger = (message) => logs.push(message);
317
+ const result = await service.checkExistingInstallation('release', packageDir, captureLogger);
318
+ expect(result).not.toBeNull();
319
+ expect(logs.some((log) => log.includes("Plugin 'release' is already installed"))).toBe(true);
320
+ expect(logs.some((log) => log.includes('release') && log.includes('local'))).toBe(true);
321
+ });
322
+ test('should log correct message for npm plugin (package name is scoped)', async () => {
323
+ const packageDir = path.join(tempDir, 'app7');
324
+ await fs.ensureDir(packageDir);
325
+ await fs.writeJson(path.join(packageDir, 'package.json'), {
326
+ name: 'app7',
327
+ version: '1.0.0',
328
+ launch77: {
329
+ installedPlugins: {
330
+ analytics: {
331
+ package: '@myorg/analytics-plugin',
332
+ version: '3.1.0',
333
+ installedAt: '2024-03-01T15:45:00.000Z',
334
+ source: 'npm',
335
+ },
336
+ },
337
+ },
338
+ });
339
+ const logs = [];
340
+ const captureLogger = (message) => logs.push(message);
341
+ const result = await service.checkExistingInstallation('analytics', packageDir, captureLogger);
342
+ expect(result).not.toBeNull();
343
+ expect(logs.some((log) => log.includes("Plugin 'analytics' is already installed"))).toBe(true);
344
+ expect(logs.some((log) => log.includes('@myorg/analytics-plugin') && log.includes('npm'))).toBe(true);
345
+ });
346
+ // Edge cases
347
+ test('should handle malformed package.json gracefully (invalid JSON)', async () => {
348
+ const packageDir = path.join(tempDir, 'app8');
349
+ await fs.ensureDir(packageDir);
350
+ await fs.writeFile(path.join(packageDir, 'package.json'), '{ invalid json }');
351
+ const result = await service.checkExistingInstallation('test-plugin', packageDir, mockLogger);
352
+ expect(result).toBeNull();
353
+ });
354
+ test('should handle package.json read errors (permissions, etc.)', async () => {
355
+ const packageDir = path.join(tempDir, 'app9');
356
+ // Don't create the directory - simulate permission/access error
357
+ const result = await service.checkExistingInstallation('test-plugin', packageDir, mockLogger);
358
+ expect(result).toBeNull();
359
+ });
360
+ });
361
+ });
362
+ //# sourceMappingURL=plugin-svc.test.js.map