refactor: add type annotations for improved type safety across various components

This commit is contained in:
Simon Larsen
2025-06-13 13:50:06 +01:00
parent c0be3e2fa5
commit 0ef1b717aa
10 changed files with 157 additions and 71 deletions

View File

@@ -12,7 +12,7 @@ describe("MarkdownEditor with SpellCheck", () => {
/>,
);
const textarea = screen.getByRole("textbox");
const textarea: HTMLTextAreaElement = screen.getByRole("textbox") as HTMLTextAreaElement;
expect(textarea.spellcheck).toBe(true);
});
@@ -25,7 +25,7 @@ describe("MarkdownEditor with SpellCheck", () => {
/>,
);
const textarea = screen.getByRole("textbox");
const textarea: HTMLTextAreaElement = screen.getByRole("textbox") as HTMLTextAreaElement;
expect(textarea.spellcheck).toBe(false);
});
@@ -38,7 +38,7 @@ describe("MarkdownEditor with SpellCheck", () => {
/>,
);
let textarea = screen.getByRole("textbox");
let textarea: HTMLTextAreaElement = screen.getByRole("textbox") as HTMLTextAreaElement;
expect(textarea.spellcheck).toBe(true);
rerender(
@@ -49,7 +49,7 @@ describe("MarkdownEditor with SpellCheck", () => {
/>,
);
textarea = screen.getByRole("textbox");
textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
expect(textarea.spellcheck).toBe(false);
});
});

View File

@@ -93,10 +93,10 @@ const CodeEditor: FunctionComponent<ComponentProps> = (
// Handle spell check configuration for Monaco Editor
useEffect(() => {
if (editorRef.current && props.type === CodeType.Markdown) {
const editor = editorRef.current;
const domNode = editor.getDomNode();
const editor: any = editorRef.current;
const domNode: HTMLElement | null = editor.getDomNode();
if (domNode) {
const textareaElement = domNode.querySelector("textarea");
const textareaElement: HTMLTextAreaElement | null = domNode.querySelector("textarea");
if (textareaElement) {
textareaElement.spellcheck = !props.disableSpellCheck;
}
@@ -150,14 +150,14 @@ const CodeEditor: FunctionComponent<ComponentProps> = (
props.onChange(code);
}
}}
onMount={(editor, monaco) => {
onMount={(editor: any, _monaco: any) => {
editorRef.current = editor;
// Configure spell check for Markdown
if (props.type === CodeType.Markdown) {
const domNode = editor.getDomNode();
const domNode: HTMLElement | null = editor.getDomNode();
if (domNode) {
const textareaElement = domNode.querySelector("textarea");
const textareaElement: HTMLTextAreaElement | null = domNode.querySelector("textarea");
if (textareaElement) {
textareaElement.spellcheck = !props.disableSpellCheck;
}

View File

@@ -383,7 +383,7 @@ const AnnouncementTable: FunctionComponent<ComponentProps> = (
] as ObjectID;
// Find the selected template
const selectedTemplate = announcementTemplates.find((template) => {
const selectedTemplate: StatusPageAnnouncementTemplate | undefined = announcementTemplates.find((template: StatusPageAnnouncementTemplate) => {
return (
template._id?.toString() === announcementTemplateId.toString()
);

View File

@@ -12,7 +12,7 @@ export async function generateOpenAPISpec(outputPath?: string): Promise<void> {
const finalOutputPath: string = outputPath || "./openapi.json";
// Ensure the directory exists
const directory = path.dirname(finalOutputPath);
const directory: string = path.dirname(finalOutputPath);
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
}

View File

@@ -51,9 +51,9 @@ export default class FrameworkGenerator {
* Scaffold starter code for a data source, provider, or resource
*/
public static scaffold(options: FrameworkScaffoldOptions): void {
const binaryPath = this.getTerraformFrameworkGeneratorPath();
const binaryPath: string = this.getTerraformFrameworkGeneratorPath();
let command = `"${binaryPath}" scaffold ${options.type} --name "${options.name}" --output-dir "${options.outputDir}"`;
let command: string = `"${binaryPath}" scaffold ${options.type} --name "${options.name}" --output-dir "${options.outputDir}"`;
if (options.packageName) {
command += ` --package "${options.packageName}"`;
@@ -64,15 +64,20 @@ export default class FrameworkGenerator {
}
try {
// eslint-disable-next-line no-console
console.log(`🏗️ Scaffolding ${options.type}: ${options.name}`);
// eslint-disable-next-line no-console
console.log(`📁 Output directory: ${options.outputDir}`);
// eslint-disable-next-line no-console
console.log(`🔧 Running command: ${command}`);
execSync(command, { stdio: "inherit" });
// eslint-disable-next-line no-console
console.log(
`✅ Successfully scaffolded ${options.type}: ${options.name}`,
);
} catch (error) {
// eslint-disable-next-line no-console
console.error(`❌ Error scaffolding ${options.type}:`, error);
throw new Error(
`Failed to scaffold ${options.type}: ${error instanceof Error ? error.message : "Unknown error"}`,
@@ -84,18 +89,22 @@ export default class FrameworkGenerator {
subcommand: "all" | "data-sources" | "resources" | "provider",
options: FrameworkGeneratorOptions,
): void {
const binaryPath = this.getTerraformFrameworkGeneratorPath();
const binaryPath: string = this.getTerraformFrameworkGeneratorPath();
let command = `"${binaryPath}" generate ${subcommand} --input "${options.specificationPath}" --output "${options.outputPath}"`;
let command: string = `"${binaryPath}" generate ${subcommand} --input "${options.specificationPath}" --output "${options.outputPath}"`;
if (options.packageName) {
command += ` --package "${options.packageName}"`;
}
try {
// eslint-disable-next-line no-console
console.log(`🔄 Generating ${subcommand} from specification...`);
// eslint-disable-next-line no-console
console.log(`📄 Input specification: ${options.specificationPath}`);
// eslint-disable-next-line no-console
console.log(`📁 Output directory: ${options.outputPath}`);
// eslint-disable-next-line no-console
console.log(`🔧 Running command: ${command}`);
// Ensure output directory exists
@@ -104,10 +113,12 @@ export default class FrameworkGenerator {
}
execSync(command, { stdio: "inherit" });
// eslint-disable-next-line no-console
console.log(
`✅ Successfully generated ${subcommand} at: ${options.outputPath}`,
);
} catch (error) {
// eslint-disable-next-line no-console
console.error(`❌ Error generating ${subcommand}:`, error);
throw new Error(
`Failed to generate ${subcommand}: ${error instanceof Error ? error.message : "Unknown error"}`,
@@ -117,7 +128,7 @@ export default class FrameworkGenerator {
private static getTerraformFrameworkGeneratorPath(): string {
// Get the Go path and construct the full path to the tfplugingen-framework binary
const goPath = execSync("go env GOPATH", { encoding: "utf8" }).trim();
const goPath: string = execSync("go env GOPATH", { encoding: "utf8" }).trim();
return path.join(goPath, "bin", this.TOOL_NAME);
}
@@ -126,7 +137,7 @@ export default class FrameworkGenerator {
*/
public static isInstalled(): boolean {
try {
const binaryPath = this.getTerraformFrameworkGeneratorPath();
const binaryPath: string = this.getTerraformFrameworkGeneratorPath();
return fs.existsSync(binaryPath);
} catch {
return false;
@@ -137,25 +148,41 @@ export default class FrameworkGenerator {
* Print usage information for the framework generator
*/
public static printUsageInfo(): void {
// eslint-disable-next-line no-console
console.log("📖 Terraform Plugin Framework Generator Usage:");
// eslint-disable-next-line no-console
console.log("");
// eslint-disable-next-line no-console
console.log("🔄 Generate Commands:");
// eslint-disable-next-line no-console
console.log(
" generateAll() - Generate all provider code (data sources, resources, and provider)",
);
// eslint-disable-next-line no-console
console.log(" generateDataSources()- Generate only data source code");
// eslint-disable-next-line no-console
console.log(" generateResources() - Generate only resource code");
// eslint-disable-next-line no-console
console.log(" generateProvider() - Generate only provider code");
// eslint-disable-next-line no-console
console.log("");
// eslint-disable-next-line no-console
console.log("🏗️ Scaffold Commands:");
// eslint-disable-next-line no-console
console.log(
" scaffold() - Create starter code for data source, provider, or resource",
);
// eslint-disable-next-line no-console
console.log("");
// eslint-disable-next-line no-console
console.log("📋 Requirements:");
// eslint-disable-next-line no-console
console.log(" - Provider Code Specification file (JSON format)");
// eslint-disable-next-line no-console
console.log(" - tfplugingen-framework tool installed");
// eslint-disable-next-line no-console
console.log(" - Go installed and properly configured");
// eslint-disable-next-line no-console
console.log("");
}
}

View File

@@ -4,21 +4,24 @@ import FrameworkGenerator from "./FrameworkGenerator";
import SpecificationConverter from "./SpecificationConverter";
import path from "path";
async function main() {
async function main(): Promise<void> {
// eslint-disable-next-line no-console
console.log("🚀 Starting Terraform Provider Generation Process...");
try {
// 1. Generate OpenAPI spec
// eslint-disable-next-line no-console
console.log("\n📄 Step 1: Generating OpenAPI specification...");
const openApiSpecPath = path.resolve(
const openApiSpecPath: string = path.resolve(
__dirname,
"../../Terraform/openapi.json",
);
await generateOpenAPISpec(openApiSpecPath);
// 2. Convert OpenAPI spec to Provider Code Specification
// eslint-disable-next-line no-console
console.log("\n🔄 Step 2: Converting to Provider Code Specification...");
const providerSpecPath = path.resolve(
const providerSpecPath: string = path.resolve(
__dirname,
"../../Terraform/provider-code-spec.json",
);
@@ -29,23 +32,26 @@ async function main() {
});
// 3. Install Framework Generator tool
// eslint-disable-next-line no-console
console.log(
"\n🔧 Step 3: Installing Terraform Plugin Framework Generator...",
);
const frameworkInstallResult =
const frameworkInstallResult: any =
await ToolInstaller.installTerraformPluginFrameworkGenerator();
if (!frameworkInstallResult.success) {
throw new Error(
`Failed to install framework generator: ${frameworkInstallResult.message}`,
);
}
// eslint-disable-next-line no-console
console.log(`${frameworkInstallResult.message}`);
// 4. Generate Terraform Provider Framework code
// eslint-disable-next-line no-console
console.log(
"\n🏗 Step 4: Generating Terraform Provider Framework code...",
);
const frameworkOutputPath = path.resolve(
const frameworkOutputPath: string = path.resolve(
__dirname,
"../../Terraform/terraform-provider-framework",
);
@@ -56,31 +62,48 @@ async function main() {
packageName: "oneuptime", // Optional: specify a package name
});
// eslint-disable-next-line no-console
console.log("\n🎉 Provider generation completed successfully!");
// eslint-disable-next-line no-console
console.log("\n📋 Generated Files:");
// eslint-disable-next-line no-console
console.log(` 📄 OpenAPI Spec: ${openApiSpecPath}`);
// eslint-disable-next-line no-console
console.log(` 📄 Provider Code Spec: ${providerSpecPath}`);
// eslint-disable-next-line no-console
console.log(` 📁 Framework Provider Code: ${frameworkOutputPath}`);
// eslint-disable-next-line no-console
console.log("\n📖 Next Steps:");
// eslint-disable-next-line no-console
console.log(" 1. Review the generated Provider Code Specification");
// eslint-disable-next-line no-console
console.log(
" 2. Customize the specification as needed for your use case",
);
// eslint-disable-next-line no-console
console.log(
" 3. Use the Framework Generator to regenerate code after modifications",
);
// eslint-disable-next-line no-console
console.log(
" 4. Implement the actual provider logic in the generated Go files",
);
FrameworkGenerator.printUsageInfo();
} catch (error) {
console.error("\n❌ Error during provider generation:", error);
const err: Error = error as Error;
// eslint-disable-next-line no-console
console.error("\n❌ Error during provider generation:", err);
// eslint-disable-next-line no-console
console.error("\n🔍 Troubleshooting Tips:");
// eslint-disable-next-line no-console
console.error(" - Ensure Go is installed and properly configured");
// eslint-disable-next-line no-console
console.error(" - Check that GOPATH is set correctly");
// eslint-disable-next-line no-console
console.error(" - Verify internet connectivity for downloading tools");
// eslint-disable-next-line no-console
console.error(
" - Make sure you have write permissions in the output directories",
);

View File

@@ -22,7 +22,7 @@ export default class GeneratorConfig {
providerName: string;
}): void {
// Read the OpenAPI spec JSON file
const openApiSpec = JSON.parse(
const openApiSpec: any = JSON.parse(
fs.readFileSync(data.openApiSpecInJsonFilePath, "utf-8"),
);
const config: any = {
@@ -37,7 +37,7 @@ export default class GeneratorConfig {
if (openApiSpec.paths) {
for (const [pathKey, pathObj] of Object.entries(openApiSpec.paths)) {
for (const [method, opRaw] of Object.entries(pathObj as any)) {
const op = opRaw as any;
const op: any = opRaw as any;
if (
!op ||
typeof op !== "object" ||
@@ -46,27 +46,27 @@ export default class GeneratorConfig {
continue;
}
const operationId = op.operationId.toLowerCase();
const isReadOperation =
const operationId: string = op.operationId.toLowerCase();
const isReadOperation: boolean =
operationId.startsWith("get") ||
operationId.startsWith("list") ||
operationId.startsWith("count") ||
operationId.includes("read") ||
operationId.includes("fetch");
const isCreateOperation =
const isCreateOperation: boolean =
operationId.startsWith("create") ||
operationId.startsWith("add") ||
method.toLowerCase() === "post";
const isUpdateOperation =
const isUpdateOperation: boolean =
operationId.startsWith("update") ||
operationId.startsWith("put") ||
method.toLowerCase() === "put";
const isDeleteOperation =
const isDeleteOperation: boolean =
operationId.startsWith("delete") || operationId.includes("remove");
if (isReadOperation) {
// Generate data source for read operations
const dsName =
const dsName: string =
this.extractResourceNameFromPath(pathKey).toLowerCase();
if (dsName) {
if (!config.data_sources[dsName]) {
@@ -79,7 +79,7 @@ export default class GeneratorConfig {
}
// Also add as resource read operation
const resourceName =
const resourceName: string =
this.extractResourceNameFromPath(pathKey).toLowerCase();
if (resourceName) {
if (!config.resources[resourceName]) {
@@ -92,7 +92,7 @@ export default class GeneratorConfig {
}
} else if (isCreateOperation) {
// Generate resource for create operations
const resourceName =
const resourceName: string =
this.extractResourceNameFromPath(pathKey).toLowerCase();
if (resourceName) {
if (!config.resources[resourceName]) {
@@ -105,7 +105,7 @@ export default class GeneratorConfig {
}
} else if (isUpdateOperation) {
// Generate resource for update operations
const resourceName =
const resourceName: string =
this.extractResourceNameFromPath(pathKey).toLowerCase();
if (resourceName) {
if (!config.resources[resourceName]) {
@@ -118,7 +118,7 @@ export default class GeneratorConfig {
}
} else if (isDeleteOperation) {
// Handle delete operations
const resourceName =
const resourceName: string =
this.extractResourceNameFromPath(pathKey).toLowerCase();
if (resourceName) {
if (!config.resources[resourceName]) {
@@ -141,7 +141,7 @@ export default class GeneratorConfig {
for (const [resourceName, resourceConfig] of Object.entries(
config.resources,
)) {
const resource = resourceConfig as any;
const resource: any = resourceConfig as any;
// If resource doesn't have 'create', try to use 'post' operation
if (!resource.create && resource.post) {
@@ -151,7 +151,7 @@ export default class GeneratorConfig {
// If resource doesn't have 'read', try to find it in data sources
if (!resource.read) {
const matchingDataSource = config.data_sources[resourceName];
const matchingDataSource: any = config.data_sources[resourceName];
if (matchingDataSource && matchingDataSource.read) {
resource.read = matchingDataSource.read;
}
@@ -159,6 +159,7 @@ export default class GeneratorConfig {
// If resource still doesn't have both 'create' and 'read', remove it
if (!resource.create || !resource.read) {
// eslint-disable-next-line no-console
console.log(
`Removing resource '${resourceName}' - missing required operations (create: ${Boolean(resource.create)}, read: ${Boolean(resource.read)})`,
);
@@ -180,7 +181,7 @@ export default class GeneratorConfig {
}
// Convert the config object to YAML
const yamlStr = yaml.dump(config, { noRefs: true, lineWidth: 120 });
const yamlStr: string = yaml.dump(config, { noRefs: true, lineWidth: 120 });
// Ensure output directory exists
if (!fs.existsSync(data.outputPath)) {
@@ -188,7 +189,7 @@ export default class GeneratorConfig {
}
// Write the YAML string to the output file
const outputFile = path.join(data.outputPath, data.outputFileName);
const outputFile: string = path.join(data.outputPath, data.outputFileName);
fs.writeFileSync(outputFile, yamlStr, "utf-8");
}
@@ -199,8 +200,8 @@ export default class GeneratorConfig {
*/
private static extractResourceNameFromPath(path: string): string {
// Remove leading slash and anything after the first parameter
const pathParts = path.replace(/^\//, "").split("/");
let resourcePath = pathParts[0] || "";
const pathParts: string[] = path.replace(/^\//, "").split("/");
let resourcePath: string = pathParts[0] || "";
// Handle paths that end with specific patterns like /count, /get-list, etc.
if (resourcePath.includes("-count") || resourcePath.includes("-get-list")) {
@@ -208,7 +209,7 @@ export default class GeneratorConfig {
}
// Convert kebab-case to snake_case and remove special characters
const resourceName = resourcePath
const resourceName: string = resourcePath
.replace(/-/g, "") // Remove hyphens
.replace(/[^a-zA-Z0-9]/g, "") // Remove any other special characters
.toLowerCase();

View File

@@ -19,6 +19,7 @@ class ToolInstaller {
public static async installTerraformPluginCodegenOpenAPI(): Promise<InstallResult> {
try {
// eslint-disable-next-line no-console
console.log("🔧 Installing Terraform Plugin Codegen OpenAPI...");
// Check if Go is installed
@@ -30,6 +31,7 @@ class ToolInstaller {
}
// Install the tool
// eslint-disable-next-line no-console
console.log(`📦 Running: go install ${this.OPENAPI_TOOL_PACKAGE}`);
execSync(`go install ${this.OPENAPI_TOOL_PACKAGE}`, {
stdio: "inherit",
@@ -37,8 +39,9 @@ class ToolInstaller {
});
// Verify installation
const version = this.getToolVersion(this.OPENAPI_TOOL_NAME);
const version: string | null = this.getToolVersion(this.OPENAPI_TOOL_NAME);
if (version) {
// eslint-disable-next-line no-console
console.log("✅ Installation successful!");
return {
success: true,
@@ -51,6 +54,7 @@ class ToolInstaller {
message: `Installation completed but ${this.OPENAPI_TOOL_NAME} is not available in PATH`,
};
} catch (error) {
// eslint-disable-next-line no-console
console.error("❌ Installation failed:", error);
return {
success: false,
@@ -61,6 +65,7 @@ class ToolInstaller {
public static async installTerraformPluginFrameworkGenerator(): Promise<InstallResult> {
try {
// eslint-disable-next-line no-console
console.log("🔧 Installing Terraform Plugin Framework Generator...");
// Check if Go is installed
@@ -72,6 +77,7 @@ class ToolInstaller {
}
// Install the tool
// eslint-disable-next-line no-console
console.log(`📦 Running: go install ${this.FRAMEWORK_TOOL_PACKAGE}`);
execSync(`go install ${this.FRAMEWORK_TOOL_PACKAGE}`, {
stdio: "inherit",
@@ -79,8 +85,9 @@ class ToolInstaller {
});
// Verify installation
const version = this.getToolVersion(this.FRAMEWORK_TOOL_NAME);
const version: string | null = this.getToolVersion(this.FRAMEWORK_TOOL_NAME);
if (version) {
// eslint-disable-next-line no-console
console.log("✅ Framework Generator installation successful!");
return {
success: true,
@@ -93,6 +100,7 @@ class ToolInstaller {
message: `Installation completed but ${this.FRAMEWORK_TOOL_NAME} is not available in PATH`,
};
} catch (error) {
// eslint-disable-next-line no-console
console.error("❌ Framework Generator installation failed:", error);
return {
success: false,
@@ -121,8 +129,8 @@ class ToolInstaller {
} catch {
try {
// Try to find the binary in GOPATH/bin or GOBIN
const goPath = this.getGoPath();
const binaryPath = path.join(goPath, "bin", toolName);
const goPath: string = this.getGoPath();
const binaryPath: string = path.join(goPath, "bin", toolName);
if (fs.existsSync(binaryPath)) {
return "latest";
}
@@ -135,25 +143,33 @@ class ToolInstaller {
private static getGoPath(): string {
try {
const goPath = execSync("go env GOPATH", { encoding: "utf8" }).trim();
const goPath: string = execSync("go env GOPATH", { encoding: "utf8" }).trim();
return goPath;
} catch {
// Default GOPATH
const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || "";
const homeDir: string = process.env["HOME"] || process.env["USERPROFILE"] || "";
return path.join(homeDir, "go");
}
}
public static printInstallationInfo(): void {
// eslint-disable-next-line no-console
console.log("📋 Installation Information:");
// eslint-disable-next-line no-console
console.log(` OpenAPI Tool: ${this.OPENAPI_TOOL_NAME}`);
// eslint-disable-next-line no-console
console.log(` OpenAPI Package: ${this.OPENAPI_TOOL_PACKAGE}`);
// eslint-disable-next-line no-console
console.log(` Framework Tool: ${this.FRAMEWORK_TOOL_NAME}`);
// eslint-disable-next-line no-console
console.log(` Framework Package: ${this.FRAMEWORK_TOOL_PACKAGE}`);
// eslint-disable-next-line no-console
console.log(" Prerequisites: Go must be installed");
// eslint-disable-next-line no-console
console.log(
" Usage: Use different methods to install the specific tool needed",
);
// eslint-disable-next-line no-console
console.log("");
}
}
@@ -163,29 +179,38 @@ async function main(): Promise<void> {
try {
ToolInstaller.printInstallationInfo();
const result = await ToolInstaller.installTerraformPluginCodegenOpenAPI();
const result: InstallResult = await ToolInstaller.installTerraformPluginCodegenOpenAPI();
if (result.success) {
// eslint-disable-next-line no-console
console.log(`🎉 ${result.message}`);
if (result.version) {
// eslint-disable-next-line no-console
console.log(`📌 Version: ${result.version}`);
}
// Print usage instructions
// eslint-disable-next-line no-console
console.log("");
// eslint-disable-next-line no-console
console.log("📖 Usage Instructions:");
// eslint-disable-next-line no-console
console.log(
" The tfplugingen-openapi tool is now available in your PATH",
);
// eslint-disable-next-line no-console
console.log(
" You can use it to generate Terraform provider code from OpenAPI specs",
);
// eslint-disable-next-line no-console
console.log(" Example: tfplugingen-openapi generate --help");
} else {
// eslint-disable-next-line no-console
console.error(`💥 ${result.message}`);
process.exit(1);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error("🚨 Unexpected error:", error);
process.exit(1);
}

View File

@@ -8,18 +8,19 @@ export default class ProviderSpec {
outputPath: string;
}): void {
// Get the Go path and construct the full path to the tfplugingen-openapi binary
const goPath = execSync("go env GOPATH", { encoding: "utf8" }).trim();
const tfplugigenBinaryPath = path.join(
const goPath: string = execSync("go env GOPATH", { encoding: "utf8" }).trim();
const tfplugigenBinaryPath: string = path.join(
goPath,
"bin",
"tfplugingen-openapi",
);
const command = `"${tfplugigenBinaryPath}" generate --config "${options.generatorConfigPath}" --output "${options.outputPath}" "${options.openApiSpecPath}"`;
const command: string = `"${tfplugigenBinaryPath}" generate --config "${options.generatorConfigPath}" --output "${options.outputPath}" "${options.openApiSpecPath}"`;
try {
execSync(command, { stdio: "inherit" });
} catch (error) {
// eslint-disable-next-line no-console
console.error(
"Error executing Terraform provider code generation command:",
error,
@@ -29,6 +30,7 @@ export default class ProviderSpec {
);
}
// eslint-disable-next-line no-console
console.log(
"Terraform provider code generated successfully at:",
options.outputPath,

View File

@@ -48,31 +48,37 @@ export default class SpecificationConverter {
providerName: string;
}): void {
try {
// eslint-disable-next-line no-console
console.log(
"🔄 Converting OpenAPI spec to Provider Code Specification...",
);
// eslint-disable-next-line no-console
console.log(`📄 Input OpenAPI spec: ${options.openApiSpecPath}`);
// eslint-disable-next-line no-console
console.log(`📁 Output path: ${options.outputPath}`);
// Read OpenAPI specification
const openApiContent = fs.readFileSync(options.openApiSpecPath, "utf8");
const openApiContent: string = fs.readFileSync(options.openApiSpecPath, "utf8");
const openApiSpec: OpenAPISpec = JSON.parse(openApiContent);
// Generate Provider Code Specification
const providerSpec = this.generateProviderSpecification(
const providerSpec: ProviderCodeSpecification = this.generateProviderSpecification(
openApiSpec,
options.providerName,
);
// Write specification to file
const outputContent = JSON.stringify(providerSpec, null, 2);
const outputContent: string = JSON.stringify(providerSpec, null, 2);
fs.writeFileSync(options.outputPath, outputContent, "utf8");
// eslint-disable-next-line no-console
console.log(
"✅ Successfully converted OpenAPI spec to Provider Code Specification",
);
// eslint-disable-next-line no-console
console.log(`📝 Generated specification saved to: ${options.outputPath}`);
} catch (error) {
// eslint-disable-next-line no-console
console.error("❌ Error converting specification:", error);
throw new Error(
`Failed to convert specification: ${error instanceof Error ? error.message : "Unknown error"}`,
@@ -154,7 +160,7 @@ export default class SpecificationConverter {
}
// Extract resource name from path (e.g., /api/v1/monitor -> monitor)
const pathSegments = pathKey.split("/").filter((segment) => {
const pathSegments: string[] = pathKey.split("/").filter((segment: string) => {
return (
segment &&
!segment.startsWith("{") &&
@@ -167,28 +173,28 @@ export default class SpecificationConverter {
continue;
}
const lastSegment = pathSegments[pathSegments.length - 1];
const lastSegment: string | undefined = pathSegments[pathSegments.length - 1];
if (!lastSegment) {
continue;
}
const resourceName = this.sanitizeResourceName(lastSegment);
const resourceName: string = this.sanitizeResourceName(lastSegment);
if (!resourceName) {
continue;
}
// Sanitize resource name to be Terraform-compatible
const sanitizedResourceName = this.sanitizeResourceName(resourceName);
const sanitizedResourceName: string = this.sanitizeResourceName(resourceName);
// Determine if this is a resource (has POST/PUT/DELETE) or data source (only GET)
const methods = Object.keys(pathValue);
const hasWriteOperations = methods.some((method) => {
const methods: string[] = Object.keys(pathValue);
const hasWriteOperations: boolean = methods.some((method: string) => {
return ["post", "put", "patch", "delete"].includes(
method.toLowerCase(),
);
});
const schema = this.generateSchemaFromPath(
const schema: any = this.generateSchemaFromPath(
pathValue,
openApiSpec.components?.schemas,
);
@@ -196,7 +202,7 @@ export default class SpecificationConverter {
if (hasWriteOperations) {
// This is a resource
if (
!resources.find((r) => {
!resources.find((r: any) => {
return r.name === sanitizedResourceName;
})
) {
@@ -208,7 +214,7 @@ export default class SpecificationConverter {
} else if (methods.includes("get")) {
// This is a data source
if (
!datasources.find((d) => {
!datasources.find((d: any) => {
return d.name === sanitizedResourceName;
})
) {
@@ -255,9 +261,9 @@ export default class SpecificationConverter {
// Try to extract more attributes from request/response schemas if available
if (pathSpec.post?.requestBody?.content?.["application/json"]?.schema) {
const requestSchema =
const requestSchema: any =
pathSpec.post.requestBody.content["application/json"].schema;
const extractedAttributes = this.extractAttributesFromSchema(
const extractedAttributes: any[] = this.extractAttributesFromSchema(
requestSchema,
schemas,
);
@@ -281,7 +287,7 @@ export default class SpecificationConverter {
continue;
}
const attribute = this.convertPropertyToAttribute(
const attribute: any = this.convertPropertyToAttribute(
propName,
propSchema as any,
);
@@ -304,7 +310,7 @@ export default class SpecificationConverter {
}
let attributeType: any;
const computedOptionalRequired = "optional";
const computedOptionalRequired: string = "optional";
switch (schema.type) {
case "string":
@@ -348,7 +354,7 @@ export default class SpecificationConverter {
return null;
}
const typeKey = Object.keys(attributeType)[0];
const typeKey: string | undefined = Object.keys(attributeType)[0];
if (!typeKey) {
return null;
}
@@ -442,10 +448,12 @@ export default class SpecificationConverter {
],
};
const content = JSON.stringify(basicSpec, null, 2);
const content: string = JSON.stringify(basicSpec, null, 2);
fs.writeFileSync(options.outputPath, content, "utf8");
// eslint-disable-next-line no-console
console.log("✅ Generated basic Provider Code Specification template");
// eslint-disable-next-line no-console
console.log(`📝 Template saved to: ${options.outputPath}`);
}
}