diff --git a/.github/workflows/validate-cms.yml b/.github/workflows/validate-cms.yml index b370820..e776b05 100644 --- a/.github/workflows/validate-cms.yml +++ b/.github/workflows/validate-cms.yml @@ -1,6 +1,10 @@ name: Validate CMS -on: [push] +on: + push: + pull_request: + branches-ignore: + - main jobs: test: diff --git a/__tests__/validate-cms.spec.js b/__tests__/validate-cms.spec.js index 218cb5e..318f3b3 100644 --- a/__tests__/validate-cms.spec.js +++ b/__tests__/validate-cms.spec.js @@ -3,25 +3,21 @@ import fs from "fs"; import path from "path"; import { expect, test } from "vitest"; import addFormats from "ajv-formats"; +import { readJsonFiles } from "../utils"; const ajv = new Ajv(); addFormats(ajv); const directoryPath = "./cms"; -const schemaDirectoryPath = "./schema.json"; -const cmsFiles = fs.readdirSync(directoryPath); -cmsFiles.forEach((file) => { - test(`Validate ${file}`, () => { - const content = JSON.parse( - fs.readFileSync(path.resolve(directoryPath, file), "utf8") - ); - const schema = JSON.parse( - fs.readFileSync( - path.resolve(schemaDirectoryPath, content.$schema), - "utf8" - ) - ); +const cmsFiles = readJsonFiles(directoryPath); + +cmsFiles.forEach((filePath) => { + const fileName = path.basename(filePath); + test(`Validate ${fileName}`, () => { + const content = JSON.parse(fs.readFileSync(filePath, "utf8")); + const schemaPath = path.resolve(filePath, "..", content.$schema); + const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8")); const isValid = ajv.validate(schema, content); diff --git a/cms/earn/strategies.json b/cms/earn/strategies.json index e0ff761..dcc0de2 100644 --- a/cms/earn/strategies.json +++ b/cms/earn/strategies.json @@ -1,4 +1,5 @@ { + "$schema": "../../schemas/strategies.schema.json", "strategies": [ { "id": "osmo-staking", diff --git a/cms/landing-page/landing-page.json b/cms/landing-page/landing-page.json index 1e9b039..8650004 100644 --- a/cms/landing-page/landing-page.json +++ b/cms/landing-page/landing-page.json @@ -1,4 +1,5 @@ { + "$schema": "../../schemas/landing-page.schema.json", "upcomingAssets": [ { "assetName": "Wormhole", diff --git a/schemas/landing-page.schema.json b/schemas/landing-page.schema.json index ce89fbd..64a0d37 100644 --- a/schemas/landing-page.schema.json +++ b/schemas/landing-page.schema.json @@ -10,42 +10,53 @@ "assetName": { "type": "string", "description": "The name of the asset.", - "example": "Osmosis" + "examples": ["Osmosis"] }, "symbol": { "type": "string", "description": "The symbol of the asset.", - "example": "OSMO" + "examples": ["OSMO"] }, "chainName": { "type": "string", "description": "The name of the blockchain or network where the asset originates.", - "example": "Osmosis" + "examples": ["Osmosis"] }, "logoURL": { "type": "string", "format": "uri", "description": "The URL to the logo image of the asset.", - "pattern": "^https://raw.githubusercontent.com\/.+\\.(png|svg)$", - "example": "https://raw.githubusercontent.com/cosmos/chain-registry/master/osmosis/images/osmo.png" + "pattern": "^https://raw.githubusercontent.com/.+\\.(png|svg)$", + "examples": [ + "https://raw.githubusercontent.com/cosmos/chain-registry/master/osmosis/images/osmo.png" + ] }, "estimatedLaunchDate": { "type": "string", "pattern": "^(Q[1-4]|((Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(\\s\\d{1,2},)?))?\\s\\d{4}$", "description": "The estimated launch date of the asset. Accepts dates in the formats: month name + year (e.g., March 2024), month name + day + year (e.g., March 22, 2024), and quarter + year (e.g., Q2 2024).", - "example": "March 22, 2024" + "examples": ["March 22, 2024"] }, "showLaunchDate": { "type": "boolean", - "description": "Indicates whether to show the launch date of the asset." + "description": "Indicates whether to show the launch date of the asset.", + "examples": [true, false] }, "osmosisAirdrop": { "type": "boolean", "description": "Indicates whether Osmosis Stakers or LPs are eligible for an airdrop of the asset.", - "example": false + "examples": [false] } }, - "required": ["assetName", "symbol", "chainName", "logoURL", "estimatedLaunchDateUtc", "showLaunchDate", "osmosisAirdrop"] + "required": [ + "assetName", + "symbol", + "chainName", + "logoURL", + "estimatedLaunchDateUtc", + "showLaunchDate", + "osmosisAirdrop" + ] } } }, diff --git a/schemas/strategies.schema.json b/schemas/strategies.schema.json index 1c76ee7..571eacb 100644 --- a/schemas/strategies.schema.json +++ b/schemas/strategies.schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "required": [ "strategies" ], + "required": ["strategies"], "properties": { "riskReportUrl": { "type": "string", @@ -12,7 +12,23 @@ "type": "array", "items": { "type": "object", - "required": [ "id", "name", "platform", "category", "type", "link", "lockDuration", "riskLevel", "startDateTimeUtc", "unlisted", "disabled", "depositDenoms", "positionDenoms", "rewardDenoms", "tags" ], + "required": [ + "id", + "name", + "platform", + "category", + "type", + "link", + "lockDuration", + "riskLevel", + "startDateTimeUtc", + "unlisted", + "disabled", + "depositDenoms", + "positionDenoms", + "rewardDenoms", + "tags" + ], "properties": { "id": { "type": "string", @@ -25,7 +41,7 @@ "platform": { "type": "string", "description": "The platform that provides the Earn Strategy.", - "example": "Levana" + "examples": ["Levana"] }, "category": { "type": "string", @@ -41,12 +57,12 @@ "type": { "type": "string", "description": "Further classification of the strategy.", - "example": "Osmosis Staking" + "examples": ["Osmosis Staking"] }, "link": { "type": "string", "format": "uri", - "pattern": "^https:\/\/.*$", + "pattern": "^https://.*$", "description": "This link redirects users to the most appropriate interface for users participate in the strategy." }, "contract": { @@ -60,7 +76,7 @@ "oneOf": [ { "type": "string", - "pattern": "^https:\/\/.*$" + "pattern": "^https://.*$" }, { "type": "string", @@ -75,7 +91,7 @@ "oneOf": [ { "type": "string", - "pattern": "^https:\/\/.*$" + "pattern": "^https://.*$" }, { "type": "string", @@ -85,12 +101,10 @@ "description": "A URL to the data endpoint for the Annual Percentage Rate (APR) of the strategy. The response must match how it's defined in the documentation." }, "geoblock": { - "type": "string", - "format": "uri", "oneOf": [ { "type": "string", - "pattern": "^https:\/\/.*$" + "pattern": "^https://.*$" }, { "type": "string", @@ -102,7 +116,7 @@ "lockDuration": { "type": "string", "pattern": "^P(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+S)?)?$", - "example": "P14D", + "examples": ["P14D"], "description": "specify the duration following ISO 8601 standard. If the strategy does not require a lock, use 'P0S' (meaning zero seconds)." }, "riskLevel": { @@ -114,7 +128,7 @@ "startDateTimeUtc": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}Z)?$", - "example": "2049-01-01T01:01:01Z", + "examples": ["2049-01-01T01:01:01Z"], "description": "The start date and time (UTC) of a strategy not only keeps record, but can be used to pre-plan and exact time to reveal the on the interface." }, "unlisted": { @@ -153,7 +167,7 @@ "type": "array", "items": { "type": "string", - "enum": [ "Correlated", "Blue Chip", "Stablecoins" ] + "enum": ["Correlated", "Blue Chip", "Stablecoins"] } } } @@ -163,7 +177,7 @@ "$defs": { "denomObject": { "type": "object", - "required": [ "coinMinimalDenom" ], + "required": ["coinMinimalDenom"], "properties": { "coinMinimalDenom": { "type": "string", @@ -172,7 +186,7 @@ "_comment": { "type": "string", "description": "An unrestricted, informal field to informs human readers which asset is being described. Asset symbol is likely best.", - "example": "$ATOM from Cosmos Hub via IBC (transfer/channel-0)" + "examples": ["$ATOM from Cosmos Hub via IBC (transfer/channel-0)"] } } } diff --git a/scripts/translate-cms.js b/scripts/translate-cms.js index ab04db7..d94fffa 100644 --- a/scripts/translate-cms.js +++ b/scripts/translate-cms.js @@ -2,10 +2,12 @@ import fs from "fs"; import path from "path"; import prettier from "prettier"; import { spawn } from "child_process"; +import { readJsonFiles } from "../utils"; const cmsDirectoryPath = "./cms"; const cacheLocationDirectory = "./project.inlang/.cache"; -const cmsFiles = fs.readdirSync(cmsDirectoryPath); + +const cmsFiles = readJsonFiles(cmsDirectoryPath); if (!fs.existsSync(cacheLocationDirectory)) { fs.mkdirSync(cacheLocationDirectory, { recursive: true }); @@ -19,7 +21,7 @@ async function writeFile(filePath, content) { const formatted = await prettier.format(JSON.stringify(content), { parser: "json", }); - fs.writeFileSync(path.resolve(cmsDirectoryPath, filePath), formatted, { + fs.writeFileSync(filePath, formatted, { encoding: "utf8", }); } @@ -62,10 +64,9 @@ async function main() { const languageContent = {}; for (const filePath of cmsFiles) { - const namespace = filePath.split(".")[0]; - const fileContent = JSON.parse( - fs.readFileSync(path.resolve(cmsDirectoryPath, filePath), "utf8") - ); + const fileName = path.basename(filePath); + const namespace = fileName.split(".")[0]; + const fileContent = JSON.parse(fs.readFileSync(filePath, "utf8")); if (!fileContent.localization) { continue; @@ -91,19 +92,14 @@ async function main() { ]); for (const filePath of cmsFiles) { - const namespace = filePath.split(".")[0]; - - const content = JSON.parse( - fs.readFileSync(path.resolve(cmsDirectoryPath, filePath), "utf8") - ); + const fileName = path.basename(filePath); + const namespace = fileName.split(".")[0]; + const content = JSON.parse(fs.readFileSync(filePath, "utf8")); if (!content.localization) { continue; } - // Create localization file for each language tag - inlangSettings.languageTags.forEach((languageTag) => {}); - content.localization = inlangSettings.languageTags.reduce( (acc, languageTag) => { const localizationFile = JSON.parse( @@ -125,7 +121,7 @@ async function main() { {} ); - await writeFile(path.resolve(cmsDirectoryPath, filePath), content); + await writeFile(filePath, content); } } diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..3cabceb --- /dev/null +++ b/utils.js @@ -0,0 +1,16 @@ +import fs from "fs"; +import path from "path"; + +// Function to recursively read all JSON files in a directory +export function readJsonFiles(dir) { + let files = []; + fs.readdirSync(dir).forEach((file) => { + const fullPath = path.join(dir, file); + if (fs.statSync(fullPath).isDirectory()) { + files = [...files, ...readJsonFiles(fullPath)]; // Recurse into directories + } else if (path.extname(file) === ".json") { + files.push(fullPath); + } + }); + return files; +}