ci: update package details and add GitHub workflows for skills validation and release

This commit is contained in:
Mohan Raj Rajamanickam 2026-03-11 13:14:25 -07:00
parent 59f7832fa0
commit aaefaa24e5
No known key found for this signature in database
GPG Key ID: 7CE83B7611FA8B8B
5 changed files with 209 additions and 3 deletions

70
.github/workflows/release-skills.yml vendored Normal file
View File

@ -0,0 +1,70 @@
name: release-skills
on:
push:
branches: [main]
paths: ['skills/**']
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
outputs:
skipped: ${{ steps.changelog.outputs.skipped }}
version: ${{ steps.changelog.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.IDEE_GH_TOKEN }}
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- run: npm ci
- name: Conventional Changelog Action
id: changelog
uses: TriPSs/conventional-changelog-action@v5
with:
git-user-name: svc-idee-bot
git-user-email: svc_idee_bot@salesforce.com
github-token: ${{ secrets.IDEE_GH_TOKEN }}
tag-prefix: ""
release-count: "0"
skip-on-empty: ${{ github.event_name == 'push' }}
version-file: "package.json"
output-file: "CHANGELOG.md"
- name: Create Github Release
id: release
uses: ncipollo/release-action@v2
if: ${{ steps.changelog.outputs.skipped == 'false' }}
with:
name: "${{ steps.changelog.outputs.version }}"
tag: "${{ steps.changelog.outputs.version }}"
commit: ${{ github.sha }}
body: |
## Changes in @salesforce/afv-skills
${{ steps.changelog.outputs.clean_changelog }}
token: ${{ secrets.IDEE_GH_TOKEN }}
skipIfReleaseExists: true
- name: Publish to npm
if: ${{ steps.changelog.outputs.skipped == 'false' && steps.release.outputs.id != '' }}
run: |
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
npm publish --access public
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Notify cline-fork
if: ${{ steps.changelog.outputs.skipped == 'false' }}
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.IDEE_GH_TOKEN }}
repository: forcedotcom/cline-fork
event-type: skills-released
client-payload: '{"version": "${{ steps.changelog.outputs.version }}"}'

10
.github/workflows/validate-pr.yml vendored Normal file
View File

@ -0,0 +1,10 @@
name: pr-validation
on:
pull_request:
types: [opened, reopened, edited]
branches: [main]
jobs:
pr-validation:
uses: salesforcecli/github-workflows/.github/workflows/validatePR.yml@main

16
.github/workflows/validate-skills.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Validate Skills
on:
pull_request:
paths: ['skills/**']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- run: npm ci
- run: node scripts/validate-skills.js

View File

@ -1,8 +1,14 @@
{
"name": "afv-library",
"description": "AI prompts and rules library for Agentforce Vibes development",
"private": true,
"name": "@salesforce/afv-skills",
"version": "1.0.0",
"description": "Salesforce skills for Agentforce Vibes",
"license": "Apache-2.0",
"files": [
"skills/*/"
],
"devDependencies": {
"@commitlint/cli": "^19.0.0",
"@commitlint/config-conventional": "^19.0.0",
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "*",
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "*"
},

104
scripts/validate-skills.js Normal file
View File

@ -0,0 +1,104 @@
#!/usr/bin/env node
// Validates the skills/ directory structure and SKILL.md format.
// Exits with code 1 if any violations are found.
const fs = require("fs")
const path = require("path")
const SKILLS_DIR = path.join(__dirname, "..", "skills")
let errors = []
let checked = 0
function parseFrontmatter(content) {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
if (!match) return null
const raw = match[1]
const result = {}
for (const line of raw.split(/\r?\n/)) {
const colonIdx = line.indexOf(":")
if (colonIdx === -1) continue
const key = line.slice(0, colonIdx).trim()
const value = line.slice(colonIdx + 1).trim()
result[key] = value
}
return result
}
function getFrontmatterEnd(content) {
const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/)
if (!match) return -1
return match[0].length
}
const topLevelEntries = fs.readdirSync(SKILLS_DIR)
for (const entry of topLevelEntries) {
const entryPath = path.join(SKILLS_DIR, entry)
const stat = fs.statSync(entryPath)
if (!stat.isDirectory()) {
errors.push(`Loose file in skills/: ${entry} (expected only directories)`)
continue
}
const skillMdPath = path.join(entryPath, "SKILL.md")
if (!fs.existsSync(skillMdPath)) {
errors.push(`Missing SKILL.md in skills/${entry}/`)
continue
}
// Check for nested subdirectories that also contain SKILL.md
const subEntries = fs.readdirSync(entryPath)
for (const sub of subEntries) {
const subPath = path.join(entryPath, sub)
if (fs.statSync(subPath).isDirectory()) {
const nestedSkillMd = path.join(subPath, "SKILL.md")
if (fs.existsSync(nestedSkillMd)) {
errors.push(
`Nested skill detected: skills/${entry}/${sub}/SKILL.md — skill directories must be exactly one level deep under skills/`
)
}
}
}
// Validate SKILL.md frontmatter and body
const content = fs.readFileSync(skillMdPath, "utf8")
const frontmatter = parseFrontmatter(content)
if (!frontmatter) {
errors.push(`skills/${entry}/SKILL.md: missing or malformed YAML frontmatter (expected --- ... --- block at top)`)
continue
}
if (!frontmatter.name) {
errors.push(`skills/${entry}/SKILL.md: missing "name" field in frontmatter`)
} else if (frontmatter.name !== entry) {
errors.push(
`skills/${entry}/SKILL.md: "name" field ("${frontmatter.name}") does not match directory name ("${entry}")`
)
}
if (!frontmatter.description || frontmatter.description.trim() === "") {
errors.push(`skills/${entry}/SKILL.md: missing or empty "description" field in frontmatter`)
}
const frontmatterEnd = getFrontmatterEnd(content)
const body = frontmatterEnd !== -1 ? content.slice(frontmatterEnd).trim() : ""
if (!body) {
errors.push(`skills/${entry}/SKILL.md: body (instructions after frontmatter) is empty`)
}
checked++
}
if (errors.length > 0) {
console.error(`\nSkill validation failed with ${errors.length} error(s):\n`)
for (const err of errors) {
console.error(`${err}`)
}
console.error("")
process.exit(1)
} else {
console.log(`Skill validation passed: ${checked} skill(s) checked.`)
}