Foreman 1.4.0: stop the skip to build, and stop shipping dead pages #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: validate | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| jobs: | |
| validate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Manifests are valid JSON with the required keys | |
| run: | | |
| python3 - <<'PY' | |
| import json, sys | |
| fail = [] | |
| mk = json.load(open('.claude-plugin/marketplace.json')) | |
| for k in ('name', 'owner', 'plugins'): | |
| if k not in mk: | |
| fail.append(f'marketplace.json missing "{k}"') | |
| for p in mk.get('plugins', []): | |
| for k in ('name', 'source', 'description'): | |
| if k not in p: | |
| fail.append(f'marketplace plugin entry missing "{k}"') | |
| pl = json.load(open('plugins/foreman/.claude-plugin/plugin.json')) | |
| if 'name' not in pl: | |
| fail.append('plugin.json missing "name"') | |
| entry = next((p for p in mk['plugins'] if p['name'] == pl['name']), None) | |
| if entry is None: | |
| fail.append('plugin.json name is not listed in marketplace.json') | |
| elif entry.get('version') != pl.get('version'): | |
| fail.append('version disagrees between marketplace.json and plugin.json') | |
| if fail: | |
| print('\n'.join(fail)); sys.exit(1) | |
| print('manifests ok') | |
| PY | |
| - name: Skill frontmatter is present and within the description limit | |
| run: | | |
| python3 - <<'PY' | |
| import re, sys | |
| t = open('plugins/foreman/skills/foreman/SKILL.md').read() | |
| m = re.match(r'^---\n(.*?)\n---\n', t, re.S) | |
| if not m: | |
| print('SKILL.md has no frontmatter'); sys.exit(1) | |
| fm = m.group(1) | |
| fail = [] | |
| if not re.search(r'^name:\s*foreman\s*$', fm, re.M): | |
| fail.append('frontmatter name is not "foreman"') | |
| d = re.search(r'^description:\s*(.+)$', fm, re.M) | |
| if not d: | |
| fail.append('frontmatter has no description') | |
| elif len(d.group(1)) > 1024: | |
| fail.append(f'description is {len(d.group(1))} chars, limit is 1024') | |
| if fail: | |
| print('\n'.join(fail)); sys.exit(1) | |
| print('frontmatter ok') | |
| PY | |
| - name: No unreplaced placeholders | |
| run: | | |
| # Bracketed last letter so this step does not match its own pattern. | |
| # CHANGELOG.md is excluded: it describes placeholders that were removed, | |
| # so quoting one there is correct rather than a defect. | |
| if grep -rn 'GH_HANDL[E]\|LINKEDIN_UR[L]\|SITE_UR[L]\|\[X\] MB' \ | |
| --include='*.md' --include='*.json' --include='*.yml' --include='*.html' \ | |
| --exclude='CHANGELOG.md' . ; then | |
| echo "unreplaced placeholder above"; exit 1 | |
| fi | |
| echo "no placeholders" | |
| - name: No em dashes or en dashes in shipped text | |
| run: | | |
| # .github is excluded so this step does not match its own pattern. | |
| if grep -rn '—\|–' \ | |
| README.md CHANGELOG.md CONTRIBUTING.md AGENTS.md plugins/ docs/ ; then | |
| echo "em dash or en dash above"; exit 1 | |
| fi | |
| echo "punctuation ok" | |
| - name: Every file SKILL.md names actually exists | |
| run: | | |
| cd plugins/foreman/skills/foreman | |
| missing=0 | |
| for f in $(grep -o '`[a-z0-9./-]*\.\(md\|html\|txt\|xml\)`' SKILL.md | tr -d '`' | sort -u); do | |
| case "$f" in | |
| CHANGELOG.md|brand.html) continue ;; | |
| esac | |
| if [ ! -f "references/$f" ] && [ ! -f "assets/$f" ] && [ ! -f "$f" ]; then | |
| echo "SKILL.md references a file that does not exist: $f"; missing=1 | |
| fi | |
| done | |
| [ "$missing" = 0 ] && echo "all referenced files present" | |
| exit $missing | |
| - name: Nothing in the plugin executes | |
| run: | | |
| # The only <script> allowed anywhere is the JSON-LD data block. | |
| if grep -rno '<script[^>]*>' plugins/ | grep -v 'application/ld+json' ; then | |
| echo "script tag above"; exit 1 | |
| fi | |
| if grep -rniE 'onclick=|onerror=|onload=|eval\(|new Function|document\.write' plugins/ ; then | |
| echo "executable content above"; exit 1 | |
| fi | |
| if find plugins -type f \( -name '*.sh' -o -name '*.py' -o -name '*.js' -o -name '*.mjs' \) | grep . ; then | |
| echo "script file above"; exit 1 | |
| fi | |
| echo "no executable content" | |
| - name: dist/foreman.skill matches the source skill | |
| run: | | |
| rm -rf /tmp/unpacked && mkdir -p /tmp/unpacked | |
| unzip -q dist/foreman.skill -d /tmp/unpacked | |
| if diff -r --exclude='.DS_Store' \ | |
| plugins/foreman/skills/foreman /tmp/unpacked/foreman ; then | |
| echo "dist archive is in sync" | |
| else | |
| echo "dist/foreman.skill has drifted from plugins/foreman/skills/foreman/" | |
| echo "rebuild it, then commit:" | |
| echo " cd plugins/foreman/skills && zip -r ../../../dist/foreman.skill foreman -x '*.DS_Store'" | |
| exit 1 | |
| fi |