diff --git a/index.ts b/index.ts index a232675..e143216 100644 --- a/index.ts +++ b/index.ts @@ -3,184 +3,184 @@ const path = require("path"); const matter = require("gray-matter"); const { execSync } = require("child_process"); -/** - * Convert git remote URL to HTTP URL - */ -function convertGitToHttpUrl(gitUrl) { - if (gitUrl.startsWith("git@github.com:")) { - return gitUrl.replace("git@github.com:", "https://github.com/"); - } - return gitUrl; -} - -/** - * Get GitHub remote URLs in HTTP and SSH format - */ -function getGithubRemoteUrls(filePath) { +function getGitHubRemoteUrl() { try { - const remoteUrl = execSync("git config --get remote.origin.url").toString().trim().replace(/https?:\/\/.*?@github\.com\//, "https://github.com/"); - const httpUrl = convertGitToHttpUrl(remoteUrl).replace( - /\.git$/, - `/tree/main/modules${filePath.replace(path.resolve(__dirname, 'modules'), '').replace(/\/README\.md$/, '')}` - ); - return { - ssh: remoteUrl, - https: httpUrl - }; + const remoteUrl = execSync("git config --get remote.origin.url") + .toString() + .trim() + .replace(/https?:\/\/.*?@github\.com\//, "https://github.com/"); + return remoteUrl.replace(/\.git$/, ""); } catch (error) { console.error("Error getting GitHub remote URL:", error.message); return null; } } -/** - * Recursively find all README.md files in “buildingblock” folders, excluding .github - */ -function findReadmes(dir) { - let results = []; - const files = fs.readdirSync(dir, { withFileTypes: true }); +function getBuildingBlockFolderUrl(filePath) { + const remoteUrl = getGitHubRemoteUrl(); + if (!remoteUrl) return null; - for (const file of files) { - const fullPath = path.join(dir, file.name); + const relativePath = filePath + .replace(path.resolve(__dirname, "modules"), "") + .replace(/\/README\.md$/, ""); + return `${remoteUrl}/tree/main/modules${relativePath}`; +} +function findReadmes(dir){ + return fs.readdirSync(dir, { withFileTypes: true }).flatMap((file) => { + const fullPath = path.join(dir, file.name); if (file.isDirectory()) { - if (file.name === ".github") continue; - results = results.concat(findReadmes(fullPath)); - } else if (file.name === "README.md" && dir.includes("buildingblock")) { - results.push(fullPath); + return file.name === ".github" ? [] : findReadmes(fullPath); } - } - - return results; + return file.name === "README.md" && dir.includes("buildingblock") + ? [fullPath] + : []; + }); } -/** - * Path to platform logo image, excluding .github - */ -function findPlatformLogos() { - const platformLogos = {}; - const modulesDir = path.resolve(__dirname, 'modules'); - const assetsDir = path.resolve(__dirname, 'website/public/assets/logos'); - const dirs = fs.readdirSync(modulesDir, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory() && dirent.name !== ".github"); - - dirs.forEach((dir) => { - const platformDir = path.join(modulesDir, dir.name); - const files = fs.readdirSync(platformDir); - - files.forEach((file) => { - if (file.endsWith(".png") || file.endsWith(".svg")) { - const sourcePath = path.join(platformDir, file); - const destinationPath = path.join(assetsDir, `${dir.name}${path.extname(file)}`); - - fs.mkdirSync(assetsDir, { recursive: true }); - - fs.copyFileSync(sourcePath, destinationPath); - - platformLogos[dir.name] = destinationPath.replace(path.resolve(__dirname, 'website/public'), "").replace(/^\/+/g, ""); - } +function copyFilesToAssets( + sourceDir, + destinationDir, + fileFilter +){ + const copiedFiles = {}; + + fs.readdirSync(sourceDir, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory() && dirent.name !== ".github") + .forEach((dir) => { + const platformDir = path.join(sourceDir, dir.name); + fs.readdirSync(platformDir) + .filter(fileFilter) + .forEach((file) => { + const sourcePath = path.join(platformDir, file); + const destinationPath = path.join( + destinationDir, + `${dir.name}${path.extname(file)}` + ); + + fs.mkdirSync(destinationDir, { recursive: true }); + fs.copyFileSync(sourcePath, destinationPath); + + copiedFiles[dir.name] = destinationPath + .replace(path.resolve(__dirname, "website/public"), "") + .replace(/^\/+/g, ""); + }); }); - }); - return platformLogos; + return copiedFiles; } -/** - * Path to buildingblock logo image in the same directory as README.md - */ -function findBuildingBlockLogo(buildingBlockDir) { - const logoFiles = []; - const assetsDir = path.resolve(__dirname, 'website/public/assets/building-block-logos'); - const files = fs.readdirSync(buildingBlockDir); +function copyPlatformLogosToAssets() { + const modulesDir = path.resolve(__dirname, "modules"); + const assetsDir = path.resolve(__dirname, "website/public/assets/logos"); + return copyFilesToAssets(modulesDir, assetsDir, (file) => + file.endsWith(".png") || file.endsWith(".svg") + ); +} - files.forEach((file) => { - if (file.endsWith(".png") || file.endsWith(".svg")) { - const { id, platform } = getIdAndPlatform(buildingBlockDir); - const sourcePath = path.join(buildingBlockDir, file); - const destinationPath = path.join(assetsDir, `${id}${path.extname(file)}`); +function copyBuildingBlockLogoToAssets(buildingBlockDir) { + const assetsDir = path.resolve( + __dirname, + "website/public/assets/building-block-logos" + ); - fs.mkdirSync(assetsDir, { recursive: true }); - fs.copyFileSync(sourcePath, destinationPath); + const logoFile = fs + .readdirSync(buildingBlockDir) + .find((file) => file.endsWith(".png")); - logoFiles.push(destinationPath.replace(path.resolve(__dirname, 'website/public'), "").replace(/^\/+/g, "")); - } - }); + if (!logoFile) return null; - return logoFiles.length > 0 ? logoFiles[0] : null; + const { id } = getIdAndPlatform(buildingBlockDir); + const sourcePath = path.join(buildingBlockDir, logoFile); + const destinationPath = path.join(assetsDir, `${id}${path.extname(logoFile)}`); + + fs.mkdirSync(assetsDir, { recursive: true }); + fs.copyFileSync(sourcePath, destinationPath); + + return destinationPath + .replace(path.resolve(__dirname, "website/public"), "") + .replace(/^\/+/g, ""); } -/** - * Parse README.md and extract relevant data - */ function parseReadme(filePath) { const buildingBlockDir = path.dirname(filePath); const content = fs.readFileSync(filePath, "utf-8"); const { data, content: body } = matter(content); const { id, platform } = getIdAndPlatform(buildingBlockDir); - const howToMatch = body.match(/## How to Use([\s\S]*?)(##|$)/); - const resourcesMatch = body.match(/## Resources([\s\S]*)/); - const inputsMatch = body.match(/## Inputs([\s\S]*?)## Outputs/); - const outputsMatch = body.match(/## Outputs([\s\S]*)/); + + const extractSection = (regex) => + body.match(regex)?.[1]?.trim() || null; const parseTable = (match) => match ? match[1] - .split("\n") - .filter((line) => line.startsWith("| line.split("|").map((s) => s.trim())) - .map(([name, description, type, _default, required]) => ({ - name: name.replace(//, "$1"), - description, - type, - required: required === "yes", - })) + .split("\n") + .filter((line) => line.startsWith("| line.split("|").map((s) => s.trim())) + .map(([name, description, type, _default, required]) => ({ + name: name.replace(//, "$1"), + description, + type, + required: required === "yes", + })) : []; - const githubUrls = getGithubRemoteUrls(filePath); - console.log(`🔗 GitHub remote URLs: ${JSON.stringify(githubUrls)}`); - console.log(`🔗 File path: ${filePath}`); + const buildingBlockUrl = getBuildingBlockFolderUrl(filePath); + const buildingBlockLogoPath = copyBuildingBlockLogoToAssets(buildingBlockDir); - const buildingBlockLogoPath = findBuildingBlockLogo(buildingBlockDir); + const backplaneDir = path.join(buildingBlockDir, "../backplane"); + const backplaneUrl = + fs.existsSync(backplaneDir) && fs.statSync(backplaneDir).isDirectory() + ? getBuildingBlockFolderUrl(backplaneDir) + : null; return { - id: id, + id, platformType: platform, logo: buildingBlockLogoPath, - githubUrls, + buildingBlockUrl, + backplaneUrl, ...data, - howToUse: howToMatch ? howToMatch[1].trim() : null, - resources: parseTable(resourcesMatch), - inputs: parseTable(inputsMatch), - outputs: parseTable(outputsMatch), + howToUse: extractSection(/## How to Use([\s\S]*?)(##|$)/), + resources: parseTable(body.match(/## Resources([\s\S]*)/)), + inputs: parseTable(body.match(/## Inputs([\s\S]*?)## Outputs/)), + outputs: parseTable(body.match(/## Outputs([\s\S]*)/)) }; } -/** - * This returns the id and platform type from the file path. - * - * @param filePath The buildingblock directory - */ function getIdAndPlatform(filePath) { - const relativePath = filePath.replace(process.cwd(), "").replace(/\\/g, "/"); + const relativePath = filePath + .replace(process.cwd(), "") + .replace(/\\/g, "/"); const pathParts = relativePath.split(path.sep).filter(Boolean); - // pathParts = [modules, , , buildingblock] const id = pathParts.slice(1, pathParts.length - 1).join("-"); - const platform = pathParts.length > 1 ? pathParts[1] : "unknown"; + const platform = pathParts[1] || "unknown"; return { id, platform }; } -const repoRoot = path.resolve(__dirname, 'modules'); -const platformLogos = findPlatformLogos(); -const readmeFiles = findReadmes(repoRoot); -const jsonData = readmeFiles.map((file) => parseReadme(file)); - -const templatesData = { - templates: jsonData, -}; - -fs.writeFileSync("website/public/assets/templates.json", JSON.stringify(templatesData, null, 2)); -console.log(`✅ Successfully processed ${readmeFiles.length} README.md files. Output saved to templates.json`); +// Main execution +function main() { + const repoRoot = path.resolve(__dirname, "modules"); + + const platformLogos = copyPlatformLogosToAssets(); + fs.writeFileSync( + "website/public/assets/platform-logos.json", + JSON.stringify(platformLogos, null, 2) + ); + console.log( + `✅ Successfully processed ${Object.entries(platformLogos).length} platform logos. Output saved to platform-logos.json` + ); + + const readmeFiles = findReadmes(repoRoot); + const jsonData = readmeFiles.map(parseReadme); + fs.writeFileSync( + "website/public/assets/templates.json", + JSON.stringify({ templates: jsonData }, null, 2) + ); + console.log( + `✅ Successfully processed ${readmeFiles.length} README.md files. Output saved to templates.json` + ); +} -fs.writeFileSync("website/public/assets/platform-logos.json", JSON.stringify(platformLogos, null, 2)); -console.log(`✅ Successfully processed ${Object.entries(platformLogos).length} platform logos. Output saved to platform-logos.json`); +main(); diff --git a/website/public/assets/building-block-logos/aks-github-connector.png b/website/public/assets/building-block-logos/aks-github-connector.png new file mode 100644 index 0000000..47f4f62 Binary files /dev/null and b/website/public/assets/building-block-logos/aks-github-connector.png differ diff --git a/website/public/assets/building-block-logos/aks-postgresql.png b/website/public/assets/building-block-logos/aks-postgresql.png new file mode 100644 index 0000000..b360079 Binary files /dev/null and b/website/public/assets/building-block-logos/aks-postgresql.png differ diff --git a/website/public/assets/building-block-logos/aws-s3_bucket.png b/website/public/assets/building-block-logos/aws-s3_bucket.png new file mode 100644 index 0000000..e31ad96 Binary files /dev/null and b/website/public/assets/building-block-logos/aws-s3_bucket.png differ diff --git a/website/public/assets/building-block-logos/azure-budget-alert.png b/website/public/assets/building-block-logos/azure-budget-alert.png new file mode 100644 index 0000000..e7489ca Binary files /dev/null and b/website/public/assets/building-block-logos/azure-budget-alert.png differ diff --git a/website/public/assets/building-block-logos/azure-key-vault.png b/website/public/assets/building-block-logos/azure-key-vault.png new file mode 100644 index 0000000..9ee9c2b Binary files /dev/null and b/website/public/assets/building-block-logos/azure-key-vault.png differ diff --git a/website/public/assets/building-block-logos/azure-postgresql.png b/website/public/assets/building-block-logos/azure-postgresql.png new file mode 100644 index 0000000..be52c23 Binary files /dev/null and b/website/public/assets/building-block-logos/azure-postgresql.png differ diff --git a/website/public/assets/building-block-logos/github-repository.png b/website/public/assets/building-block-logos/github-repository.png new file mode 100644 index 0000000..94777fd Binary files /dev/null and b/website/public/assets/building-block-logos/github-repository.png differ diff --git a/website/public/assets/templates.json b/website/public/assets/templates.json index 41db5fd..add2ac1 100644 --- a/website/public/assets/templates.json +++ b/website/public/assets/templates.json @@ -4,10 +4,8 @@ "id": "aks-github-connector", "platformType": "aks", "logo": "assets/building-block-logos/aks-github-connector.png", - "githubUrls": { - "ssh": "git@github.com:meshcloud/meshstack-hub.git", - "https": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aks/github-connector/buildingblock" - }, + "buildingBlockUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aks/github-connector/buildingblock", + "backplaneUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aks/github-connector/backplane", "name": "GitHub Actions Integration with AKS", "supportedPlatforms": [ "aks" @@ -48,10 +46,8 @@ "id": "aks-postgresql", "platformType": "aks", "logo": "assets/building-block-logos/aks-postgresql.png", - "githubUrls": { - "ssh": "git@github.com:meshcloud/meshstack-hub.git", - "https": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aks/postgresql/buildingblock" - }, + "buildingBlockUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aks/postgresql/buildingblock", + "backplaneUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aks/postgresql/backplane", "name": "PostgreSQL Integration with AKS", "supportedPlatforms": [ "aks" @@ -129,10 +125,8 @@ "id": "aws-s3_bucket", "platformType": "aws", "logo": "assets/building-block-logos/aws-s3_bucket.png", - "githubUrls": { - "ssh": "git@github.com:meshcloud/meshstack-hub.git", - "https": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aws/s3_bucket/buildingblock" - }, + "buildingBlockUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aws/s3_bucket/buildingblock", + "backplaneUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/aws/s3_bucket/backplane", "name": "AWS S3 Bucket", "supportedPlatforms": [ "aws" @@ -234,10 +228,8 @@ "id": "azure-budget-alert", "platformType": "azure", "logo": "assets/building-block-logos/azure-budget-alert.png", - "githubUrls": { - "ssh": "git@github.com:meshcloud/meshstack-hub.git", - "https": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/budget-alert/buildingblock" - }, + "buildingBlockUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/budget-alert/buildingblock", + "backplaneUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/budget-alert/backplane", "name": "Azure Subscription Budget Alert", "supportedPlatforms": [ "azure" @@ -339,10 +331,8 @@ "id": "azure-key-vault", "platformType": "azure", "logo": "assets/building-block-logos/azure-key-vault.png", - "githubUrls": { - "ssh": "git@github.com:meshcloud/meshstack-hub.git", - "https": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/key-vault/buildingblock" - }, + "buildingBlockUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/key-vault/buildingblock", + "backplaneUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/key-vault/backplane", "name": "Azure Key Vault", "supportedPlatforms": [ "azure" @@ -468,10 +458,8 @@ "id": "azure-postgresql", "platformType": "azure", "logo": "assets/building-block-logos/azure-postgresql.png", - "githubUrls": { - "ssh": "git@github.com:meshcloud/meshstack-hub.git", - "https": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/postgresql/buildingblock" - }, + "buildingBlockUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/postgresql/buildingblock", + "backplaneUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/azure/postgresql/backplane", "name": "Azure PostgreSQL Deployment", "supportedPlatforms": [ "azure" @@ -729,10 +717,8 @@ "id": "github-repository", "platformType": "github", "logo": "assets/building-block-logos/github-repository.png", - "githubUrls": { - "ssh": "git@github.com:meshcloud/meshstack-hub.git", - "https": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/github/repository/buildingblock" - }, + "buildingBlockUrl": "https://github.com/meshcloud/meshstack-hub/tree/main/modules/github/repository/buildingblock", + "backplaneUrl": null, "name": "GitHub Repository Creation", "supportedPlatforms": [ "github" diff --git a/website/src/app/core/template.ts b/website/src/app/core/template.ts index c580f6f..dab707e 100644 --- a/website/src/app/core/template.ts +++ b/website/src/app/core/template.ts @@ -5,10 +5,8 @@ export interface Template { description: string; platformType: PlatformType; howToUse: string; - githubUrls: { - ssh: string; - https: string; - }; + buildingBlockUrl: string; + backplaneUrl: string | null; supportedPlatforms: PlatformType[]; } diff --git a/website/src/app/features/template-details/template-details.component.html b/website/src/app/features/template-details/template-details.component.html index c5d12e1..3e28e66 100644 --- a/website/src/app/features/template-details/template-details.component.html +++ b/website/src/app/features/template-details/template-details.component.html @@ -26,7 +26,7 @@

{{ template.description }}

-
+

Source @@ -50,21 +50,34 @@

-
-

How to use this template

+
+

How to use this definition

+
OR

Option 2: Copy the Files into your own Repository

Alternatively, you can copy the Terraform files needed and take ownership of the code.

  1. Copy the files in your own repository
  2. +
  3. + Prepare for adding the definition into meshStack by running the + Terraform backplane + files +
  4. Open meshStack and create a new building block definition
  5. Follow the instructions in the wizard and make sure you select "Terraform" as implementation type @@ -92,4 +105,4 @@

    Option 2: Copy the Files into your own Repository

-
+
\ No newline at end of file diff --git a/website/src/app/features/template-details/template-details.component.scss b/website/src/app/features/template-details/template-details.component.scss index db93c37..21f810e 100644 --- a/website/src/app/features/template-details/template-details.component.scss +++ b/website/src/app/features/template-details/template-details.component.scss @@ -7,3 +7,7 @@ white-space: pre-wrap; line-height: 2rem; } + +ol li { + margin-bottom: 0.5rem; +} diff --git a/website/src/app/features/template-details/template-details.component.ts b/website/src/app/features/template-details/template-details.component.ts index 7fd95b3..2ff133c 100644 --- a/website/src/app/features/template-details/template-details.component.ts +++ b/website/src/app/features/template-details/template-details.component.ts @@ -19,6 +19,7 @@ interface TemplateDetailsVm { description: string; howToUse: string; source: string; + backplaneUrl: string | null; } @Component({ @@ -100,7 +101,7 @@ export class TemplateDetailsComponent implements OnInit, OnDestroy { map(template => ({ ...template, imageUrl: template.logo, - source: template.githubUrls.https, + source: template.buildingBlockUrl, howToUse: template.howToUse })) );