mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-09 00:42:46 +08:00
ci: update package details and add GitHub workflows for skills validation and release
This commit is contained in:
parent
59f7832fa0
commit
aaefaa24e5
70
.github/workflows/release-skills.yml
vendored
Normal file
70
.github/workflows/release-skills.yml
vendored
Normal 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
10
.github/workflows/validate-pr.yml
vendored
Normal 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
16
.github/workflows/validate-skills.yml
vendored
Normal 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
|
||||||
12
package.json
12
package.json
@ -1,8 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "afv-library",
|
"name": "@salesforce/afv-skills",
|
||||||
"description": "AI prompts and rules library for Agentforce Vibes development",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"description": "Salesforce skills for Agentforce Vibes",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"files": [
|
||||||
|
"skills/*/"
|
||||||
|
],
|
||||||
"devDependencies": {
|
"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-b2e-experimental": "*",
|
||||||
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "*"
|
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "*"
|
||||||
},
|
},
|
||||||
|
|||||||
104
scripts/validate-skills.js
Normal file
104
scripts/validate-skills.js
Normal 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.`)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user