diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..3c1b731 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,15 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/universal +{ + "customizations": { + "vscode": { + "extensions": [ + "denoland.vscode-deno", + "GitHub.vscode-github-actions" + ] + } + }, + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": ".devcontainer/postCreate.sh" +} diff --git a/.devcontainer/postCreate.sh b/.devcontainer/postCreate.sh new file mode 100755 index 0000000..dcfb39c --- /dev/null +++ b/.devcontainer/postCreate.sh @@ -0,0 +1,6 @@ +#!/bin/bash +curl -fsSL https://deno.land/install.sh | CI=devcontainer sh +echo 'export PATH=$PATH:~/.deno/bin' >> ~/.bashrc +echo 'export PATH=$PATH:~/.deno/bin' >> ~/.zshrc +echo 'export DENO_INSTALL=~/.deno' >> ~/.bashrc +echo 'export DENO_INSTALL=~/.deno' >> ~/.zshrc \ No newline at end of file diff --git a/.github/scripts/parse.ts b/.github/scripts/parse.ts new file mode 100644 index 0000000..837000d --- /dev/null +++ b/.github/scripts/parse.ts @@ -0,0 +1,9 @@ +import { CoreMods } from "./types/core-mods.ts"; + +// Read data +const jsonText = await Deno.readTextFile("core_mods.json"); + +const data = JSON.parse(jsonText) as CoreMods; +const stringifiedData = JSON.stringify(data, null, " "); + +console.log(stringifiedData); \ No newline at end of file diff --git a/.github/scripts/types/core-mods.ts b/.github/scripts/types/core-mods.ts new file mode 100644 index 0000000..15e18f6 --- /dev/null +++ b/.github/scripts/types/core-mods.ts @@ -0,0 +1,11 @@ +export interface CoreMods { + lastUpdated: string; + mods: Mod[]; +} + +export interface Mod { + id: string; + version: string; + downloadLink: string; + filename?: string; +} \ No newline at end of file diff --git a/.github/scripts/validate-schema.ts b/.github/scripts/validate-schema.ts new file mode 100644 index 0000000..535f9b9 --- /dev/null +++ b/.github/scripts/validate-schema.ts @@ -0,0 +1,24 @@ +import Ajv from "https://esm.sh/ajv@8"; +import addFormats from "https://esm.sh/ajv-formats"; + +// Read schema and data +const schemaText = await Deno.readTextFile("core_mods.schema.json"); +const jsonText = await Deno.readTextFile("core_mods.json"); + +const schema = JSON.parse(schemaText); +const data = JSON.parse(jsonText); + +// Create AJV instance with formats (for date-time, uri, etc.) +const ajv = new Ajv({ allErrors: true, strict: false }); +addFormats(ajv); + +const validate = ajv.compile(schema); +const valid = validate(data); + +if (valid) { + console.log("%ccore_mods.json is valid.", "color: green"); +} else { + console.error("%cValidation errors:", "color: red"); + console.error(validate.errors); + Deno.exit(1); +} \ No newline at end of file diff --git a/.github/scripts/validate-timestamps.ts b/.github/scripts/validate-timestamps.ts new file mode 100644 index 0000000..7c34c15 --- /dev/null +++ b/.github/scripts/validate-timestamps.ts @@ -0,0 +1,34 @@ +// Read and parse the JSON file asynchronously using Deno's API. +const fileText = await Deno.readTextFile("core_mods.json"); +const parsedData = JSON.parse(fileText); + +const GREEN = "\x1b[32m"; +const RED = "\x1b[31m"; +const RESET = "\x1b[0m"; + +function isStrictISO8601(value: string): boolean { + const ISO_8601_FULL = /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:[+-]\d\d:\d\d|Z)?$/i; + return ISO_8601_FULL.test(value); +} + +let errored = false; + +for (const [version, data] of Object.entries(parsedData)) { + if (version === "$schema") { + continue; + } + + const valid = isStrictISO8601(data.lastUpdated); + console.log( + `${version}: ${data.lastUpdated} is ${ + valid ? `${GREEN}VALID${RESET}` : `${RED}INVALID${RESET}` + }` + ); + if (!valid) { + errored = true; + } +} + +if (errored) { + Deno.exit(1); +} diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..9fe89b0 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,57 @@ +name: Validate core_mods.json + +on: + pull_request: + push: + branches: 'main' + +jobs: + validate: + name: ${{ matrix.job.name }} + runs-on: ubuntu-latest + continue-on-error: true + strategy: + matrix: + job: + - name: Parse JSON + script: parse.ts + - name: Validate lastUpdated timestamps + script: validate-timestamps.ts + - name: Validate against schema + script: validate-schema.ts + + steps: + - name: Checkout repository + if: github.event_name != 'pull_request' + uses: actions/checkout@v4 + + - name: Checkout main branch + if: github.event_name == 'pull_request' + uses: actions/checkout@v4 + with: + ref: main + + - name: Checkout PR changes + if: github.event_name == 'pull_request' + uses: actions/checkout@v4 + with: + path: pr + + - name: Copy core_mods.json and schema from PR + if: github.event_name == 'pull_request' + run: | + mv pr/core_mods.json core_mods.json + + if [[ -f pr/core_mods.schema.json ]]; then + mv pr/core_mods.schema.json core_mods.schema.json + fi + + rm -rf pr + + - uses: denoland/setup-deno@v2 + + - name: Run script ${{ matrix.job.script }} + shell: bash + run: | + deno run --allow-read ".github/scripts/${{ matrix.job.script }}" + \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..67b06df --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "denoland.vscode-deno", + "github.vscode-github-actions" + ] +} \ No newline at end of file