vcluster-yaml-mcp-server 1.3.2 → 1.4.0
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/cli-handlers.d.ts +22 -0
- package/dist/cli-handlers.js +258 -0
- package/dist/cli-utils.d.ts +18 -0
- package/dist/cli-utils.js +52 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +201 -0
- package/dist/completions.d.ts +17 -0
- package/{src → dist}/completions.js +10 -18
- package/dist/formatters.d.ts +54 -0
- package/dist/formatters.js +151 -0
- package/dist/github.d.ts +15 -0
- package/dist/github.js +195 -0
- package/dist/http-server.d.ts +3 -0
- package/dist/http-server.js +158 -0
- package/dist/index.d.ts +3 -0
- package/{src → dist}/index.js +2 -4
- package/dist/instrumentation.d.ts +2 -0
- package/{src → dist}/instrumentation.js +16 -21
- package/dist/middleware/auth.d.ts +7 -0
- package/dist/middleware/auth.js +38 -0
- package/dist/schema-validator.d.ts +71 -0
- package/dist/schema-validator.js +329 -0
- package/dist/server-info.d.ts +38 -0
- package/dist/server-info.js +137 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +125 -0
- package/dist/snippet-validator.d.ts +35 -0
- package/dist/snippet-validator.js +330 -0
- package/dist/tool-handlers.d.ts +102 -0
- package/dist/tool-handlers.js +349 -0
- package/dist/tool-registry.d.ts +20 -0
- package/dist/tool-registry.js +35 -0
- package/dist/types/index.d.ts +168 -0
- package/dist/types/index.js +5 -0
- package/dist/validation-rules.d.ts +10 -0
- package/dist/validation-rules.js +158 -0
- package/package.json +28 -14
- package/src/cli-handlers.js +0 -290
- package/src/cli-utils.js +0 -62
- package/src/cli.js +0 -219
- package/src/formatters.js +0 -168
- package/src/github.js +0 -220
- package/src/http-server.js +0 -178
- package/src/middleware/auth.js +0 -42
- package/src/schema-validator.js +0 -380
- package/src/server-info.js +0 -153
- package/src/server.js +0 -180
- package/src/snippet-validator.js +0 -382
- package/src/snippet-validator.test.js +0 -366
- package/src/tool-handlers.js +0 -408
- package/src/tool-registry.js +0 -45
- package/src/validation-rules.js +0 -181
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI handlers that bridge CLI commands to underlying logic
|
|
3
|
+
* Reuses githubClient and validation logic from server.js
|
|
4
|
+
* Returns structured data for CLI formatters
|
|
5
|
+
*/
|
|
6
|
+
import type { CliQueryOptions, CliValidateOptions, CliResult } from './types/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* Handle query command
|
|
9
|
+
* Searches for configuration fields in vCluster YAML
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleQuery(query: string, options: CliQueryOptions): Promise<CliResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Handle list-versions command
|
|
14
|
+
* Lists available vCluster versions from GitHub
|
|
15
|
+
*/
|
|
16
|
+
export declare function handleListVersions(): Promise<CliResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Handle validate command
|
|
19
|
+
* Validates vCluster YAML configuration
|
|
20
|
+
*/
|
|
21
|
+
export declare function handleValidate(content: string, options: CliValidateOptions): Promise<CliResult>;
|
|
22
|
+
//# sourceMappingURL=cli-handlers.d.ts.map
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI handlers that bridge CLI commands to underlying logic
|
|
3
|
+
* Reuses githubClient and validation logic from server.js
|
|
4
|
+
* Returns structured data for CLI formatters
|
|
5
|
+
*/
|
|
6
|
+
import yaml from 'js-yaml';
|
|
7
|
+
import { githubClient } from './github.js';
|
|
8
|
+
import { validateSnippet } from './snippet-validator.js';
|
|
9
|
+
// Helper function to get the type of a value
|
|
10
|
+
function getType(value) {
|
|
11
|
+
if (value === null)
|
|
12
|
+
return 'null';
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return 'array';
|
|
15
|
+
if (typeof value === 'object')
|
|
16
|
+
return 'object';
|
|
17
|
+
if (typeof value === 'number') {
|
|
18
|
+
return Number.isInteger(value) ? 'integer' : 'number';
|
|
19
|
+
}
|
|
20
|
+
return typeof value;
|
|
21
|
+
}
|
|
22
|
+
// Helper function to rank search results
|
|
23
|
+
function rankResult(item, searchTerm) {
|
|
24
|
+
const pathLower = item.path.toLowerCase();
|
|
25
|
+
const keyLower = item.key.toLowerCase();
|
|
26
|
+
let score = 0;
|
|
27
|
+
// Exact match
|
|
28
|
+
if (pathLower === searchTerm || keyLower === searchTerm) {
|
|
29
|
+
score += 100;
|
|
30
|
+
}
|
|
31
|
+
// Starts with search term
|
|
32
|
+
if (pathLower.startsWith(searchTerm) || keyLower.startsWith(searchTerm)) {
|
|
33
|
+
score += 50;
|
|
34
|
+
}
|
|
35
|
+
// Ends with search term
|
|
36
|
+
if (pathLower.endsWith(searchTerm) || keyLower.endsWith(searchTerm)) {
|
|
37
|
+
score += 40;
|
|
38
|
+
}
|
|
39
|
+
// Contains search term
|
|
40
|
+
if (pathLower.includes(searchTerm) || keyLower.includes(searchTerm)) {
|
|
41
|
+
score += 30;
|
|
42
|
+
}
|
|
43
|
+
// Prefer leaf nodes (actual values)
|
|
44
|
+
if (item.isLeaf) {
|
|
45
|
+
score += 10;
|
|
46
|
+
}
|
|
47
|
+
// Prefer shorter paths (more specific)
|
|
48
|
+
const pathDepth = item.path.split('.').length;
|
|
49
|
+
score -= pathDepth;
|
|
50
|
+
return score;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Handle query command
|
|
54
|
+
* Searches for configuration fields in vCluster YAML
|
|
55
|
+
*/
|
|
56
|
+
export async function handleQuery(query, options) {
|
|
57
|
+
const version = options.version || 'main';
|
|
58
|
+
const fileName = options.file || 'chart/values.yaml';
|
|
59
|
+
let yamlData;
|
|
60
|
+
try {
|
|
61
|
+
yamlData = await githubClient.getYamlContent(fileName, version);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: `Could not load ${fileName} from GitHub (version: ${version}). Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
67
|
+
metadata: {
|
|
68
|
+
query,
|
|
69
|
+
file: fileName,
|
|
70
|
+
version
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const searchTerm = query.toLowerCase();
|
|
75
|
+
const results = [];
|
|
76
|
+
// Helper function to extract all paths and values
|
|
77
|
+
function extractInfo(obj, path = '') {
|
|
78
|
+
const info = [];
|
|
79
|
+
if (obj && typeof obj === 'object' && !Array.isArray(obj)) {
|
|
80
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
81
|
+
const currentPath = path ? `${path}.${key}` : key;
|
|
82
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
83
|
+
info.push({ path: currentPath, key, value, isLeaf: false });
|
|
84
|
+
info.push(...extractInfo(value, currentPath));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
info.push({ path: currentPath, key, value, isLeaf: true });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return info;
|
|
92
|
+
}
|
|
93
|
+
const allInfo = extractInfo(yamlData);
|
|
94
|
+
// Support dot notation queries
|
|
95
|
+
const isDotNotation = searchTerm.includes('.');
|
|
96
|
+
if (isDotNotation) {
|
|
97
|
+
// Exact and partial dot notation matching
|
|
98
|
+
for (const item of allInfo) {
|
|
99
|
+
const pathLower = item.path.toLowerCase();
|
|
100
|
+
if (pathLower === searchTerm ||
|
|
101
|
+
pathLower.endsWith(searchTerm) ||
|
|
102
|
+
pathLower.includes(searchTerm)) {
|
|
103
|
+
results.push(item);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Keyword-based search
|
|
109
|
+
const keywords = searchTerm.split(/\s+/);
|
|
110
|
+
for (const item of allInfo) {
|
|
111
|
+
const pathLower = item.path.toLowerCase();
|
|
112
|
+
const keyLower = item.key.toLowerCase();
|
|
113
|
+
const valueStr = JSON.stringify(item.value).toLowerCase();
|
|
114
|
+
const allKeywordsMatch = keywords.every(kw => pathLower.includes(kw) || keyLower.includes(kw) || valueStr.includes(kw));
|
|
115
|
+
if (allKeywordsMatch) {
|
|
116
|
+
results.push(item);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Sort results by relevance
|
|
121
|
+
results.sort((a, b) => {
|
|
122
|
+
const scoreA = rankResult(a, searchTerm);
|
|
123
|
+
const scoreB = rankResult(b, searchTerm);
|
|
124
|
+
return scoreB - scoreA;
|
|
125
|
+
});
|
|
126
|
+
// Format results for CLI output
|
|
127
|
+
const formattedResults = results.slice(0, 50).map(item => ({
|
|
128
|
+
field: item.path,
|
|
129
|
+
value: item.value,
|
|
130
|
+
type: getType(item.value),
|
|
131
|
+
path: item.path,
|
|
132
|
+
description: '' // Could be enhanced with comments parsing
|
|
133
|
+
}));
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
results: formattedResults,
|
|
137
|
+
metadata: {
|
|
138
|
+
query,
|
|
139
|
+
file: fileName,
|
|
140
|
+
version,
|
|
141
|
+
resultCount: formattedResults.length,
|
|
142
|
+
totalMatches: results.length
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Handle list-versions command
|
|
148
|
+
* Lists available vCluster versions from GitHub
|
|
149
|
+
*/
|
|
150
|
+
export async function handleListVersions() {
|
|
151
|
+
try {
|
|
152
|
+
const tags = await githubClient.getTags();
|
|
153
|
+
// Only show versions starting with 'v'
|
|
154
|
+
const versionTags = tags.filter(tag => tag.startsWith('v'));
|
|
155
|
+
// Always include main branch
|
|
156
|
+
const versions = ['main', ...versionTags];
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
versions,
|
|
160
|
+
metadata: {
|
|
161
|
+
totalCount: versions.length,
|
|
162
|
+
source: 'github'
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
success: false,
|
|
169
|
+
error: `Failed to fetch versions: ${error instanceof Error ? error.message : String(error)}`,
|
|
170
|
+
versions: [],
|
|
171
|
+
metadata: {
|
|
172
|
+
totalCount: 0,
|
|
173
|
+
source: 'github'
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Handle validate command
|
|
180
|
+
* Validates vCluster YAML configuration
|
|
181
|
+
*/
|
|
182
|
+
export async function handleValidate(content, options) {
|
|
183
|
+
const version = options.version || 'main';
|
|
184
|
+
// Validate YAML syntax first
|
|
185
|
+
try {
|
|
186
|
+
yaml.load(content);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
valid: false,
|
|
192
|
+
errors: [
|
|
193
|
+
{
|
|
194
|
+
path: 'root',
|
|
195
|
+
message: error instanceof Error ? error.message : String(error),
|
|
196
|
+
type: 'syntax'
|
|
197
|
+
}
|
|
198
|
+
],
|
|
199
|
+
metadata: {
|
|
200
|
+
version,
|
|
201
|
+
contentLength: content.length
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Fetch schema for validation
|
|
206
|
+
try {
|
|
207
|
+
const schemaContent = await githubClient.getFileContent('chart/values.schema.json', version);
|
|
208
|
+
const fullSchema = JSON.parse(schemaContent);
|
|
209
|
+
// Use snippet validator
|
|
210
|
+
const result = validateSnippet(content, fullSchema, version);
|
|
211
|
+
if (result.valid) {
|
|
212
|
+
return {
|
|
213
|
+
success: true,
|
|
214
|
+
valid: true,
|
|
215
|
+
errors: [],
|
|
216
|
+
metadata: {
|
|
217
|
+
version,
|
|
218
|
+
contentLength: content.length
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
// Format errors for CLI
|
|
224
|
+
const errors = (result.errors || []).map(err => ({
|
|
225
|
+
path: err.path || 'root',
|
|
226
|
+
message: err.message || 'Validation error',
|
|
227
|
+
type: err.keyword || 'validation'
|
|
228
|
+
}));
|
|
229
|
+
return {
|
|
230
|
+
success: true,
|
|
231
|
+
valid: false,
|
|
232
|
+
errors,
|
|
233
|
+
metadata: {
|
|
234
|
+
version,
|
|
235
|
+
contentLength: content.length
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
valid: false,
|
|
244
|
+
errors: [
|
|
245
|
+
{
|
|
246
|
+
path: 'root',
|
|
247
|
+
message: `Failed to load schema: ${error instanceof Error ? error.message : String(error)}`,
|
|
248
|
+
type: 'schema-error'
|
|
249
|
+
}
|
|
250
|
+
],
|
|
251
|
+
metadata: {
|
|
252
|
+
version,
|
|
253
|
+
contentLength: content.length
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=cli-handlers.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI utility functions
|
|
3
|
+
* Provides helpers for stdin reading and file operations
|
|
4
|
+
*/
|
|
5
|
+
export interface ContentSource {
|
|
6
|
+
content: string;
|
|
7
|
+
source: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Read content from stdin
|
|
11
|
+
* Used for piping content to CLI commands
|
|
12
|
+
*/
|
|
13
|
+
export declare function readStdin(): Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Read content from a file or stdin based on the file argument
|
|
16
|
+
*/
|
|
17
|
+
export declare function readContentSource(file?: string): Promise<ContentSource>;
|
|
18
|
+
//# sourceMappingURL=cli-utils.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI utility functions
|
|
3
|
+
* Provides helpers for stdin reading and file operations
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
6
|
+
import { stdin } from 'process';
|
|
7
|
+
/**
|
|
8
|
+
* Read content from stdin
|
|
9
|
+
* Used for piping content to CLI commands
|
|
10
|
+
*/
|
|
11
|
+
export async function readStdin() {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
let data = '';
|
|
14
|
+
stdin.setEncoding('utf8');
|
|
15
|
+
stdin.on('data', (chunk) => data += chunk);
|
|
16
|
+
stdin.on('end', () => resolve(data));
|
|
17
|
+
stdin.on('error', reject);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read content from a file or stdin based on the file argument
|
|
22
|
+
*/
|
|
23
|
+
export async function readContentSource(file) {
|
|
24
|
+
// Read from stdin if no file provided or file is '-'
|
|
25
|
+
if (!file || file === '-') {
|
|
26
|
+
// If no file argument provided (not even '-'), check if we're in interactive mode
|
|
27
|
+
// In interactive mode (TTY), we should not wait for stdin
|
|
28
|
+
if (!file) {
|
|
29
|
+
// stdin.isTTY is true when running in interactive terminal
|
|
30
|
+
// stdin.isTTY is false/undefined when piping or redirecting
|
|
31
|
+
const isInteractive = stdin.isTTY === true;
|
|
32
|
+
if (isInteractive) {
|
|
33
|
+
throw new Error('No input provided. Please specify a file or pipe content via stdin.\n' +
|
|
34
|
+
'Examples:\n' +
|
|
35
|
+
' vcluster-yaml validate my-config.yaml\n' +
|
|
36
|
+
' cat my-config.yaml | vcluster-yaml validate -\n' +
|
|
37
|
+
' vcluster-yaml validate --help');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const content = await readStdin();
|
|
41
|
+
return { content, source: 'stdin' };
|
|
42
|
+
}
|
|
43
|
+
// Read from file
|
|
44
|
+
try {
|
|
45
|
+
const content = await readFile(file, 'utf-8');
|
|
46
|
+
return { content, source: file };
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error(`Cannot read file '${file}': ${error instanceof Error ? error.message : String(error)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=cli-utils.js.map
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* vCluster CLI - Standalone command-line interface
|
|
4
|
+
* Provides query, validate, and list-versions commands
|
|
5
|
+
* Wraps MCP server functionality with user-friendly CLI
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import { readFile } from 'fs/promises';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
import { handleQuery, handleListVersions, handleValidate } from './cli-handlers.js';
|
|
12
|
+
import { formatOutput } from './formatters.js';
|
|
13
|
+
import { readContentSource } from './cli-utils.js';
|
|
14
|
+
import { generateBashCompletion, generateZshCompletion, getInstallInstructions } from './completions.js';
|
|
15
|
+
// Get package.json version
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
const packageJson = JSON.parse(await readFile(join(__dirname, '../package.json'), 'utf-8'));
|
|
19
|
+
const program = new Command();
|
|
20
|
+
program
|
|
21
|
+
.name('vcluster-yaml')
|
|
22
|
+
.description('vCluster YAML configuration CLI')
|
|
23
|
+
.version(packageJson.version);
|
|
24
|
+
// Query command
|
|
25
|
+
program
|
|
26
|
+
.command('query <query>')
|
|
27
|
+
.description('Search for vCluster configuration fields')
|
|
28
|
+
.option('--file <file>', 'Configuration file to search', 'chart/values.yaml')
|
|
29
|
+
.option('-s, --schema-version <version>', 'vCluster version or branch', 'main')
|
|
30
|
+
.option('-f, --format <format>', 'Output format: json, yaml, table (default: table)', 'table')
|
|
31
|
+
.addHelpText('after', `
|
|
32
|
+
Examples:
|
|
33
|
+
$ vcluster-yaml query sync
|
|
34
|
+
$ vcluster-yaml query sync --schema-version v0.24.0
|
|
35
|
+
$ vcluster-yaml query "controlPlane.replicas"
|
|
36
|
+
`)
|
|
37
|
+
.action(async (query, options) => {
|
|
38
|
+
try {
|
|
39
|
+
const format = options.format;
|
|
40
|
+
// Validate format option
|
|
41
|
+
if (!['json', 'yaml', 'table'].includes(format)) {
|
|
42
|
+
console.error(`Error: Invalid format "${format}". Must be one of: json, yaml, table`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// Add validation for empty query
|
|
46
|
+
if (!query || query.trim() === '') {
|
|
47
|
+
console.error(`Error: Query cannot be empty. Try 'vcluster-yaml query sync' or see examples with --help`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const result = await handleQuery(query, {
|
|
51
|
+
file: options.file || 'chart/values.yaml',
|
|
52
|
+
version: options.schemaVersion || 'main'
|
|
53
|
+
});
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
// Error case - output error message and exit with code 1
|
|
56
|
+
if (format === 'json') {
|
|
57
|
+
console.log(JSON.stringify(result, null, 2));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.error(result.error);
|
|
61
|
+
}
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const output = formatOutput(result, format, 'query');
|
|
65
|
+
console.log(output);
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// List versions command
|
|
74
|
+
program
|
|
75
|
+
.command('list-versions')
|
|
76
|
+
.description('List available vCluster versions')
|
|
77
|
+
.option('-f, --format <format>', 'Output format: json, yaml, table (default: table)', 'table')
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
try {
|
|
80
|
+
const format = options.format;
|
|
81
|
+
// Validate format option
|
|
82
|
+
if (!['json', 'yaml', 'table'].includes(format)) {
|
|
83
|
+
console.error(`Error: Invalid format "${format}". Must be one of: json, yaml, table`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const result = await handleListVersions();
|
|
87
|
+
if (!result.success) {
|
|
88
|
+
if (format === 'json') {
|
|
89
|
+
console.log(JSON.stringify(result, null, 2));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.error(result.error);
|
|
93
|
+
}
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const output = formatOutput(result, format, 'list-versions');
|
|
97
|
+
console.log(output);
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
// Validate command
|
|
106
|
+
program
|
|
107
|
+
.command('validate [file]')
|
|
108
|
+
.description('Validate vCluster configuration')
|
|
109
|
+
.option('-s, --schema-version <version>', 'vCluster version for schema', 'main')
|
|
110
|
+
.option('-f, --format <format>', 'Output format: json, yaml, table (default: table)', 'table')
|
|
111
|
+
.addHelpText('after', `
|
|
112
|
+
Arguments:
|
|
113
|
+
file YAML file to validate (use '-' for stdin, omit to read from stdin)
|
|
114
|
+
|
|
115
|
+
Examples:
|
|
116
|
+
$ vcluster-yaml validate vcluster.yaml
|
|
117
|
+
$ vcluster-yaml validate vcluster.yaml --schema-version v0.24.0
|
|
118
|
+
$ cat vcluster.yaml | vcluster-yaml validate -
|
|
119
|
+
$ vcluster-yaml validate - < vcluster.yaml
|
|
120
|
+
`)
|
|
121
|
+
.action(async (file, options) => {
|
|
122
|
+
try {
|
|
123
|
+
const format = options.format;
|
|
124
|
+
// Validate format option
|
|
125
|
+
if (!['json', 'yaml', 'table'].includes(format)) {
|
|
126
|
+
console.error(`Error: Invalid format "${format}". Must be one of: json, yaml, table`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
// Read content from file or stdin
|
|
130
|
+
let content;
|
|
131
|
+
try {
|
|
132
|
+
const result = await readContentSource(file);
|
|
133
|
+
content = result.content;
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
// Check for empty content
|
|
140
|
+
if (!content || content.trim() === '') {
|
|
141
|
+
console.error(`Error: No content to validate. Please provide a file path or pipe content via stdin.`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
const result = await handleValidate(content, {
|
|
145
|
+
version: options.schemaVersion || 'main'
|
|
146
|
+
});
|
|
147
|
+
// For validation, always output the result (even if not successful)
|
|
148
|
+
// The formatOutput will handle error cases appropriately
|
|
149
|
+
const output = formatOutput(result, format, 'validate');
|
|
150
|
+
console.log(output);
|
|
151
|
+
// Exit with code 1 if validation failed OR if we couldn't load schema
|
|
152
|
+
if (!result.success || !result.valid) {
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// Completion command
|
|
163
|
+
program
|
|
164
|
+
.command('completion <shell>')
|
|
165
|
+
.description('Generate shell completion script')
|
|
166
|
+
.addHelpText('after', `
|
|
167
|
+
Supported shells:
|
|
168
|
+
bash Bash completion script
|
|
169
|
+
zsh Zsh completion script
|
|
170
|
+
|
|
171
|
+
Examples:
|
|
172
|
+
$ vcluster-yaml completion bash > ~/.vcluster-yaml-completion.bash
|
|
173
|
+
$ vcluster-yaml completion zsh > ~/.zsh/completion/_vcluster-yaml
|
|
174
|
+
$ vcluster-yaml completion bash --help
|
|
175
|
+
`)
|
|
176
|
+
.action((shell) => {
|
|
177
|
+
const validShells = ['bash', 'zsh'];
|
|
178
|
+
if (!validShells.includes(shell)) {
|
|
179
|
+
console.error(`Error: Unsupported shell "${shell}". Supported shells: ${validShells.join(', ')}`);
|
|
180
|
+
console.error('');
|
|
181
|
+
console.error('Examples:');
|
|
182
|
+
console.error(' vcluster-yaml completion bash > ~/.vcluster-yaml-completion.bash');
|
|
183
|
+
console.error(' vcluster-yaml completion zsh > ~/.zsh/completion/_vcluster-yaml');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
let script;
|
|
187
|
+
if (shell === 'bash') {
|
|
188
|
+
script = generateBashCompletion();
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
script = generateZshCompletion();
|
|
192
|
+
}
|
|
193
|
+
// Output the script
|
|
194
|
+
console.log(script);
|
|
195
|
+
// Add installation instructions to stderr so they don't end up in the script
|
|
196
|
+
console.error('');
|
|
197
|
+
console.error(getInstallInstructions(shell));
|
|
198
|
+
});
|
|
199
|
+
// Parse arguments
|
|
200
|
+
program.parse();
|
|
201
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell completion generators
|
|
3
|
+
* Generates bash and zsh completion scripts for vcluster-yaml CLI
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Generate bash completion script
|
|
7
|
+
*/
|
|
8
|
+
export declare function generateBashCompletion(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Generate zsh completion script
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateZshCompletion(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Display installation instructions for a shell
|
|
15
|
+
*/
|
|
16
|
+
export declare function getInstallInstructions(shell: string): string;
|
|
17
|
+
//# sourceMappingURL=completions.d.ts.map
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
* Shell completion generators
|
|
3
3
|
* Generates bash and zsh completion scripts for vcluster-yaml CLI
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
/**
|
|
7
6
|
* Generate bash completion script
|
|
8
|
-
* @returns {string} Bash completion script
|
|
9
7
|
*/
|
|
10
8
|
export function generateBashCompletion() {
|
|
11
|
-
|
|
9
|
+
return `# vcluster-yaml bash completion
|
|
12
10
|
|
|
13
11
|
_vcluster_yaml_completions() {
|
|
14
12
|
local cur prev words cword
|
|
@@ -82,13 +80,11 @@ _vcluster_yaml_completions() {
|
|
|
82
80
|
complete -F _vcluster_yaml_completions vcluster-yaml
|
|
83
81
|
`;
|
|
84
82
|
}
|
|
85
|
-
|
|
86
83
|
/**
|
|
87
84
|
* Generate zsh completion script
|
|
88
|
-
* @returns {string} Zsh completion script
|
|
89
85
|
*/
|
|
90
86
|
export function generateZshCompletion() {
|
|
91
|
-
|
|
87
|
+
return `#compdef vcluster-yaml
|
|
92
88
|
|
|
93
89
|
# vcluster-yaml zsh completion
|
|
94
90
|
|
|
@@ -183,15 +179,12 @@ _vcluster_yaml() {
|
|
|
183
179
|
_vcluster_yaml "$@"
|
|
184
180
|
`;
|
|
185
181
|
}
|
|
186
|
-
|
|
187
182
|
/**
|
|
188
183
|
* Display installation instructions for a shell
|
|
189
|
-
* @param {string} shell - Shell type (bash or zsh)
|
|
190
|
-
* @returns {string} Installation instructions
|
|
191
184
|
*/
|
|
192
185
|
export function getInstallInstructions(shell) {
|
|
193
|
-
|
|
194
|
-
|
|
186
|
+
if (shell === 'bash') {
|
|
187
|
+
return `
|
|
195
188
|
Bash Completion Installation:
|
|
196
189
|
|
|
197
190
|
1. Save the completion script:
|
|
@@ -206,10 +199,9 @@ Bash Completion Installation:
|
|
|
206
199
|
Or install system-wide (requires sudo):
|
|
207
200
|
$ sudo vcluster-yaml completion bash > /etc/bash_completion.d/vcluster-yaml
|
|
208
201
|
`;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return `
|
|
202
|
+
}
|
|
203
|
+
if (shell === 'zsh') {
|
|
204
|
+
return `
|
|
213
205
|
Zsh Completion Installation:
|
|
214
206
|
|
|
215
207
|
1. Create completion directory if needed:
|
|
@@ -228,7 +220,7 @@ Zsh Completion Installation:
|
|
|
228
220
|
Or install system-wide (requires sudo):
|
|
229
221
|
$ sudo vcluster-yaml completion zsh > /usr/local/share/zsh/site-functions/_vcluster-yaml
|
|
230
222
|
`;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return 'Unsupported shell. Available: bash, zsh';
|
|
223
|
+
}
|
|
224
|
+
return 'Unsupported shell. Available: bash, zsh';
|
|
234
225
|
}
|
|
226
|
+
//# sourceMappingURL=completions.js.map
|