@johnlwin-test/create-repro 0.1.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/LICENSE +175 -0
- package/README.md +116 -0
- package/package.json +33 -0
- package/src/cli.js +308 -0
- package/src/operations.js +268 -0
- package/src/regions.js +235 -0
- package/src/sdks/java/generate.js +79 -0
- package/src/sdks/java/templates/LOG.md +0 -0
- package/src/sdks/java/templates/Main.java +24 -0
- package/src/sdks/java/templates/README.md +14 -0
- package/src/sdks/java/templates/pom.xml +61 -0
- package/src/sdks/java/utils/maven.js +24 -0
- package/src/sdks/javascript/environments/browser/generate.js +208 -0
- package/src/sdks/javascript/environments/browser/templates/index.html +27 -0
- package/src/sdks/javascript/environments/browser/templates/index.js +62 -0
- package/src/sdks/javascript/environments/node/generate.js +55 -0
- package/src/sdks/javascript/environments/node/templates/index.js +12 -0
- package/src/sdks/javascript/environments/react-native/generate.js +286 -0
- package/src/sdks/javascript/environments/react-native/templates/App.js +147 -0
- package/src/services.js +310 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Attempts to get available operations for a service by installing and inspecting the package
|
|
8
|
+
* @param {string} servicePackage - The service package name (e.g., "@aws-sdk/client-s3")
|
|
9
|
+
* @returns {Promise<{operations: string[], clientName: string, error?: string}>} - Operations in kebab-case, actual client name, and optional error
|
|
10
|
+
*/
|
|
11
|
+
export async function getServiceOperations(servicePackage) {
|
|
12
|
+
let tempDir = null;
|
|
13
|
+
try {
|
|
14
|
+
// Create a temporary directory
|
|
15
|
+
tempDir = path.join(os.tmpdir(), `aws-sdk-inspect-${Date.now()}`);
|
|
16
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
17
|
+
|
|
18
|
+
// Create a minimal package.json
|
|
19
|
+
fs.writeFileSync(
|
|
20
|
+
path.join(tempDir, "package.json"),
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
name: "temp-inspector",
|
|
23
|
+
version: "1.0.0",
|
|
24
|
+
type: "module"
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// Install the package
|
|
29
|
+
console.log(` Installing ${servicePackage}...`);
|
|
30
|
+
try {
|
|
31
|
+
execSync(`npm install ${servicePackage}@latest --no-save --silent --no-audit --no-fund --loglevel=error`, {
|
|
32
|
+
cwd: tempDir,
|
|
33
|
+
stdio: "pipe",
|
|
34
|
+
timeout: 60000, // 60 second timeout
|
|
35
|
+
});
|
|
36
|
+
} catch (installError) {
|
|
37
|
+
// Check if it's a 404 (package doesn't exist)
|
|
38
|
+
const errorOutput = installError.stderr?.toString() || installError.message;
|
|
39
|
+
if (errorOutput.includes('404') || errorOutput.includes('Not Found')) {
|
|
40
|
+
return {
|
|
41
|
+
operations: [],
|
|
42
|
+
clientName: "",
|
|
43
|
+
error: `Package "${servicePackage}" not found on npm. Please verify the package name.`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
throw installError; // Re-throw other errors
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Find the package directory
|
|
50
|
+
const packagePath = path.join(tempDir, "node_modules", ...servicePackage.split('/'));
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(packagePath)) {
|
|
53
|
+
throw new Error(`Package not found at ${packagePath}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Read package.json to find exports
|
|
57
|
+
const packageJsonPath = path.join(packagePath, "package.json");
|
|
58
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
59
|
+
|
|
60
|
+
const operations = new Set();
|
|
61
|
+
let clientName = "";
|
|
62
|
+
|
|
63
|
+
// Method 1: Parse exports field from package.json
|
|
64
|
+
if (packageJson.exports) {
|
|
65
|
+
for (const key of Object.keys(packageJson.exports)) {
|
|
66
|
+
// Look for command exports like "./commands/ListBucketsCommand"
|
|
67
|
+
const commandMatch = key.match(/\/commands\/([A-Z][a-zA-Z0-9]+Command)/);
|
|
68
|
+
if (commandMatch) {
|
|
69
|
+
const commandName = commandMatch[1];
|
|
70
|
+
const operationName = commandName
|
|
71
|
+
.replace(/Command$/, '')
|
|
72
|
+
.replace(/([A-Z])/g, (match, p1, offset) =>
|
|
73
|
+
offset > 0 ? `-${p1.toLowerCase()}` : p1.toLowerCase()
|
|
74
|
+
);
|
|
75
|
+
operations.add(operationName);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Look for the client export (e.g., "./DynamoDBClient")
|
|
79
|
+
const clientMatch = key.match(/^\.\/([A-Za-z0-9]+Client)$/);
|
|
80
|
+
if (clientMatch && !clientName) {
|
|
81
|
+
clientName = clientMatch[1];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Method 2: Scan the commands directory if it exists
|
|
87
|
+
const commandsDir = path.join(packagePath, "dist-cjs", "commands");
|
|
88
|
+
if (fs.existsSync(commandsDir)) {
|
|
89
|
+
const files = fs.readdirSync(commandsDir);
|
|
90
|
+
for (const file of files) {
|
|
91
|
+
if (file.endsWith('Command.js')) {
|
|
92
|
+
const commandName = file.replace('.js', '');
|
|
93
|
+
const operationName = commandName
|
|
94
|
+
.replace(/Command$/, '')
|
|
95
|
+
.replace(/([A-Z])/g, (match, p1, offset) =>
|
|
96
|
+
offset > 0 ? `-${p1.toLowerCase()}` : p1.toLowerCase()
|
|
97
|
+
);
|
|
98
|
+
operations.add(operationName);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Method 3: Try dist-es directory
|
|
104
|
+
const commandsDirES = path.join(packagePath, "dist-es", "commands");
|
|
105
|
+
if (fs.existsSync(commandsDirES)) {
|
|
106
|
+
const files = fs.readdirSync(commandsDirES);
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
if (file.endsWith('Command.js') || file.endsWith('Command.mjs')) {
|
|
109
|
+
const commandName = file.replace(/\.(m)?js$/, '');
|
|
110
|
+
const operationName = commandName
|
|
111
|
+
.replace(/Command$/, '')
|
|
112
|
+
.replace(/([A-Z])/g, (match, p1, offset) =>
|
|
113
|
+
offset > 0 ? `-${p1.toLowerCase()}` : p1.toLowerCase()
|
|
114
|
+
);
|
|
115
|
+
operations.add(operationName);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Fallback: Find client name from dist-cjs or dist-es if not found in exports
|
|
121
|
+
if (!clientName) {
|
|
122
|
+
for (const distDir of ["dist-cjs", "dist-es"]) {
|
|
123
|
+
const dir = path.join(packagePath, distDir);
|
|
124
|
+
if (fs.existsSync(dir)) {
|
|
125
|
+
const files = fs.readdirSync(dir);
|
|
126
|
+
const clientFile = files.find(f => f.match(/^[A-Z][a-zA-Z0-9]*Client\.(js|mjs)$/));
|
|
127
|
+
if (clientFile) {
|
|
128
|
+
clientName = clientFile.replace(/\.(m)?js$/, '');
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Cleanup
|
|
136
|
+
if (tempDir && fs.existsSync(tempDir)) {
|
|
137
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const result = Array.from(operations).sort();
|
|
141
|
+
console.log(` Found ${result.length} operations`);
|
|
142
|
+
if (clientName) {
|
|
143
|
+
console.log(` Client: ${clientName}`);
|
|
144
|
+
}
|
|
145
|
+
return { operations: result, clientName };
|
|
146
|
+
|
|
147
|
+
} catch (error) {
|
|
148
|
+
// Cleanup on error
|
|
149
|
+
if (tempDir && fs.existsSync(tempDir)) {
|
|
150
|
+
try {
|
|
151
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
152
|
+
} catch (cleanupError) {
|
|
153
|
+
// Ignore cleanup errors
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Return error details for better user feedback
|
|
158
|
+
console.warn(` Could not fetch operations: ${error.message}`);
|
|
159
|
+
return {
|
|
160
|
+
operations: [],
|
|
161
|
+
clientName: "",
|
|
162
|
+
error: error.message
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Validates operation name format (kebab-case)
|
|
169
|
+
* @param {string} operation - Operation name to validate
|
|
170
|
+
* @returns {boolean} - True if valid kebab-case format
|
|
171
|
+
*/
|
|
172
|
+
export function isValidOperationFormat(operation) {
|
|
173
|
+
// Must be kebab-case: lowercase letters, numbers, and hyphens only
|
|
174
|
+
// Must start with a letter
|
|
175
|
+
const kebabCasePattern = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
176
|
+
return kebabCasePattern.test(operation);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Validates if an operation exists for a given service
|
|
181
|
+
* @param {string} operation - Operation name in kebab-case
|
|
182
|
+
* @param {string[]} availableOperations - List of available operations
|
|
183
|
+
* @returns {boolean} - True if operation exists
|
|
184
|
+
*/
|
|
185
|
+
export function isValidOperation(operation, availableOperations) {
|
|
186
|
+
if (availableOperations.length === 0) {
|
|
187
|
+
// If we don't have the list, just validate format
|
|
188
|
+
return isValidOperationFormat(operation);
|
|
189
|
+
}
|
|
190
|
+
return availableOperations.includes(operation);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Gets operation suggestions based on partial input
|
|
195
|
+
* @param {string} input - Partial operation name
|
|
196
|
+
* @param {string[]} availableOperations - List of available operations
|
|
197
|
+
* @returns {string[]} - Array of matching operation names
|
|
198
|
+
*/
|
|
199
|
+
export function getOperationSuggestions(input, availableOperations) {
|
|
200
|
+
if (availableOperations.length === 0) return [];
|
|
201
|
+
|
|
202
|
+
const lowerInput = input.toLowerCase();
|
|
203
|
+
return availableOperations.filter(op =>
|
|
204
|
+
op.toLowerCase().includes(lowerInput)
|
|
205
|
+
).slice(0, 20); // Limit to 20 suggestions
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Provides helpful error message for invalid operations
|
|
210
|
+
* @param {string} operation - The invalid operation name
|
|
211
|
+
* @param {string[]} availableOperations - List of available operations
|
|
212
|
+
* @returns {string} - Error message with suggestions
|
|
213
|
+
*/
|
|
214
|
+
export function getOperationErrorMessage(operation, availableOperations) {
|
|
215
|
+
if (!isValidOperationFormat(operation)) {
|
|
216
|
+
return "Operation must be in kebab-case format (e.g., list-buckets, get-object)";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (availableOperations.length === 0) {
|
|
220
|
+
return "Could not validate operation. Please ensure the operation name is correct.";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Find similar operations (Levenshtein distance or simple substring match)
|
|
224
|
+
const similar = availableOperations.filter(op => {
|
|
225
|
+
const distance = levenshteinDistance(operation, op);
|
|
226
|
+
return distance <= 2; // Allow up to 2 character differences
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (similar.length > 0) {
|
|
230
|
+
return `Operation not found. Did you mean: ${similar.slice(0, 3).join(", ")}?`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return `Operation not found. Available operations: ${availableOperations.slice(0, 5).join(", ")}...`;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Calculate Levenshtein distance between two strings
|
|
238
|
+
* @param {string} a - First string
|
|
239
|
+
* @param {string} b - Second string
|
|
240
|
+
* @returns {number} - Edit distance
|
|
241
|
+
*/
|
|
242
|
+
function levenshteinDistance(a, b) {
|
|
243
|
+
const matrix = [];
|
|
244
|
+
|
|
245
|
+
for (let i = 0; i <= b.length; i++) {
|
|
246
|
+
matrix[i] = [i];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (let j = 0; j <= a.length; j++) {
|
|
250
|
+
matrix[0][j] = j;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (let i = 1; i <= b.length; i++) {
|
|
254
|
+
for (let j = 1; j <= a.length; j++) {
|
|
255
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
256
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
257
|
+
} else {
|
|
258
|
+
matrix[i][j] = Math.min(
|
|
259
|
+
matrix[i - 1][j - 1] + 1, // substitution
|
|
260
|
+
matrix[i][j - 1] + 1, // insertion
|
|
261
|
+
matrix[i - 1][j] + 1 // deletion
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return matrix[b.length][a.length];
|
|
268
|
+
}
|
package/src/regions.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Comprehensive list of AWS regions
|
|
2
|
+
|
|
3
|
+
export const AWS_REGIONS = [
|
|
4
|
+
// US Regions
|
|
5
|
+
"us-east-1", // US East (N. Virginia)
|
|
6
|
+
"us-east-2", // US East (Ohio)
|
|
7
|
+
"us-west-1", // US West (N. California)
|
|
8
|
+
"us-west-2", // US West (Oregon)
|
|
9
|
+
|
|
10
|
+
// Africa
|
|
11
|
+
"af-south-1", // Africa (Cape Town)
|
|
12
|
+
|
|
13
|
+
// Asia Pacific
|
|
14
|
+
"ap-east-1", // Asia Pacific (Hong Kong)
|
|
15
|
+
"ap-south-1", // Asia Pacific (Mumbai)
|
|
16
|
+
"ap-south-2", // Asia Pacific (Hyderabad)
|
|
17
|
+
"ap-northeast-1", // Asia Pacific (Tokyo)
|
|
18
|
+
"ap-northeast-2", // Asia Pacific (Seoul)
|
|
19
|
+
"ap-northeast-3", // Asia Pacific (Osaka)
|
|
20
|
+
"ap-southeast-1", // Asia Pacific (Singapore)
|
|
21
|
+
"ap-southeast-2", // Asia Pacific (Sydney)
|
|
22
|
+
"ap-southeast-3", // Asia Pacific (Jakarta)
|
|
23
|
+
"ap-southeast-4", // Asia Pacific (Melbourne)
|
|
24
|
+
|
|
25
|
+
// Canada
|
|
26
|
+
"ca-central-1", // Canada (Central)
|
|
27
|
+
"ca-west-1", // Canada (Calgary)
|
|
28
|
+
|
|
29
|
+
// Europe
|
|
30
|
+
"eu-central-1", // Europe (Frankfurt)
|
|
31
|
+
"eu-central-2", // Europe (Zurich)
|
|
32
|
+
"eu-west-1", // Europe (Ireland)
|
|
33
|
+
"eu-west-2", // Europe (London)
|
|
34
|
+
"eu-west-3", // Europe (Paris)
|
|
35
|
+
"eu-south-1", // Europe (Milan)
|
|
36
|
+
"eu-south-2", // Europe (Spain)
|
|
37
|
+
"eu-north-1", // Europe (Stockholm)
|
|
38
|
+
|
|
39
|
+
// Middle East
|
|
40
|
+
"me-south-1", // Middle East (Bahrain)
|
|
41
|
+
"me-central-1", // Middle East (UAE)
|
|
42
|
+
|
|
43
|
+
// South America
|
|
44
|
+
"sa-east-1", // South America (São Paulo)
|
|
45
|
+
|
|
46
|
+
// AWS GovCloud (US)
|
|
47
|
+
"us-gov-east-1", // AWS GovCloud (US-East)
|
|
48
|
+
"us-gov-west-1", // AWS GovCloud (US-West)
|
|
49
|
+
|
|
50
|
+
// China (requires separate account)
|
|
51
|
+
"cn-north-1", // China (Beijing)
|
|
52
|
+
"cn-northwest-1", // China (Ningxia)
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
// Region display names for better UX
|
|
56
|
+
const REGION_NAMES = {
|
|
57
|
+
"us-east-1": "US East (N. Virginia)",
|
|
58
|
+
"us-east-2": "US East (Ohio)",
|
|
59
|
+
"us-west-1": "US West (N. California)",
|
|
60
|
+
"us-west-2": "US West (Oregon)",
|
|
61
|
+
"af-south-1": "Africa (Cape Town)",
|
|
62
|
+
"ap-east-1": "Asia Pacific (Hong Kong)",
|
|
63
|
+
"ap-south-1": "Asia Pacific (Mumbai)",
|
|
64
|
+
"ap-south-2": "Asia Pacific (Hyderabad)",
|
|
65
|
+
"ap-northeast-1": "Asia Pacific (Tokyo)",
|
|
66
|
+
"ap-northeast-2": "Asia Pacific (Seoul)",
|
|
67
|
+
"ap-northeast-3": "Asia Pacific (Osaka)",
|
|
68
|
+
"ap-southeast-1": "Asia Pacific (Singapore)",
|
|
69
|
+
"ap-southeast-2": "Asia Pacific (Sydney)",
|
|
70
|
+
"ap-southeast-3": "Asia Pacific (Jakarta)",
|
|
71
|
+
"ap-southeast-4": "Asia Pacific (Melbourne)",
|
|
72
|
+
"ca-central-1": "Canada (Central)",
|
|
73
|
+
"ca-west-1": "Canada (Calgary)",
|
|
74
|
+
"eu-central-1": "Europe (Frankfurt)",
|
|
75
|
+
"eu-central-2": "Europe (Zurich)",
|
|
76
|
+
"eu-west-1": "Europe (Ireland)",
|
|
77
|
+
"eu-west-2": "Europe (London)",
|
|
78
|
+
"eu-west-3": "Europe (Paris)",
|
|
79
|
+
"eu-south-1": "Europe (Milan)",
|
|
80
|
+
"eu-south-2": "Europe (Spain)",
|
|
81
|
+
"eu-north-1": "Europe (Stockholm)",
|
|
82
|
+
"me-south-1": "Middle East (Bahrain)",
|
|
83
|
+
"me-central-1": "Middle East (UAE)",
|
|
84
|
+
"sa-east-1": "South America (São Paulo)",
|
|
85
|
+
"us-gov-east-1": "AWS GovCloud (US-East)",
|
|
86
|
+
"us-gov-west-1": "AWS GovCloud (US-West)",
|
|
87
|
+
"cn-north-1": "China (Beijing)",
|
|
88
|
+
"cn-northwest-1": "China (Ningxia)",
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validates if a region code is valid
|
|
93
|
+
* @param {string} region - The region code (e.g., "us-west-2")
|
|
94
|
+
* @returns {boolean} - True if valid, false otherwise
|
|
95
|
+
*/
|
|
96
|
+
export function isValidRegion(region) {
|
|
97
|
+
return AWS_REGIONS.includes(region.toLowerCase());
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Gets the display name for a region
|
|
102
|
+
* @param {string} region - The region code
|
|
103
|
+
* @returns {string} - Display name or the region code if not found
|
|
104
|
+
*/
|
|
105
|
+
export function getRegionDisplayName(region) {
|
|
106
|
+
return REGION_NAMES[region] || region;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculate Levenshtein distance between two strings
|
|
111
|
+
* @param {string} a - First string
|
|
112
|
+
* @param {string} b - Second string
|
|
113
|
+
* @returns {number} - Edit distance
|
|
114
|
+
*/
|
|
115
|
+
function levenshteinDistance(a, b) {
|
|
116
|
+
const matrix = [];
|
|
117
|
+
|
|
118
|
+
for (let i = 0; i <= b.length; i++) {
|
|
119
|
+
matrix[i] = [i];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
for (let j = 0; j <= a.length; j++) {
|
|
123
|
+
matrix[0][j] = j;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (let i = 1; i <= b.length; i++) {
|
|
127
|
+
for (let j = 1; j <= a.length; j++) {
|
|
128
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
129
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
130
|
+
} else {
|
|
131
|
+
matrix[i][j] = Math.min(
|
|
132
|
+
matrix[i - 1][j - 1] + 1, // substitution
|
|
133
|
+
matrix[i][j - 1] + 1, // insertion
|
|
134
|
+
matrix[i - 1][j] + 1 // deletion
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return matrix[b.length][a.length];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Finds similar region codes based on typos
|
|
145
|
+
* @param {string} region - The region code to check
|
|
146
|
+
* @param {number} maxDistance - Maximum edit distance (default: 2)
|
|
147
|
+
* @returns {string[]} - Array of similar region codes
|
|
148
|
+
*/
|
|
149
|
+
export function findSimilarRegions(region, maxDistance = 2) {
|
|
150
|
+
const lowerRegion = region.toLowerCase();
|
|
151
|
+
|
|
152
|
+
return AWS_REGIONS.filter(validRegion => {
|
|
153
|
+
const distance = levenshteinDistance(lowerRegion, validRegion);
|
|
154
|
+
return distance > 0 && distance <= maxDistance;
|
|
155
|
+
}).slice(0, 5); // Limit to top 5 suggestions
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Gets region suggestions based on partial input
|
|
160
|
+
* @param {string} input - Partial region code
|
|
161
|
+
* @returns {string[]} - Array of matching region codes
|
|
162
|
+
*/
|
|
163
|
+
export function getRegionSuggestions(input) {
|
|
164
|
+
if (!input) return AWS_REGIONS;
|
|
165
|
+
|
|
166
|
+
const lowerInput = input.toLowerCase();
|
|
167
|
+
return AWS_REGIONS.filter(region =>
|
|
168
|
+
region.toLowerCase().includes(lowerInput)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Validates region format (basic pattern check)
|
|
174
|
+
* @param {string} region - Region code to validate
|
|
175
|
+
* @returns {boolean} - True if format is valid
|
|
176
|
+
*/
|
|
177
|
+
export function isValidRegionFormat(region) {
|
|
178
|
+
// AWS region format: 2-3 letter prefix, direction, number
|
|
179
|
+
// Examples: us-west-2, ap-southeast-1, eu-central-1
|
|
180
|
+
const regionPattern = /^[a-z]{2,3}-(north|south|east|west|central|northeast|northwest|southeast|southwest)-\d+$/;
|
|
181
|
+
const govCloudPattern = /^us-gov-(east|west)-\d+$/;
|
|
182
|
+
const chinaPattern = /^cn-(north|northwest)-\d+$/;
|
|
183
|
+
|
|
184
|
+
return regionPattern.test(region) || govCloudPattern.test(region) || chinaPattern.test(region);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Provides helpful error message for invalid regions
|
|
189
|
+
* @param {string} region - The invalid region code
|
|
190
|
+
* @returns {string} - Error message with suggestions
|
|
191
|
+
*/
|
|
192
|
+
export function getRegionErrorMessage(region) {
|
|
193
|
+
const lowerRegion = region.toLowerCase();
|
|
194
|
+
|
|
195
|
+
// Check if it's a valid format but not in our list
|
|
196
|
+
if (!isValidRegionFormat(lowerRegion)) {
|
|
197
|
+
return `Invalid region format. AWS regions follow the pattern: prefix-direction-number (e.g., us-west-2, eu-central-1)`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Find similar regions
|
|
201
|
+
const similar = findSimilarRegions(lowerRegion);
|
|
202
|
+
|
|
203
|
+
if (similar.length > 0) {
|
|
204
|
+
const suggestions = similar.map(r => `${r} (${getRegionDisplayName(r)})`).join(", ");
|
|
205
|
+
return `Region not found. Did you mean: ${suggestions}?`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check for common mistakes
|
|
209
|
+
if (lowerRegion.includes("_")) {
|
|
210
|
+
const corrected = lowerRegion.replace(/_/g, "-");
|
|
211
|
+
if (isValidRegion(corrected)) {
|
|
212
|
+
return `Invalid format. Did you mean: ${corrected}? (use hyphens, not underscores)`;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return `Region not found. Use format: prefix-direction-number (e.g., us-west-2, eu-central-1)`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Converts Java-style region format to standard format
|
|
221
|
+
* @param {string} region - Region in Java format (e.g., "US_WEST_1")
|
|
222
|
+
* @returns {string} - Region in standard format (e.g., "us-west-1")
|
|
223
|
+
*/
|
|
224
|
+
export function javaRegionToStandard(region) {
|
|
225
|
+
return region.toLowerCase().replace(/_/g, "-");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Converts standard region format to Java-style format
|
|
230
|
+
* @param {string} region - Region in standard format (e.g., "us-west-1")
|
|
231
|
+
* @returns {string} - Region in Java format (e.g., "US_WEST_1")
|
|
232
|
+
*/
|
|
233
|
+
export function standardRegionToJava(region) {
|
|
234
|
+
return region.toUpperCase().replace(/-/g, "_");
|
|
235
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
// import { generateMavenWrapper } from "./utils/maven.js";
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
// Template cache to avoid repeated file reads
|
|
9
|
+
const TEMPLATES = {
|
|
10
|
+
mainJava: fs.readFileSync(
|
|
11
|
+
path.join(__dirname, "templates/Main.java"),
|
|
12
|
+
"utf-8"
|
|
13
|
+
),
|
|
14
|
+
pomXml: fs.readFileSync(path.join(__dirname, "templates/pom.xml"), "utf-8"),
|
|
15
|
+
readme: fs.readFileSync(path.join(__dirname, "templates/README.md"), "utf-8"),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const generateJavaProject = (answers, projectDir) => {
|
|
19
|
+
// Create Maven project structure
|
|
20
|
+
const paths = {
|
|
21
|
+
mainJava: path.join(projectDir, "src/main/java/com/aws/repro"),
|
|
22
|
+
testJava: path.join(projectDir, "src/test/java/com/aws/repro"),
|
|
23
|
+
resources: path.join(projectDir, "src/main/resources"),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Create directories
|
|
27
|
+
Object.values(paths).forEach((p) => {
|
|
28
|
+
fs.mkdirSync(p, { recursive: true });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Process service name for Java SDK
|
|
32
|
+
const javaService = answers.service.toLowerCase();
|
|
33
|
+
const operationPascal =
|
|
34
|
+
answers.operation.charAt(0).toUpperCase() + answers.operation.slice(1);
|
|
35
|
+
|
|
36
|
+
// Generate Main.java
|
|
37
|
+
const mainJavaContent = TEMPLATES.mainJava
|
|
38
|
+
.replace(/{{service}}/g, javaService)
|
|
39
|
+
.replace(
|
|
40
|
+
/{{Service}}/g,
|
|
41
|
+
javaService.charAt(0).toUpperCase() + javaService.slice(1)
|
|
42
|
+
)
|
|
43
|
+
.replace(/{{operation}}/g, answers.operation)
|
|
44
|
+
.replace(/{{Operation}}/g, operationPascal)
|
|
45
|
+
.replace(/{{region}}/g, answers.region.toUpperCase());
|
|
46
|
+
|
|
47
|
+
// Generate pom.xml
|
|
48
|
+
const pomXmlContent = TEMPLATES.pomXml
|
|
49
|
+
.replace(/{{service}}/g, javaService)
|
|
50
|
+
.replace(/{{aws-sdk-version}}/g, "2.20.136");
|
|
51
|
+
|
|
52
|
+
// Generate README
|
|
53
|
+
const readmeContent = TEMPLATES.readme
|
|
54
|
+
.replace(/{{service}}/g, javaService)
|
|
55
|
+
.replace(/{{operation}}/g, answers.operation)
|
|
56
|
+
.replace(/{{region}}/g, answers.region);
|
|
57
|
+
|
|
58
|
+
// Write core files
|
|
59
|
+
fs.writeFileSync(path.join(paths.mainJava, "Main.java"), mainJavaContent);
|
|
60
|
+
fs.writeFileSync(path.join(projectDir, "pom.xml"), pomXmlContent);
|
|
61
|
+
fs.writeFileSync(path.join(projectDir, "README.md"), readmeContent);
|
|
62
|
+
|
|
63
|
+
// // Add Maven wrapper
|
|
64
|
+
// generateMavenWrapper(projectDir);
|
|
65
|
+
|
|
66
|
+
// Add .gitignore
|
|
67
|
+
fs.writeFileSync(
|
|
68
|
+
path.join(projectDir, ".gitignore"),
|
|
69
|
+
"target/\n.classpath\n.project\n.settings/\nbin/\n"
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Helper to render templates with replacements
|
|
74
|
+
function renderTemplate(template, variables) {
|
|
75
|
+
return Object.entries(variables).reduce(
|
|
76
|
+
(acc, [key, value]) => acc.replace(new RegExp(`{{${key}}}`, "g"), value),
|
|
77
|
+
template
|
|
78
|
+
);
|
|
79
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
package com.aws.repro;
|
|
2
|
+
import software.amazon.awssdk.regions.Region;
|
|
3
|
+
import software.amazon.awssdk.services.{{service}}.{{Service}}Client;
|
|
4
|
+
import software.amazon.awssdk.services.{{service}}.model.{{Operation}}Request;
|
|
5
|
+
import software.amazon.awssdk.services.{{service}}.model.{{Operation}}Response;
|
|
6
|
+
public class Main {
|
|
7
|
+
public static void main(String[] args) {
|
|
8
|
+
{{Service}}Client client = {{Service}}Client.builder()
|
|
9
|
+
.region(Region.{{region}})
|
|
10
|
+
.build();
|
|
11
|
+
{{Operation}}Request request = {{Operation}}Request.builder()
|
|
12
|
+
// Configure request parameters here
|
|
13
|
+
.build();
|
|
14
|
+
try {
|
|
15
|
+
{{Operation}}Response response = client.{{operation}}(request);
|
|
16
|
+
System.out.println("Operation successful:");
|
|
17
|
+
System.out.println(response);
|
|
18
|
+
} catch (Exception e) {
|
|
19
|
+
System.err.println("Error executing operation:");
|
|
20
|
+
e.printStackTrace();
|
|
21
|
+
System.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# AWS Java SDK Repro Project
|
|
2
|
+
|
|
3
|
+
Service: {{service}}
|
|
4
|
+
Operation: {{operation}}
|
|
5
|
+
Region: {{region}}
|
|
6
|
+
|
|
7
|
+
## Running the Project
|
|
8
|
+
|
|
9
|
+
Configure AWS credentials:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
|
|
13
|
+
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
|
|
14
|
+
```
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
3
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
4
|
+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
5
|
+
<modelVersion>4.0.0</modelVersion>
|
|
6
|
+
|
|
7
|
+
<groupId>com.aws.repro</groupId>
|
|
8
|
+
<artifactId>aws-java-repro</artifactId>
|
|
9
|
+
<version>1.0.0</version>
|
|
10
|
+
<packaging>jar</packaging>
|
|
11
|
+
|
|
12
|
+
<properties>
|
|
13
|
+
<maven.compiler.source>11</maven.compiler.source>
|
|
14
|
+
<maven.compiler.target>11</maven.compiler.target>
|
|
15
|
+
<aws.sdk.version>2.20.136</aws.sdk.version>
|
|
16
|
+
</properties>
|
|
17
|
+
|
|
18
|
+
<dependencyManagement>
|
|
19
|
+
<dependencies>
|
|
20
|
+
<dependency>
|
|
21
|
+
<groupId>software.amazon.awssdk</groupId>
|
|
22
|
+
<artifactId>bom</artifactId>
|
|
23
|
+
<version>${aws.sdk.version}</version>
|
|
24
|
+
<type>pom</type>
|
|
25
|
+
<scope>import</scope>
|
|
26
|
+
</dependency>
|
|
27
|
+
</dependencies>
|
|
28
|
+
</dependencyManagement>
|
|
29
|
+
|
|
30
|
+
<dependencies>
|
|
31
|
+
<dependency>
|
|
32
|
+
<groupId>software.amazon.awssdk</groupId>
|
|
33
|
+
<artifactId>{{service}}</artifactId>
|
|
34
|
+
</dependency>
|
|
35
|
+
</dependencies>
|
|
36
|
+
|
|
37
|
+
<build>
|
|
38
|
+
<plugins>
|
|
39
|
+
<plugin>
|
|
40
|
+
<groupId>org.apache.maven.plugins</groupId>
|
|
41
|
+
<artifactId>maven-shade-plugin</artifactId>
|
|
42
|
+
<version>3.3.0</version>
|
|
43
|
+
<executions>
|
|
44
|
+
<execution>
|
|
45
|
+
<phase>package</phase>
|
|
46
|
+
<goals>
|
|
47
|
+
<goal>shade</goal>
|
|
48
|
+
</goals>
|
|
49
|
+
<configuration>
|
|
50
|
+
<transformers>
|
|
51
|
+
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
|
52
|
+
<mainClass>com.aws.repro.Main</mainClass>
|
|
53
|
+
</transformer>
|
|
54
|
+
</transformers>
|
|
55
|
+
</configuration>
|
|
56
|
+
</execution>
|
|
57
|
+
</executions>
|
|
58
|
+
</plugin>
|
|
59
|
+
</plugins>
|
|
60
|
+
</build>
|
|
61
|
+
</project>
|