diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000..fe357e2 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,32 @@ +name: "Copilot Setup Steps" + +# Automatically run the setup steps when they are changed to allow for easy validation, and +# allow manual testing through the repository's "Actions" tab +on: + workflow_dispatch: + pull_request: + branches: [main] + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + + # Set the permissions to the lowest permissions possible needed for your steps. + # Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v2.x diff --git a/.github/workflows/validate-conformance.yml b/.github/workflows/validate-conformance.yml new file mode 100644 index 0000000..9e9fbee --- /dev/null +++ b/.github/workflows/validate-conformance.yml @@ -0,0 +1,41 @@ +name: Validate Conformance Report + +on: + pull_request: + paths: + - 'data/conformance.json' + - 'scripts/generate-conformance-report.ts' + - 'CONFORMANCE.md' + push: + branches: + - main + paths: + - 'data/conformance.json' + - 'scripts/generate-conformance-report.ts' + +jobs: + validate: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup Deno + uses: denoland/setup-deno@v1 + with: + deno-version: v2.x + + - name: Generate conformance report + run: deno task generate + + - name: Check if CONFORMANCE.md is up to date + run: | + if ! git diff --exit-code CONFORMANCE.md; then + echo "Error: CONFORMANCE.md is not up to date with conformance.json" + echo "Please run 'deno task generate' and commit the changes" + exit 1 + fi diff --git a/.github/workflows/validate-docs.yml b/.github/workflows/validate-docs.yml new file mode 100644 index 0000000..1eeacad --- /dev/null +++ b/.github/workflows/validate-docs.yml @@ -0,0 +1,90 @@ +name: Validate Documentation + +on: + pull_request: + paths: + - '**.md' + push: + branches: + - main + paths: + - '**.md' + +jobs: + validate-links: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Check for broken relative links + run: | + echo "Checking for broken relative links in markdown files..." + + # Function to check if a file exists + check_link() { + local file="$1" + local link="$2" + local dir=$(dirname "$file") + + # Skip external links + if [[ "$link" =~ ^https?:// ]]; then + return 0 + fi + + # Skip anchors only + if [[ "$link" =~ ^# ]]; then + return 0 + fi + + # Remove anchor from link + local path="${link%%#*}" + + # Resolve relative path + local full_path="$dir/$path" + + if [ ! -e "$full_path" ]; then + echo "::error file=$file::Broken link found: $link (resolved to $full_path)" + return 1 + fi + return 0 + } + + error_count=0 + + # Find all markdown files and check their links + while IFS= read -r file; do + # Extract markdown links [text](url) + grep -oP '\[([^\]]+)\]\(([^\)]+)\)' "$file" | while IFS= read -r match; do + link=$(echo "$match" | sed -n 's/.*](\([^)]*\)).*/\1/p') + if ! check_link "$file" "$link"; then + error_count=$((error_count + 1)) + fi + done + done < <(find . -name "*.md" -not -path "./.git/*" -not -path "./node_modules/*") + + if [ $error_count -gt 0 ]; then + echo "Found $error_count broken link(s)" + exit 1 + fi + + echo "✓ No broken links found" + + markdown-lint: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Run markdown lint + uses: DavidAnson/markdownlint-cli2-action@v18 + with: + globs: '**/*.md' diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..77032d0 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,19 @@ +{ + "default": true, + "MD007": false, + "MD009": false, + "MD012": false, + "MD013": false, + "MD022": false, + "MD026": false, + "MD030": false, + "MD031": false, + "MD032": false, + "MD033": false, + "MD034": false, + "MD036": false, + "MD041": false, + "MD047": false, + "MD052": false, + "MD060": false +} diff --git a/CONFORMANCE.md b/CONFORMANCE.md index 0196ddf..b9de90d 100644 --- a/CONFORMANCE.md +++ b/CONFORMANCE.md @@ -47,7 +47,7 @@ This table shows the percentage of the 17 libraries listed below that meet diffe | **Java Libraries** | **OCPS 1.0** | **OCPS 1.1** | **OCPS 1.2** | **OCPS 1.3** | **OCPS 1.4** | **Notes** | | :--- | :---: | :---: | :---: | :---: | :---: | :--- | | **Spring Framework (`@Scheduled`)** | 🟡 | ❌ | ✅ | ✅ | 🟡 | Supports seconds, `L`, `W`, `#`. Uses `AND` logic for date fields. `?` is a wildcard. No nicknames. [7] | -| **Quartz** | 🟡 | ❌ | ✅ | ✅ | 🟡 | Original source for `L`, `W`, `#`. Does not allow combining dom ad dow, and but requires `?` to disambiguate date fields. No nicknames. [8][9] | +| **Quartz** | 🟡 | ❌ | ✅ | ✅ | 🟡 | Original source for `L`, `W`, `#`. Does not allow combining dom and dow, but requires `?` to disambiguate date fields. No nicknames. [8][9] | ### .NET Libraries @@ -128,6 +128,6 @@ This table shows the percentage of the 17 libraries listed below that meet diffe --- -*This document is automatically generated from [data/conformance.json](./data/conformance.json) on 2025-10-28.* +*This document is automatically generated from [data/conformance.json](./data/conformance.json) on 2025-11-19.* *This table is based on analysis of official documentation and community knowledge. For the most accurate details, please refer to the documentation of the respective libraries.* \ No newline at end of file diff --git a/README.md b/README.md index 7933ff2..12c74b0 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,16 @@ Changes to this **README** should always be submitted as a pull request. The Implementation Conformance Matrix is automatically generated from the `data/conformance.json` file. To propose changes, please edit `data/conformance.json` and submit a pull request. -If you wish to regenerate the `CONFORMANCE.md` file locally to preview your changes, you can run the generation script. This requires Deno to be installed. +If you wish to regenerate the `CONFORMANCE.md` file locally to preview your changes, you can run the generation script. This requires [Deno](https://deno.land/) to be installed. -**From the root of the repository:** -`cd scripts && deno run -A generate-conformance-report.ts` +**Using the deno task (recommended):** +```bash +deno task generate +``` + +**Or directly:** +```bash +cd scripts && deno run -A generate-conformance-report.ts +``` + +**Note:** Pull requests that modify `data/conformance.json` will be automatically validated by GitHub Actions to ensure `CONFORMANCE.md` is kept in sync. diff --git a/data/conformance.json b/data/conformance.json index 1cf93ab..e7f638d 100644 --- a/data/conformance.json +++ b/data/conformance.json @@ -24,7 +24,7 @@ "name": "Java Libraries", "items": [ { "name": "Spring Framework (`@Scheduled`)", "compliance": { "1.0": "partial", "1.1": "none", "1.2": "full", "1.3": "full", "1.4": "partial" }, "notes": "Supports seconds, `L`, `W`, `#`. Uses `AND` logic for date fields. `?` is a wildcard. No nicknames.", "references": [7] }, - { "name": "Quartz", "compliance": { "1.0": "partial", "1.1": "none", "1.2": "full", "1.3": "full", "1.4": "partial" }, "notes": "Original source for `L`, `W`, `#`. Does not allow combining dom ad dow, and but requires `?` to disambiguate date fields. No nicknames.", "references": [8, 9] } + { "name": "Quartz", "compliance": { "1.0": "partial", "1.1": "none", "1.2": "full", "1.3": "full", "1.4": "partial" }, "notes": "Original source for `L`, `W`, `#`. Does not allow combining dom and dow, but requires `?` to disambiguate date fields. No nicknames.", "references": [8, 9] } ] }, { diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..7104c8a --- /dev/null +++ b/deno.json @@ -0,0 +1,12 @@ +{ + "tasks": { + "generate": "cd scripts && deno run -A generate-conformance-report.ts", + "generate:check": "cd scripts && deno run -A generate-conformance-report.ts && cd .. && git diff --exit-code CONFORMANCE.md" + }, + "fmt": { + "exclude": ["data/", "CONFORMANCE.md"] + }, + "lint": { + "exclude": ["data/", "CONFORMANCE.md"] + } +} diff --git a/scripts/generate-conformance-report.ts b/scripts/generate-conformance-report.ts index 6d4f603..6cef0e0 100644 --- a/scripts/generate-conformance-report.ts +++ b/scripts/generate-conformance-report.ts @@ -73,14 +73,31 @@ interface ConformanceData { async function generateConformanceReport() { try { // 1. Read and parse the JSON data + console.log('Reading conformance.json...'); const jsonText = await Deno.readTextFile('../data/conformance.json'); - const data: ConformanceData = JSON.parse(jsonText); + + let data: ConformanceData; + try { + data = JSON.parse(jsonText); + } catch (parseError) { + console.error('Failed to parse conformance.json:', parseError); + Deno.exit(1); + } + + // Validate data structure + if (!data.legend || !data.categories || !data.references) { + console.error('Invalid conformance.json structure: missing required fields'); + Deno.exit(1); + } const versions = getVersions(data); const libraries = getLibraries(data); const usedReferenceIds = getUsedReferenceIds(data); + console.log(`Found ${versions.length} versions and ${libraries.length} libraries`); + // 2. Generate dynamic parts of the Markdown + console.log('Generating report sections...'); const legend = generateLegend(data.legend); const adoptionSummary = generateAdoptionSummary(versions, libraries); const tables = generateAllTables(data.categories, versions, data.legend); @@ -98,11 +115,13 @@ async function generateConformanceReport() { .trim(); // 4. Write the output file + console.log('Writing CONFORMANCE.md...'); await Deno.writeTextFile('../CONFORMANCE.md', md); - console.log('Successfully generated CONFORMANCE.md'); + console.log('✓ Successfully generated CONFORMANCE.md'); } catch (error) { console.error('Error generating conformance report:', error); + Deno.exit(1); } }