@camunda8/cli 2.0.1-alpha.2 → 2.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -8
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +66 -3
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +14 -1
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/plugins.d.ts +15 -1
- package/dist/commands/plugins.d.ts.map +1 -1
- package/dist/commands/plugins.js +429 -76
- package/dist/commands/plugins.js.map +1 -1
- package/dist/commands/process-instances.d.ts +1 -0
- package/dist/commands/process-instances.d.ts.map +1 -1
- package/dist/commands/process-instances.js +4 -0
- package/dist/commands/process-instances.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -1
- package/dist/default-plugins/hello-world/README.md +158 -0
- package/dist/default-plugins/hello-world/c8ctl-plugin.js +65 -0
- package/dist/default-plugins/hello-world/package.json +8 -0
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin-loader.d.ts +1 -1
- package/dist/plugin-loader.d.ts.map +1 -1
- package/dist/plugin-loader.js +81 -3
- package/dist/plugin-loader.js.map +1 -1
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +16 -0
- package/dist/runtime.js.map +1 -1
- package/package.json +3 -2
package/dist/commands/plugins.js
CHANGED
|
@@ -3,46 +3,43 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getLogger } from "../logger.js";
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
6
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { clearLoadedPlugins } from "../plugin-loader.js";
|
|
9
|
-
import {
|
|
9
|
+
import { ensurePluginsDir } from "../config.js";
|
|
10
|
+
import { addPluginToRegistry, removePluginFromRegistry, getRegisteredPlugins, isPluginRegistered, getPluginEntry, } from "../plugin-registry.js";
|
|
10
11
|
/**
|
|
11
12
|
* Load a plugin (npm install wrapper)
|
|
12
13
|
* Supports either package name or --from flag with URL
|
|
14
|
+
* Installs to global plugins directory
|
|
13
15
|
*/
|
|
14
16
|
export async function loadPlugin(packageNameOrFrom, fromUrl) {
|
|
15
17
|
const logger = getLogger();
|
|
16
18
|
// Validate exclusive usage
|
|
17
19
|
if (packageNameOrFrom && fromUrl) {
|
|
18
|
-
logger.error('Cannot specify both package name and --from flag. Use either "
|
|
20
|
+
logger.error('Cannot specify both package name and --from flag. Use either "c8ctl load plugin <name>" or "c8ctl load plugin --from <url>"');
|
|
19
21
|
process.exit(1);
|
|
20
22
|
}
|
|
21
23
|
if (!packageNameOrFrom && !fromUrl) {
|
|
22
|
-
logger.error('Package name or --from URL required. Usage:
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
// Check if we have package.json in current directory
|
|
26
|
-
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
27
|
-
if (!existsSync(packageJsonPath)) {
|
|
28
|
-
logger.error('No package.json found in current directory.');
|
|
29
|
-
logger.info('💡 Actionable hint: Run "npm init -y" to create a package.json, or navigate to a directory with an existing package.json');
|
|
24
|
+
logger.error('Package name or --from URL required. Usage: c8ctl load plugin <package-name> OR c8ctl load plugin --from <url>');
|
|
30
25
|
process.exit(1);
|
|
31
26
|
}
|
|
27
|
+
// Get global plugins directory
|
|
28
|
+
const pluginsDir = ensurePluginsDir();
|
|
32
29
|
try {
|
|
33
30
|
let pluginName;
|
|
34
31
|
let pluginSource;
|
|
35
32
|
if (fromUrl) {
|
|
36
33
|
// Install from URL (file://, https://, git://, etc.)
|
|
37
34
|
logger.info(`Loading plugin from: ${fromUrl}...`);
|
|
38
|
-
execSync(`npm install ${fromUrl}`, { stdio: 'inherit' });
|
|
39
|
-
// Extract package name from
|
|
40
|
-
pluginName = extractPackageNameFromUrl(fromUrl);
|
|
35
|
+
execSync(`npm install ${fromUrl} --prefix "${pluginsDir}"`, { stdio: 'inherit' });
|
|
36
|
+
// Extract package name from installed package
|
|
37
|
+
pluginName = extractPackageNameFromUrl(fromUrl, pluginsDir);
|
|
41
38
|
pluginSource = fromUrl;
|
|
42
39
|
// Validate plugin name
|
|
43
40
|
if (!pluginName || pluginName.trim() === '') {
|
|
44
41
|
logger.error('Failed to extract plugin name from URL');
|
|
45
|
-
logger.info('
|
|
42
|
+
logger.info('Ensure the URL points to a valid npm package with a package.json file');
|
|
46
43
|
process.exit(1);
|
|
47
44
|
}
|
|
48
45
|
logger.success('Plugin loaded successfully from URL', fromUrl);
|
|
@@ -50,7 +47,7 @@ export async function loadPlugin(packageNameOrFrom, fromUrl) {
|
|
|
50
47
|
else {
|
|
51
48
|
// Install from npm registry by package name
|
|
52
49
|
logger.info(`Loading plugin: ${packageNameOrFrom}...`);
|
|
53
|
-
execSync(`npm install ${packageNameOrFrom}`, { stdio: 'inherit' });
|
|
50
|
+
execSync(`npm install ${packageNameOrFrom} --prefix "${pluginsDir}"`, { stdio: 'inherit' });
|
|
54
51
|
pluginName = packageNameOrFrom;
|
|
55
52
|
pluginSource = packageNameOrFrom;
|
|
56
53
|
logger.success('Plugin loaded successfully', packageNameOrFrom);
|
|
@@ -64,39 +61,112 @@ export async function loadPlugin(packageNameOrFrom, fromUrl) {
|
|
|
64
61
|
}
|
|
65
62
|
catch (error) {
|
|
66
63
|
logger.error('Failed to load plugin', error);
|
|
67
|
-
logger.info('
|
|
64
|
+
logger.info('Check that the plugin name/URL is correct and you have network access if loading from a remote source');
|
|
68
65
|
process.exit(1);
|
|
69
66
|
}
|
|
70
67
|
}
|
|
71
68
|
/**
|
|
72
|
-
*
|
|
73
|
-
* This is a best-effort extraction - for complex cases, the user may need to specify manually
|
|
74
|
-
* Note: This doesn't handle all edge cases like scoped packages in git URLs
|
|
69
|
+
* Check if a package has a c8ctl plugin file
|
|
75
70
|
*/
|
|
76
|
-
function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
function hasPluginFile(packagePath) {
|
|
72
|
+
return existsSync(join(packagePath, 'c8ctl-plugin.js')) ||
|
|
73
|
+
existsSync(join(packagePath, 'c8ctl-plugin.ts'));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if a package is a valid c8ctl plugin
|
|
77
|
+
*/
|
|
78
|
+
function isValidPlugin(pkgPath) {
|
|
79
|
+
const pkgJsonPath = join(pkgPath, 'package.json');
|
|
80
|
+
if (!existsSync(pkgJsonPath))
|
|
81
|
+
return false;
|
|
82
|
+
try {
|
|
83
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
84
|
+
return hasPluginFile(pkgPath) && pkgJson.keywords?.includes('c8ctl');
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get package name from a valid plugin directory
|
|
92
|
+
*/
|
|
93
|
+
function getPackageName(pkgPath) {
|
|
94
|
+
const pkgJsonPath = join(pkgPath, 'package.json');
|
|
95
|
+
try {
|
|
96
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'));
|
|
97
|
+
return pkgJson.name;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
84
101
|
}
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Scan directory entries for c8ctl plugins
|
|
105
|
+
*/
|
|
106
|
+
function scanForPlugin(nodeModulesPath, entries) {
|
|
107
|
+
for (const entry of entries.filter(e => !e.startsWith('.'))) {
|
|
108
|
+
const pkgPath = entry.startsWith('@')
|
|
109
|
+
? null // Scoped packages handled separately
|
|
110
|
+
: join(nodeModulesPath, entry);
|
|
111
|
+
if (pkgPath && isValidPlugin(pkgPath)) {
|
|
112
|
+
return getPackageName(pkgPath);
|
|
113
|
+
}
|
|
114
|
+
// Handle scoped packages
|
|
115
|
+
if (entry.startsWith('@')) {
|
|
116
|
+
const scopePath = join(nodeModulesPath, entry);
|
|
117
|
+
try {
|
|
118
|
+
const scopedPackages = readdirSync(scopePath);
|
|
119
|
+
for (const scopedPkg of scopedPackages) {
|
|
120
|
+
const pkgPath = join(scopePath, scopedPkg);
|
|
121
|
+
if (isValidPlugin(pkgPath)) {
|
|
122
|
+
return getPackageName(pkgPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Skip scoped packages that can't be read
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Extract package name from URL or installed package
|
|
135
|
+
* Tries to read package.json from installed package, falls back to URL parsing
|
|
136
|
+
*/
|
|
137
|
+
function extractPackageNameFromUrl(url, pluginsDir) {
|
|
138
|
+
// Try to scan node_modules to find the package by reading package.json
|
|
139
|
+
try {
|
|
140
|
+
const nodeModulesPath = join(pluginsDir, 'node_modules');
|
|
141
|
+
if (existsSync(nodeModulesPath)) {
|
|
142
|
+
const entries = readdirSync(nodeModulesPath);
|
|
143
|
+
const foundName = scanForPlugin(nodeModulesPath, entries);
|
|
144
|
+
if (foundName)
|
|
145
|
+
return foundName;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// Fall through to URL-based name extraction
|
|
150
|
+
}
|
|
151
|
+
// Fallback: extract from URL pattern
|
|
152
|
+
const match = url.match(/\/([^\/]+?)(\.git)?$/);
|
|
153
|
+
return match ? match[1] : url.replace(/[^a-zA-Z0-9-_@\/]/g, '-');
|
|
87
154
|
}
|
|
88
155
|
/**
|
|
89
156
|
* Unload a plugin (npm uninstall wrapper)
|
|
157
|
+
* Uninstalls from global plugins directory
|
|
90
158
|
*/
|
|
91
159
|
export async function unloadPlugin(packageName) {
|
|
92
160
|
const logger = getLogger();
|
|
93
161
|
if (!packageName) {
|
|
94
|
-
logger.error('Package name required. Usage:
|
|
162
|
+
logger.error('Package name required. Usage: c8ctl unload plugin <package-name>');
|
|
95
163
|
process.exit(1);
|
|
96
164
|
}
|
|
165
|
+
// Get global plugins directory
|
|
166
|
+
const pluginsDir = ensurePluginsDir();
|
|
97
167
|
try {
|
|
98
168
|
logger.info(`Unloading plugin: ${packageName}...`);
|
|
99
|
-
execSync(`npm uninstall ${packageName}`, { stdio: 'inherit' });
|
|
169
|
+
execSync(`npm uninstall ${packageName} --prefix "${pluginsDir}"`, { stdio: 'inherit' });
|
|
100
170
|
// Only remove from registry after successful uninstall
|
|
101
171
|
removePluginFromRegistry(packageName);
|
|
102
172
|
logger.debug(`Removed ${packageName} from plugin registry`);
|
|
@@ -108,10 +178,59 @@ export async function unloadPlugin(packageName) {
|
|
|
108
178
|
}
|
|
109
179
|
catch (error) {
|
|
110
180
|
logger.error('Failed to unload plugin', error);
|
|
111
|
-
logger.info('
|
|
181
|
+
logger.info('Verify the plugin name is correct by running "c8ctl list plugins"');
|
|
112
182
|
process.exit(1);
|
|
113
183
|
}
|
|
114
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Scan a directory entry for c8ctl plugins and add to the set
|
|
187
|
+
*/
|
|
188
|
+
function addPluginIfFound(entry, nodeModulesPath, installedPlugins) {
|
|
189
|
+
if (entry.startsWith('.'))
|
|
190
|
+
return;
|
|
191
|
+
if (entry.startsWith('@')) {
|
|
192
|
+
// Scoped package - scan subdirectories
|
|
193
|
+
const scopePath = join(nodeModulesPath, entry);
|
|
194
|
+
try {
|
|
195
|
+
readdirSync(scopePath)
|
|
196
|
+
.filter(pkg => !pkg.startsWith('.'))
|
|
197
|
+
.forEach(scopedPkg => {
|
|
198
|
+
const packageNameWithScope = `${entry}/${scopedPkg}`;
|
|
199
|
+
const packagePath = join(nodeModulesPath, entry, scopedPkg);
|
|
200
|
+
if (hasPluginFile(packagePath)) {
|
|
201
|
+
installedPlugins.add(packageNameWithScope);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
// Skip packages that can't be read
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
// Regular package
|
|
211
|
+
const packagePath = join(nodeModulesPath, entry);
|
|
212
|
+
if (hasPluginFile(packagePath)) {
|
|
213
|
+
installedPlugins.add(entry);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Scan node_modules for installed plugins
|
|
219
|
+
*/
|
|
220
|
+
function scanInstalledPlugins(nodeModulesPath) {
|
|
221
|
+
const installedPlugins = new Set();
|
|
222
|
+
if (!existsSync(nodeModulesPath)) {
|
|
223
|
+
return installedPlugins;
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const entries = readdirSync(nodeModulesPath);
|
|
227
|
+
entries.forEach(entry => addPluginIfFound(entry, nodeModulesPath, installedPlugins));
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
getLogger().debug('Error scanning global plugins directory:', error);
|
|
231
|
+
}
|
|
232
|
+
return installedPlugins;
|
|
233
|
+
}
|
|
115
234
|
/**
|
|
116
235
|
* List installed plugins
|
|
117
236
|
*/
|
|
@@ -120,34 +239,15 @@ export function listPlugins() {
|
|
|
120
239
|
try {
|
|
121
240
|
// Get plugins from registry (local source of truth)
|
|
122
241
|
const registeredPlugins = getRegisteredPlugins();
|
|
123
|
-
// Check
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
128
|
-
const dependencies = packageJson.dependencies || {};
|
|
129
|
-
const devDependencies = packageJson.devDependencies || {};
|
|
130
|
-
const allDeps = { ...dependencies, ...devDependencies };
|
|
131
|
-
// Find c8ctl plugins in package.json
|
|
132
|
-
for (const [name] of Object.entries(allDeps)) {
|
|
133
|
-
try {
|
|
134
|
-
const packageDir = join(process.cwd(), 'node_modules', name);
|
|
135
|
-
const hasPluginFile = existsSync(join(packageDir, 'c8ctl-plugin.js')) ||
|
|
136
|
-
existsSync(join(packageDir, 'c8ctl-plugin.ts'));
|
|
137
|
-
if (hasPluginFile) {
|
|
138
|
-
packageJsonPlugins.add(name);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
// Skip packages that can't be read
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
242
|
+
// Check global plugins directory
|
|
243
|
+
const pluginsDir = ensurePluginsDir();
|
|
244
|
+
const nodeModulesPath = join(pluginsDir, 'node_modules');
|
|
245
|
+
const installedPlugins = scanInstalledPlugins(nodeModulesPath);
|
|
146
246
|
// Build unified list with status
|
|
147
247
|
const plugins = [];
|
|
148
248
|
// Add registered plugins
|
|
149
249
|
for (const plugin of registeredPlugins) {
|
|
150
|
-
const isInstalled =
|
|
250
|
+
const isInstalled = installedPlugins.has(plugin.name);
|
|
151
251
|
const installStatus = isInstalled ? '✓ Installed' : '⚠ Not installed';
|
|
152
252
|
plugins.push({
|
|
153
253
|
Name: plugin.name,
|
|
@@ -155,14 +255,14 @@ export function listPlugins() {
|
|
|
155
255
|
Source: plugin.source,
|
|
156
256
|
'Installed At': new Date(plugin.installedAt).toLocaleString(),
|
|
157
257
|
});
|
|
158
|
-
|
|
258
|
+
installedPlugins.delete(plugin.name);
|
|
159
259
|
}
|
|
160
|
-
// Add any plugins
|
|
161
|
-
for (const name of
|
|
260
|
+
// Add any plugins installed but not in registry
|
|
261
|
+
for (const name of installedPlugins) {
|
|
162
262
|
plugins.push({
|
|
163
263
|
Name: name,
|
|
164
264
|
Status: '⚠ Not in registry',
|
|
165
|
-
Source: '
|
|
265
|
+
Source: 'Unknown',
|
|
166
266
|
'Installed At': 'Unknown',
|
|
167
267
|
});
|
|
168
268
|
}
|
|
@@ -175,28 +275,23 @@ export function listPlugins() {
|
|
|
175
275
|
logger.table(plugins);
|
|
176
276
|
if (needsSync) {
|
|
177
277
|
logger.info('');
|
|
178
|
-
logger.info('
|
|
278
|
+
logger.info('Some plugins are out of sync. Run "c8ctl sync plugins" to synchronize your plugins');
|
|
179
279
|
}
|
|
180
280
|
}
|
|
181
281
|
catch (error) {
|
|
182
282
|
logger.error('Failed to list plugins', error);
|
|
183
|
-
logger.info('💡 Actionable hint: Ensure you are in a directory with a package.json file');
|
|
184
283
|
process.exit(1);
|
|
185
284
|
}
|
|
186
285
|
}
|
|
187
286
|
/**
|
|
188
287
|
* Sync plugins - synchronize registry with actual installations
|
|
189
|
-
*
|
|
288
|
+
* Registry has precedence - plugins are installed to global directory
|
|
190
289
|
*/
|
|
191
290
|
export async function syncPlugins() {
|
|
192
291
|
const logger = getLogger();
|
|
193
|
-
//
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
logger.error('No package.json found in current directory.');
|
|
197
|
-
logger.info('💡 Actionable hint: Run "npm init -y" to create a package.json, or navigate to a directory with an existing package.json');
|
|
198
|
-
process.exit(1);
|
|
199
|
-
}
|
|
292
|
+
// Get global plugins directory
|
|
293
|
+
const pluginsDir = ensurePluginsDir();
|
|
294
|
+
const nodeModulesPath = join(pluginsDir, 'node_modules');
|
|
200
295
|
logger.info('Starting plugin synchronization...');
|
|
201
296
|
logger.info('');
|
|
202
297
|
// Get registered plugins (local source of truth)
|
|
@@ -217,14 +312,14 @@ export async function syncPlugins() {
|
|
|
217
312
|
for (const plugin of registeredPlugins) {
|
|
218
313
|
logger.info(`Syncing ${plugin.name}...`);
|
|
219
314
|
try {
|
|
220
|
-
// Check if plugin is installed
|
|
221
|
-
const packageDir = join(
|
|
315
|
+
// Check if plugin is installed in global directory
|
|
316
|
+
const packageDir = join(nodeModulesPath, plugin.name);
|
|
222
317
|
const isInstalled = existsSync(packageDir);
|
|
223
318
|
if (isInstalled) {
|
|
224
319
|
logger.info(` ✓ ${plugin.name} is already installed, attempting rebuild...`);
|
|
225
320
|
// Try npm rebuild first
|
|
226
321
|
try {
|
|
227
|
-
execSync(`npm rebuild ${plugin.name}`, { stdio: 'pipe' });
|
|
322
|
+
execSync(`npm rebuild ${plugin.name} --prefix "${pluginsDir}"`, { stdio: 'pipe' });
|
|
228
323
|
logger.success(` ✓ ${plugin.name} rebuilt successfully`);
|
|
229
324
|
syncedCount++;
|
|
230
325
|
continue;
|
|
@@ -238,7 +333,7 @@ export async function syncPlugins() {
|
|
|
238
333
|
}
|
|
239
334
|
// Fresh install
|
|
240
335
|
try {
|
|
241
|
-
execSync(`npm install ${plugin.source}`, { stdio: 'inherit' });
|
|
336
|
+
execSync(`npm install ${plugin.source} --prefix "${pluginsDir}"`, { stdio: 'inherit' });
|
|
242
337
|
logger.success(` ✓ ${plugin.name} installed successfully`);
|
|
243
338
|
syncedCount++;
|
|
244
339
|
}
|
|
@@ -272,9 +367,267 @@ export async function syncPlugins() {
|
|
|
272
367
|
logger.error(` - ${failure.plugin}: ${failure.error}`);
|
|
273
368
|
}
|
|
274
369
|
logger.info('');
|
|
275
|
-
logger.info('
|
|
370
|
+
logger.info('Check network connectivity and verify plugin sources are accessible. You may need to remove failed plugins from the registry with "c8ctl unload plugin <name>"');
|
|
276
371
|
process.exit(1);
|
|
277
372
|
}
|
|
278
373
|
logger.success('All plugins synced successfully!');
|
|
279
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Upgrade a plugin to the latest version or a specific version
|
|
377
|
+
*/
|
|
378
|
+
export async function upgradePlugin(packageName, version) {
|
|
379
|
+
const logger = getLogger();
|
|
380
|
+
if (!packageName) {
|
|
381
|
+
logger.error('Package name required. Usage: c8ctl upgrade plugin <package-name> [version]');
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
// Check if plugin is registered
|
|
385
|
+
if (!isPluginRegistered(packageName)) {
|
|
386
|
+
logger.error(`Plugin "${packageName}" is not registered.`);
|
|
387
|
+
logger.info('Run "c8ctl list plugins" to see installed plugins');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
const pluginEntry = getPluginEntry(packageName);
|
|
391
|
+
const pluginsDir = ensurePluginsDir();
|
|
392
|
+
try {
|
|
393
|
+
const versionSpec = version ? `@${version}` : '@latest';
|
|
394
|
+
logger.info(`Upgrading plugin: ${packageName} to ${version || 'latest'}...`);
|
|
395
|
+
// Uninstall current version
|
|
396
|
+
execSync(`npm uninstall ${packageName} --prefix "${pluginsDir}"`, { stdio: 'pipe' });
|
|
397
|
+
// Install new version
|
|
398
|
+
const installTarget = version ? `${packageName}${versionSpec}` : pluginEntry.source;
|
|
399
|
+
execSync(`npm install ${installTarget} --prefix "${pluginsDir}"`, { stdio: 'inherit' });
|
|
400
|
+
// Update registry with new source if version was specified
|
|
401
|
+
if (version) {
|
|
402
|
+
addPluginToRegistry(packageName, `${packageName}${versionSpec}`);
|
|
403
|
+
}
|
|
404
|
+
// Clear plugin cache
|
|
405
|
+
clearLoadedPlugins();
|
|
406
|
+
logger.success('Plugin upgraded successfully', packageName);
|
|
407
|
+
logger.info('Plugin will be available on next command execution');
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
logger.error('Failed to upgrade plugin', error);
|
|
411
|
+
logger.info('Check network connectivity and verify the package/version exists');
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Downgrade a plugin to a specific version
|
|
417
|
+
*/
|
|
418
|
+
export async function downgradePlugin(packageName, version) {
|
|
419
|
+
const logger = getLogger();
|
|
420
|
+
if (!packageName || !version) {
|
|
421
|
+
logger.error('Package name and version required. Usage: c8ctl downgrade plugin <package-name> <version>');
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
// Check if plugin is registered
|
|
425
|
+
if (!isPluginRegistered(packageName)) {
|
|
426
|
+
logger.error(`Plugin "${packageName}" is not registered.`);
|
|
427
|
+
logger.info('Run "c8ctl list plugins" to see installed plugins');
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
const pluginsDir = ensurePluginsDir();
|
|
431
|
+
try {
|
|
432
|
+
logger.info(`Downgrading plugin: ${packageName} to version ${version}...`);
|
|
433
|
+
// Uninstall current version
|
|
434
|
+
execSync(`npm uninstall ${packageName} --prefix "${pluginsDir}"`, { stdio: 'pipe' });
|
|
435
|
+
// Install specific version
|
|
436
|
+
const installTarget = `${packageName}@${version}`;
|
|
437
|
+
execSync(`npm install ${installTarget} --prefix "${pluginsDir}"`, { stdio: 'inherit' });
|
|
438
|
+
// Update registry with new source
|
|
439
|
+
addPluginToRegistry(packageName, installTarget);
|
|
440
|
+
// Clear plugin cache
|
|
441
|
+
clearLoadedPlugins();
|
|
442
|
+
logger.success('Plugin downgraded successfully', packageName);
|
|
443
|
+
logger.info('Plugin will be available on next command execution');
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
logger.error('Failed to downgrade plugin', error);
|
|
447
|
+
logger.info('Check network connectivity and verify the version exists');
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Initialize a new plugin project with TypeScript template
|
|
453
|
+
*/
|
|
454
|
+
export async function initPlugin(pluginName) {
|
|
455
|
+
const logger = getLogger();
|
|
456
|
+
const { mkdirSync, writeFileSync } = await import('node:fs');
|
|
457
|
+
const { resolve } = await import('node:path');
|
|
458
|
+
// Use provided name or default
|
|
459
|
+
const name = pluginName || 'my-c8ctl-plugin';
|
|
460
|
+
const dirName = name.startsWith('c8ctl-') ? name : `c8ctl-${name}`;
|
|
461
|
+
const pluginDir = resolve(process.cwd(), dirName);
|
|
462
|
+
// Check if directory already exists
|
|
463
|
+
if (existsSync(pluginDir)) {
|
|
464
|
+
logger.error(`Directory "${dirName}" already exists.`);
|
|
465
|
+
logger.info('Choose a different name or remove the existing directory');
|
|
466
|
+
process.exit(1);
|
|
467
|
+
}
|
|
468
|
+
try {
|
|
469
|
+
logger.info(`Creating plugin: ${dirName}...`);
|
|
470
|
+
// Create plugin directory
|
|
471
|
+
mkdirSync(pluginDir, { recursive: true });
|
|
472
|
+
// Create package.json
|
|
473
|
+
const packageJson = {
|
|
474
|
+
name: dirName,
|
|
475
|
+
version: '1.0.0',
|
|
476
|
+
type: 'module',
|
|
477
|
+
description: `A c8ctl plugin`,
|
|
478
|
+
keywords: ['c8ctl', 'c8ctl-plugin'],
|
|
479
|
+
main: 'c8ctl-plugin.js',
|
|
480
|
+
scripts: {
|
|
481
|
+
build: 'tsc',
|
|
482
|
+
watch: 'tsc --watch',
|
|
483
|
+
},
|
|
484
|
+
devDependencies: {
|
|
485
|
+
typescript: '^5.0.0',
|
|
486
|
+
'@types/node': '^22.0.0',
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
writeFileSync(join(pluginDir, 'package.json'), JSON.stringify(packageJson, null, 2));
|
|
490
|
+
// Create tsconfig.json
|
|
491
|
+
const tsConfig = {
|
|
492
|
+
compilerOptions: {
|
|
493
|
+
target: 'ES2022',
|
|
494
|
+
module: 'ES2022',
|
|
495
|
+
moduleResolution: 'node',
|
|
496
|
+
outDir: '.',
|
|
497
|
+
rootDir: './src',
|
|
498
|
+
strict: true,
|
|
499
|
+
esModuleInterop: true,
|
|
500
|
+
skipLibCheck: true,
|
|
501
|
+
forceConsistentCasingInFileNames: true,
|
|
502
|
+
},
|
|
503
|
+
include: ['src/**/*'],
|
|
504
|
+
exclude: ['node_modules'],
|
|
505
|
+
};
|
|
506
|
+
writeFileSync(join(pluginDir, 'tsconfig.json'), JSON.stringify(tsConfig, null, 2));
|
|
507
|
+
// Create src directory
|
|
508
|
+
mkdirSync(join(pluginDir, 'src'), { recursive: true });
|
|
509
|
+
// Create c8ctl-plugin.ts
|
|
510
|
+
const pluginTemplate = `/**
|
|
511
|
+
* ${dirName} - A c8ctl plugin
|
|
512
|
+
*/
|
|
513
|
+
|
|
514
|
+
// The c8ctl runtime is available globally
|
|
515
|
+
declare const c8ctl: {
|
|
516
|
+
version: string;
|
|
517
|
+
nodeVersion: string;
|
|
518
|
+
platform: string;
|
|
519
|
+
arch: string;
|
|
520
|
+
cwd: string;
|
|
521
|
+
outputMode: 'text' | 'json';
|
|
522
|
+
activeProfile?: string;
|
|
523
|
+
activeTenant?: string;
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
// Optional metadata for help text
|
|
527
|
+
export const metadata = {
|
|
528
|
+
name: '${dirName}',
|
|
529
|
+
description: 'A c8ctl plugin',
|
|
530
|
+
commands: {
|
|
531
|
+
hello: {
|
|
532
|
+
description: 'Say hello from the plugin',
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// Required commands export
|
|
538
|
+
export const commands = {
|
|
539
|
+
hello: async (args: string[]) => {
|
|
540
|
+
console.log('Hello from ${dirName}!');
|
|
541
|
+
console.log('c8ctl version:', c8ctl.version);
|
|
542
|
+
console.log('Node version:', c8ctl.nodeVersion);
|
|
543
|
+
|
|
544
|
+
if (args.length > 0) {
|
|
545
|
+
console.log('Arguments:', args.join(', '));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Example: Access c8ctl runtime
|
|
549
|
+
console.log('Current directory:', c8ctl.cwd);
|
|
550
|
+
console.log('Output mode:', c8ctl.outputMode);
|
|
551
|
+
|
|
552
|
+
if (c8ctl.activeProfile) {
|
|
553
|
+
console.log('Active profile:', c8ctl.activeProfile);
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
};
|
|
557
|
+
`;
|
|
558
|
+
writeFileSync(join(pluginDir, 'src', 'c8ctl-plugin.ts'), pluginTemplate);
|
|
559
|
+
// Create README.md
|
|
560
|
+
const readme = `# ${dirName}
|
|
561
|
+
|
|
562
|
+
A c8ctl plugin.
|
|
563
|
+
|
|
564
|
+
## Development
|
|
565
|
+
|
|
566
|
+
1. Install dependencies:
|
|
567
|
+
\`\`\`bash
|
|
568
|
+
npm install
|
|
569
|
+
\`\`\`
|
|
570
|
+
|
|
571
|
+
2. Build the plugin:
|
|
572
|
+
\`\`\`bash
|
|
573
|
+
npm run build
|
|
574
|
+
\`\`\`
|
|
575
|
+
|
|
576
|
+
3. Load the plugin for testing:
|
|
577
|
+
\`\`\`bash
|
|
578
|
+
c8ctl load plugin --from file://\${PWD}
|
|
579
|
+
\`\`\`
|
|
580
|
+
|
|
581
|
+
4. Test the plugin command:
|
|
582
|
+
\`\`\`bash
|
|
583
|
+
c8ctl hello
|
|
584
|
+
\`\`\`
|
|
585
|
+
|
|
586
|
+
## Plugin Structure
|
|
587
|
+
|
|
588
|
+
- \`src/c8ctl-plugin.ts\` - Plugin source code (TypeScript)
|
|
589
|
+
- \`c8ctl-plugin.js\` - Compiled plugin file (JavaScript)
|
|
590
|
+
- \`package.json\` - Package metadata with c8ctl keywords
|
|
591
|
+
|
|
592
|
+
## Publishing
|
|
593
|
+
|
|
594
|
+
Before publishing, ensure:
|
|
595
|
+
- The plugin is built (\`npm run build\`)
|
|
596
|
+
- The package.json has correct metadata
|
|
597
|
+
- Keywords include 'c8ctl' or 'c8ctl-plugin'
|
|
598
|
+
|
|
599
|
+
Then publish to npm:
|
|
600
|
+
\`\`\`bash
|
|
601
|
+
npm publish
|
|
602
|
+
\`\`\`
|
|
603
|
+
|
|
604
|
+
Users can install your plugin with:
|
|
605
|
+
\`\`\`bash
|
|
606
|
+
c8ctl load plugin ${dirName}
|
|
607
|
+
\`\`\`
|
|
608
|
+
`;
|
|
609
|
+
writeFileSync(join(pluginDir, 'README.md'), readme);
|
|
610
|
+
// Create .gitignore
|
|
611
|
+
const gitignore = `node_modules/
|
|
612
|
+
*.js
|
|
613
|
+
*.js.map
|
|
614
|
+
!c8ctl-plugin.js
|
|
615
|
+
`;
|
|
616
|
+
writeFileSync(join(pluginDir, '.gitignore'), gitignore);
|
|
617
|
+
logger.success('Plugin scaffolding created successfully!');
|
|
618
|
+
logger.info('');
|
|
619
|
+
logger.info(`Next steps:`);
|
|
620
|
+
logger.info(` 1. cd ${dirName}`);
|
|
621
|
+
logger.info(` 2. npm install`);
|
|
622
|
+
logger.info(` 3. npm run build`);
|
|
623
|
+
logger.info(` 4. c8ctl load plugin --from file://\${PWD}`);
|
|
624
|
+
logger.info(` 5. c8ctl hello`);
|
|
625
|
+
logger.info('');
|
|
626
|
+
logger.info(`Edit src/c8ctl-plugin.ts to add your plugin logic.`);
|
|
627
|
+
}
|
|
628
|
+
catch (error) {
|
|
629
|
+
logger.error('Failed to create plugin', error);
|
|
630
|
+
process.exit(1);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
280
633
|
//# sourceMappingURL=plugins.js.map
|