@launch77/cli 1.4.1 ā 1.4.4
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 +21 -0
- package/dist/infrastructure/npm-package.d.ts +42 -0
- package/dist/infrastructure/npm-package.d.ts.map +1 -0
- package/dist/infrastructure/npm-package.js +46 -0
- package/dist/infrastructure/npm-package.js.map +1 -0
- package/dist/infrastructure/package-resolver.d.ts +107 -0
- package/dist/infrastructure/package-resolver.d.ts.map +1 -0
- package/dist/infrastructure/package-resolver.js +143 -0
- package/dist/infrastructure/package-resolver.js.map +1 -0
- package/dist/infrastructure/package-resolver.test.d.ts +2 -0
- package/dist/infrastructure/package-resolver.test.d.ts.map +1 -0
- package/dist/infrastructure/package-resolver.test.js +251 -0
- package/dist/infrastructure/package-resolver.test.js.map +1 -0
- package/dist/modules/app/commands/create-app.d.ts.map +1 -1
- package/dist/modules/app/commands/create-app.js +6 -2
- package/dist/modules/app/commands/create-app.js.map +1 -1
- package/dist/modules/app/lib/app-template-resolver.d.ts +14 -0
- package/dist/modules/app/lib/app-template-resolver.d.ts.map +1 -0
- package/dist/modules/app/lib/app-template-resolver.js +36 -0
- package/dist/modules/app/lib/app-template-resolver.js.map +1 -0
- package/dist/modules/app/services/app-svc.d.ts +2 -1
- package/dist/modules/app/services/app-svc.d.ts.map +1 -1
- package/dist/modules/app/services/app-svc.js +46 -11
- package/dist/modules/app/services/app-svc.js.map +1 -1
- package/dist/modules/plugin/lib/plugin-resolver.d.ts +10 -72
- package/dist/modules/plugin/lib/plugin-resolver.d.ts.map +1 -1
- package/dist/modules/plugin/lib/plugin-resolver.js +12 -115
- package/dist/modules/plugin/lib/plugin-resolver.js.map +1 -1
- package/dist/modules/plugin/lib/plugin-resolver.test.js +43 -39
- package/dist/modules/plugin/lib/plugin-resolver.test.js.map +1 -1
- package/dist/modules/plugin/services/plugin-svc.d.ts +2 -0
- package/dist/modules/plugin/services/plugin-svc.d.ts.map +1 -1
- package/dist/modules/plugin/services/plugin-svc.js +14 -17
- package/dist/modules/plugin/services/plugin-svc.js.map +1 -1
- package/package.json +3 -3
- package/src/infrastructure/npm-package.ts +73 -0
- package/src/infrastructure/package-resolver.test.ts +313 -0
- package/src/infrastructure/package-resolver.ts +194 -0
- package/src/modules/app/commands/create-app.ts +6 -2
- package/src/modules/app/lib/app-template-resolver.ts +40 -0
- package/src/modules/app/services/app-svc.ts +49 -12
- package/src/modules/plugin/lib/plugin-resolver.test.ts +46 -39
- package/src/modules/plugin/lib/plugin-resolver.ts +12 -142
- package/src/modules/plugin/services/plugin-svc.ts +15 -15
|
@@ -1,99 +1,106 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import * as path from 'path'
|
|
3
1
|
import * as os from 'os'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
|
|
4
4
|
import fs from 'fs-extra'
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import { PluginResolver } from './plugin-resolver.js'
|
|
7
8
|
|
|
8
9
|
describe('Plugin Resolver', () => {
|
|
9
|
-
|
|
10
|
+
let resolver: PluginResolver
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
resolver = new PluginResolver()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('validateInput', () => {
|
|
10
17
|
it('should accept valid unscoped plugin names', () => {
|
|
11
|
-
expect(
|
|
12
|
-
expect(
|
|
13
|
-
expect(
|
|
18
|
+
expect(resolver.validateInput('release')).toEqual({ isValid: true })
|
|
19
|
+
expect(resolver.validateInput('my-plugin')).toEqual({ isValid: true })
|
|
20
|
+
expect(resolver.validateInput('analytics-v2')).toEqual({ isValid: true })
|
|
14
21
|
})
|
|
15
22
|
|
|
16
23
|
it('should accept valid scoped npm packages', () => {
|
|
17
|
-
expect(
|
|
18
|
-
expect(
|
|
19
|
-
expect(
|
|
24
|
+
expect(resolver.validateInput('@ibm/plugin-name')).toEqual({ isValid: true })
|
|
25
|
+
expect(resolver.validateInput('@launch77-shared/plugin-release')).toEqual({ isValid: true })
|
|
26
|
+
expect(resolver.validateInput('@org/analytics')).toEqual({ isValid: true })
|
|
20
27
|
})
|
|
21
28
|
|
|
22
29
|
it('should reject empty names', () => {
|
|
23
|
-
const result =
|
|
30
|
+
const result = resolver.validateInput('')
|
|
24
31
|
expect(result.isValid).toBe(false)
|
|
25
32
|
expect(result.error).toBeDefined()
|
|
26
33
|
})
|
|
27
34
|
|
|
28
35
|
it('should reject whitespace-only names', () => {
|
|
29
|
-
const result =
|
|
36
|
+
const result = resolver.validateInput(' ')
|
|
30
37
|
expect(result.isValid).toBe(false)
|
|
31
38
|
expect(result.error).toBeDefined()
|
|
32
39
|
})
|
|
33
40
|
|
|
34
41
|
it('should reject invalid scoped packages', () => {
|
|
35
|
-
const result1 =
|
|
42
|
+
const result1 = resolver.validateInput('@invalid')
|
|
36
43
|
expect(result1.isValid).toBe(false)
|
|
37
44
|
expect(result1.error).toBeDefined()
|
|
38
45
|
|
|
39
|
-
const result2 =
|
|
46
|
+
const result2 = resolver.validateInput('@/package')
|
|
40
47
|
expect(result2.isValid).toBe(false)
|
|
41
48
|
expect(result2.error).toBeDefined()
|
|
42
49
|
|
|
43
|
-
const result3 =
|
|
50
|
+
const result3 = resolver.validateInput('@org/')
|
|
44
51
|
expect(result3.isValid).toBe(false)
|
|
45
52
|
expect(result3.error).toBeDefined()
|
|
46
53
|
})
|
|
47
54
|
|
|
48
55
|
it('should reject names with uppercase letters', () => {
|
|
49
|
-
const result =
|
|
56
|
+
const result = resolver.validateInput('MyPlugin')
|
|
50
57
|
expect(result.isValid).toBe(false)
|
|
51
58
|
expect(result.error).toContain('lowercase')
|
|
52
59
|
})
|
|
53
60
|
|
|
54
61
|
it('should reject names starting with numbers', () => {
|
|
55
|
-
const result =
|
|
62
|
+
const result = resolver.validateInput('123plugin')
|
|
56
63
|
expect(result.isValid).toBe(false)
|
|
57
64
|
expect(result.error).toBeDefined()
|
|
58
65
|
})
|
|
59
66
|
|
|
60
67
|
it('should reject names with special characters', () => {
|
|
61
|
-
const result1 =
|
|
68
|
+
const result1 = resolver.validateInput('plugin_name')
|
|
62
69
|
expect(result1.isValid).toBe(false)
|
|
63
70
|
|
|
64
|
-
const result2 =
|
|
71
|
+
const result2 = resolver.validateInput('plugin.name')
|
|
65
72
|
expect(result2.isValid).toBe(false)
|
|
66
73
|
|
|
67
|
-
const result3 =
|
|
74
|
+
const result3 = resolver.validateInput('plugin name')
|
|
68
75
|
expect(result3.isValid).toBe(false)
|
|
69
76
|
})
|
|
70
77
|
|
|
71
78
|
it('should trim whitespace before validation', () => {
|
|
72
|
-
expect(
|
|
73
|
-
expect(
|
|
79
|
+
expect(resolver.validateInput(' release ')).toEqual({ isValid: true })
|
|
80
|
+
expect(resolver.validateInput(' @ibm/analytics ')).toEqual({ isValid: true })
|
|
74
81
|
})
|
|
75
82
|
})
|
|
76
83
|
|
|
77
84
|
describe('toNpmPackageName', () => {
|
|
78
85
|
it('should prefix unscoped names with @launch77-shared/plugin-', () => {
|
|
79
|
-
expect(toNpmPackageName('release')).toBe('@launch77-shared/plugin-release')
|
|
80
|
-
expect(toNpmPackageName('my-plugin')).toBe('@launch77-shared/plugin-my-plugin')
|
|
81
|
-
expect(toNpmPackageName('analytics-v2')).toBe('@launch77-shared/plugin-analytics-v2')
|
|
86
|
+
expect(resolver.toNpmPackageName('release')).toBe('@launch77-shared/plugin-release')
|
|
87
|
+
expect(resolver.toNpmPackageName('my-plugin')).toBe('@launch77-shared/plugin-my-plugin')
|
|
88
|
+
expect(resolver.toNpmPackageName('analytics-v2')).toBe('@launch77-shared/plugin-analytics-v2')
|
|
82
89
|
})
|
|
83
90
|
|
|
84
91
|
it('should return scoped packages as-is', () => {
|
|
85
|
-
expect(toNpmPackageName('@ibm/analytics')).toBe('@ibm/analytics')
|
|
86
|
-
expect(toNpmPackageName('@launch77-shared/plugin-release')).toBe('@launch77-shared/plugin-release')
|
|
87
|
-
expect(toNpmPackageName('@org/package')).toBe('@org/package')
|
|
92
|
+
expect(resolver.toNpmPackageName('@ibm/analytics')).toBe('@ibm/analytics')
|
|
93
|
+
expect(resolver.toNpmPackageName('@launch77-shared/plugin-release')).toBe('@launch77-shared/plugin-release')
|
|
94
|
+
expect(resolver.toNpmPackageName('@org/package')).toBe('@org/package')
|
|
88
95
|
})
|
|
89
96
|
|
|
90
97
|
it('should trim whitespace', () => {
|
|
91
|
-
expect(toNpmPackageName(' release ')).toBe('@launch77-shared/plugin-release')
|
|
92
|
-
expect(toNpmPackageName(' @ibm/analytics ')).toBe('@ibm/analytics')
|
|
98
|
+
expect(resolver.toNpmPackageName(' release ')).toBe('@launch77-shared/plugin-release')
|
|
99
|
+
expect(resolver.toNpmPackageName(' @ibm/analytics ')).toBe('@ibm/analytics')
|
|
93
100
|
})
|
|
94
101
|
})
|
|
95
102
|
|
|
96
|
-
describe('
|
|
103
|
+
describe('resolveLocation', () => {
|
|
97
104
|
let tempDir: string
|
|
98
105
|
|
|
99
106
|
beforeEach(async () => {
|
|
@@ -115,7 +122,7 @@ describe('Plugin Resolver', () => {
|
|
|
115
122
|
await fs.writeFile(path.join(pluginPath, 'plugin.json'), JSON.stringify({ name: 'my-plugin', version: '1.0.0' }))
|
|
116
123
|
await fs.writeFile(path.join(pluginPath, 'dist/generator.js'), 'console.log("test")')
|
|
117
124
|
|
|
118
|
-
const result = await
|
|
125
|
+
const result = await resolver.resolveLocation('my-plugin', tempDir)
|
|
119
126
|
|
|
120
127
|
expect(result).toEqual({
|
|
121
128
|
source: 'local',
|
|
@@ -125,7 +132,7 @@ describe('Plugin Resolver', () => {
|
|
|
125
132
|
})
|
|
126
133
|
|
|
127
134
|
it('should resolve to npm if local plugin does not exist', async () => {
|
|
128
|
-
const result = await
|
|
135
|
+
const result = await resolver.resolveLocation('release', tempDir)
|
|
129
136
|
|
|
130
137
|
expect(result).toEqual({
|
|
131
138
|
source: 'npm',
|
|
@@ -141,7 +148,7 @@ describe('Plugin Resolver', () => {
|
|
|
141
148
|
await fs.ensureDir(path.join(pluginPath, 'dist'))
|
|
142
149
|
await fs.writeFile(path.join(pluginPath, 'dist/generator.js'), 'console.log("test")')
|
|
143
150
|
|
|
144
|
-
const result = await
|
|
151
|
+
const result = await resolver.resolveLocation('incomplete', tempDir)
|
|
145
152
|
|
|
146
153
|
expect(result).toEqual({
|
|
147
154
|
source: 'npm',
|
|
@@ -156,7 +163,7 @@ describe('Plugin Resolver', () => {
|
|
|
156
163
|
await fs.ensureDir(pluginPath)
|
|
157
164
|
await fs.writeFile(path.join(pluginPath, 'plugin.json'), JSON.stringify({ name: 'incomplete', version: '1.0.0' }))
|
|
158
165
|
|
|
159
|
-
const result = await
|
|
166
|
+
const result = await resolver.resolveLocation('incomplete', tempDir)
|
|
160
167
|
|
|
161
168
|
expect(result).toEqual({
|
|
162
169
|
source: 'npm',
|
|
@@ -170,7 +177,7 @@ describe('Plugin Resolver', () => {
|
|
|
170
177
|
const pluginPath = path.join(tempDir, 'plugins', '@ibm')
|
|
171
178
|
await fs.ensureDir(pluginPath)
|
|
172
179
|
|
|
173
|
-
const result = await
|
|
180
|
+
const result = await resolver.resolveLocation('@ibm/analytics', tempDir)
|
|
174
181
|
|
|
175
182
|
expect(result).toEqual({
|
|
176
183
|
source: 'npm',
|
|
@@ -180,7 +187,7 @@ describe('Plugin Resolver', () => {
|
|
|
180
187
|
})
|
|
181
188
|
|
|
182
189
|
it('should resolve scoped @launch77-shared packages to npm', async () => {
|
|
183
|
-
const result = await
|
|
190
|
+
const result = await resolver.resolveLocation('@launch77-shared/plugin-release', tempDir)
|
|
184
191
|
|
|
185
192
|
expect(result).toEqual({
|
|
186
193
|
source: 'npm',
|
|
@@ -193,7 +200,7 @@ describe('Plugin Resolver', () => {
|
|
|
193
200
|
// Remove plugins directory
|
|
194
201
|
await fs.remove(path.join(tempDir, 'plugins'))
|
|
195
202
|
|
|
196
|
-
const result = await
|
|
203
|
+
const result = await resolver.resolveLocation('release', tempDir)
|
|
197
204
|
|
|
198
205
|
expect(result).toEqual({
|
|
199
206
|
source: 'npm',
|
|
@@ -203,7 +210,7 @@ describe('Plugin Resolver', () => {
|
|
|
203
210
|
})
|
|
204
211
|
|
|
205
212
|
it('should trim whitespace from plugin names', async () => {
|
|
206
|
-
const result = await
|
|
213
|
+
const result = await resolver.resolveLocation(' release ', tempDir)
|
|
207
214
|
|
|
208
215
|
expect(result).toEqual({
|
|
209
216
|
source: 'npm',
|
|
@@ -1,160 +1,30 @@
|
|
|
1
1
|
import * as path from 'path'
|
|
2
2
|
|
|
3
3
|
import fs from 'fs-extra'
|
|
4
|
-
import { parsePluginName, isValidNpmPackageName } from '@launch77/plugin-runtime'
|
|
5
4
|
|
|
6
|
-
import
|
|
5
|
+
import { PackageResolver } from '../../../infrastructure/package-resolver.js'
|
|
7
6
|
|
|
8
7
|
/**
|
|
9
|
-
* Plugin
|
|
10
|
-
*/
|
|
11
|
-
export interface PluginResolution {
|
|
12
|
-
/** The source of the plugin */
|
|
13
|
-
source: 'local' | 'npm'
|
|
14
|
-
/** The resolved name/package to use */
|
|
15
|
-
resolvedName: string
|
|
16
|
-
/** The local path if source is 'local' */
|
|
17
|
-
localPath?: string
|
|
18
|
-
/** The npm package name if source is 'npm' */
|
|
19
|
-
npmPackage?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Validate plugin input name
|
|
24
|
-
*
|
|
25
|
-
* Accepts:
|
|
26
|
-
* - Unscoped names (e.g., "release", "my-plugin")
|
|
27
|
-
* - Scoped npm packages (e.g., "@ibm/plugin-name")
|
|
8
|
+
* Plugin resolver implementation
|
|
28
9
|
*
|
|
29
|
-
*
|
|
30
|
-
* -
|
|
31
|
-
* -
|
|
32
|
-
* - Names with invalid characters
|
|
33
|
-
*
|
|
34
|
-
* @param name - The plugin name to validate
|
|
35
|
-
* @returns ValidationResult with isValid and optional error message
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* validatePluginInput('release') // { isValid: true }
|
|
39
|
-
* validatePluginInput('@ibm/analytics') // { isValid: true }
|
|
40
|
-
* validatePluginInput('@invalid') // { isValid: false, error: '...' }
|
|
10
|
+
* Resolves plugins from:
|
|
11
|
+
* - Local workspace plugins/ directory
|
|
12
|
+
* - npm packages with @launch77-shared/plugin- prefix
|
|
41
13
|
*/
|
|
42
|
-
export
|
|
43
|
-
|
|
44
|
-
return
|
|
45
|
-
isValid: false,
|
|
46
|
-
error: 'Plugin name cannot be empty',
|
|
47
|
-
}
|
|
14
|
+
export class PluginResolver extends PackageResolver {
|
|
15
|
+
protected getFolderName(): string {
|
|
16
|
+
return 'plugins'
|
|
48
17
|
}
|
|
49
18
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// Parse the name to determine type and validate
|
|
53
|
-
const parsed = parsePluginName(trimmedName)
|
|
54
|
-
|
|
55
|
-
if (!parsed.isValid) {
|
|
56
|
-
return {
|
|
57
|
-
isValid: false,
|
|
58
|
-
error: parsed.error,
|
|
59
|
-
}
|
|
19
|
+
protected getPackagePrefix(): string {
|
|
20
|
+
return '@launch77-shared/plugin-'
|
|
60
21
|
}
|
|
61
22
|
|
|
62
|
-
|
|
63
|
-
if (parsed.type === 'scoped') {
|
|
64
|
-
return isValidNpmPackageName(trimmedName)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Unscoped names are valid for both local and npm
|
|
68
|
-
return { isValid: true }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Convert an unscoped plugin name to an npm package name
|
|
73
|
-
*
|
|
74
|
-
* Rules:
|
|
75
|
-
* - Unscoped names: prefix with @launch77-shared/plugin-
|
|
76
|
-
* - Scoped names: use as-is
|
|
77
|
-
*
|
|
78
|
-
* @param name - The plugin name (must be validated first)
|
|
79
|
-
* @returns The npm package name
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* toNpmPackageName('release') // '@launch77-shared/plugin-release'
|
|
83
|
-
* toNpmPackageName('@ibm/analytics') // '@ibm/analytics'
|
|
84
|
-
*/
|
|
85
|
-
export function toNpmPackageName(name: string): string {
|
|
86
|
-
const trimmedName = name.trim()
|
|
87
|
-
|
|
88
|
-
// If already scoped, use as-is
|
|
89
|
-
if (trimmedName.startsWith('@')) {
|
|
90
|
-
return trimmedName
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Otherwise, convert to @launch77-shared/plugin-<name>
|
|
94
|
-
return `@launch77-shared/plugin-${trimmedName}`
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Resolve plugin location from name
|
|
99
|
-
*
|
|
100
|
-
* Resolution order:
|
|
101
|
-
* 1. Check local workspace plugins directory
|
|
102
|
-
* 2. Resolve to npm package name
|
|
103
|
-
*
|
|
104
|
-
* @param name - The plugin name to resolve
|
|
105
|
-
* @param workspaceRoot - The workspace root directory
|
|
106
|
-
* @returns PluginResolution with source and resolved location
|
|
107
|
-
*
|
|
108
|
-
* @example
|
|
109
|
-
* // Local plugin found
|
|
110
|
-
* await resolvePluginLocation('my-plugin', '/workspace')
|
|
111
|
-
* // { source: 'local', resolvedName: 'my-plugin', localPath: '/workspace/plugins/my-plugin' }
|
|
112
|
-
*
|
|
113
|
-
* // Not found locally, resolve to npm
|
|
114
|
-
* await resolvePluginLocation('release', '/workspace')
|
|
115
|
-
* // { source: 'npm', resolvedName: 'release', npmPackage: '@launch77-shared/plugin-release' }
|
|
116
|
-
*
|
|
117
|
-
* // Scoped package always resolves to npm
|
|
118
|
-
* await resolvePluginLocation('@ibm/analytics', '/workspace')
|
|
119
|
-
* // { source: 'npm', resolvedName: '@ibm/analytics', npmPackage: '@ibm/analytics' }
|
|
120
|
-
*/
|
|
121
|
-
export async function resolvePluginLocation(name: string, workspaceRoot: string): Promise<PluginResolution> {
|
|
122
|
-
const trimmedName = name.trim()
|
|
123
|
-
const parsed = parsePluginName(trimmedName)
|
|
124
|
-
|
|
125
|
-
// If scoped, always use npm (local plugins are never scoped)
|
|
126
|
-
if (parsed.type === 'scoped') {
|
|
127
|
-
return {
|
|
128
|
-
source: 'npm',
|
|
129
|
-
resolvedName: trimmedName,
|
|
130
|
-
npmPackage: trimmedName,
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Check local workspace plugins directory
|
|
135
|
-
const localPath = path.join(workspaceRoot, 'plugins', trimmedName)
|
|
136
|
-
const localExists = await fs.pathExists(localPath)
|
|
137
|
-
|
|
138
|
-
if (localExists) {
|
|
23
|
+
protected async verify(localPath: string): Promise<boolean> {
|
|
139
24
|
// Verify it's a valid plugin (has plugin.json and dist/generator.js)
|
|
140
25
|
const hasPluginJson = await fs.pathExists(path.join(localPath, 'plugin.json'))
|
|
141
26
|
const hasGenerator = await fs.pathExists(path.join(localPath, 'dist/generator.js'))
|
|
142
27
|
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
source: 'local',
|
|
146
|
-
resolvedName: trimmedName,
|
|
147
|
-
localPath,
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Not found locally, resolve to npm package
|
|
153
|
-
const npmPackage = toNpmPackageName(trimmedName)
|
|
154
|
-
|
|
155
|
-
return {
|
|
156
|
-
source: 'npm',
|
|
157
|
-
resolvedName: trimmedName,
|
|
158
|
-
npmPackage,
|
|
28
|
+
return hasPluginJson && hasGenerator
|
|
159
29
|
}
|
|
160
30
|
}
|
|
@@ -5,8 +5,9 @@ import chalk from 'chalk'
|
|
|
5
5
|
import { execa } from 'execa'
|
|
6
6
|
import { readPluginMetadata } from '@launch77/plugin-runtime'
|
|
7
7
|
|
|
8
|
+
import { downloadNpmPackage } from '../../../infrastructure/npm-package.js'
|
|
8
9
|
import { PluginInstallationError, InvalidPluginContextError, createInvalidContextError, MissingPluginTargetsError, createInvalidTargetError, NpmInstallationError, PluginResolutionError } from '../errors/plugin-errors.js'
|
|
9
|
-
import {
|
|
10
|
+
import { PluginResolver } from '../lib/plugin-resolver.js'
|
|
10
11
|
|
|
11
12
|
import type { Launch77Context, Launch77LocationType, Launch77PackageManifest, InstalledPluginMetadata, PluginMetadata } from '@launch77/plugin-runtime'
|
|
12
13
|
import type { InstallPluginRequest, InstallPluginResult } from '../types/plugin-types.js'
|
|
@@ -30,6 +31,12 @@ function locationTypeToTarget(locationType: Launch77LocationType): string | null
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export class PluginService {
|
|
34
|
+
private pluginResolver: PluginResolver
|
|
35
|
+
|
|
36
|
+
constructor() {
|
|
37
|
+
this.pluginResolver = new PluginResolver()
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
/**
|
|
34
41
|
* Validate that we're in a valid package directory and return the target type
|
|
35
42
|
*/
|
|
@@ -47,14 +54,14 @@ export class PluginService {
|
|
|
47
54
|
logger(chalk.blue(`\nš Resolving plugin "${pluginName}"...`))
|
|
48
55
|
logger(` āā Validating plugin name...`)
|
|
49
56
|
|
|
50
|
-
const validation =
|
|
57
|
+
const validation = this.pluginResolver.validateInput(pluginName)
|
|
51
58
|
if (!validation.isValid) {
|
|
52
59
|
throw new PluginResolutionError(pluginName, validation.error || 'Invalid plugin name')
|
|
53
60
|
}
|
|
54
61
|
logger(` ā āā ${chalk.green('ā')} Valid plugin name`)
|
|
55
62
|
|
|
56
63
|
logger(` āā Checking local workspace: ${chalk.dim(`plugins/${pluginName}`)}`)
|
|
57
|
-
const resolution = await
|
|
64
|
+
const resolution = await this.pluginResolver.resolveLocation(pluginName, workspaceRoot)
|
|
58
65
|
|
|
59
66
|
let pluginPath: string
|
|
60
67
|
|
|
@@ -154,19 +161,12 @@ export class PluginService {
|
|
|
154
161
|
private async downloadNpmPlugin(npmPackage: string, workspaceRoot: string, logger: (message: string) => void): Promise<string> {
|
|
155
162
|
logger(` āā Installing from npm: ${chalk.cyan(npmPackage)}...`)
|
|
156
163
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
stdio: 'pipe', // Capture output for clean logging
|
|
162
|
-
})
|
|
164
|
+
const result = await downloadNpmPackage({
|
|
165
|
+
packageName: npmPackage,
|
|
166
|
+
workspaceRoot,
|
|
167
|
+
})
|
|
163
168
|
|
|
164
|
-
|
|
165
|
-
const pluginPath = path.join(workspaceRoot, 'node_modules', npmPackage)
|
|
166
|
-
return pluginPath
|
|
167
|
-
} catch (error) {
|
|
168
|
-
throw new NpmInstallationError(npmPackage, error instanceof Error ? error : undefined)
|
|
169
|
-
}
|
|
169
|
+
return result.packagePath
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
private getPackagePath(context: Launch77Context): string {
|