diff --git a/.github/workflows/release-skills.yml b/.github/workflows/release-skills.yml index da82486..57e1573 100644 --- a/.github/workflows/release-skills.yml +++ b/.github/workflows/release-skills.yml @@ -6,6 +6,10 @@ on: paths: ['skills/**'] workflow_dispatch: +permissions: + contents: write + packages: write + jobs: release: runs-on: ubuntu-latest @@ -39,7 +43,7 @@ jobs: - name: Create Github Release id: release - uses: ncipollo/release-action@v2 + uses: ncipollo/release-action@v1 if: ${{ steps.changelog.outputs.skipped == 'false' }} with: name: "${{ steps.changelog.outputs.version }}" @@ -53,6 +57,7 @@ jobs: skipIfReleaseExists: true - name: Publish to npm + id: publish if: ${{ steps.changelog.outputs.skipped == 'false' && steps.release.outputs.id != '' }} run: | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc @@ -61,7 +66,7 @@ jobs: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Notify cline-fork - if: ${{ steps.changelog.outputs.skipped == 'false' }} + if: ${{ steps.changelog.outputs.skipped == 'false' && steps.release.outputs.id != '' && steps.publish.outcome == 'success' }} uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.IDEE_GH_TOKEN }} diff --git a/.github/workflows/validate-skills.yml b/.github/workflows/validate-skills.yml index d7f6014..55e051b 100644 --- a/.github/workflows/validate-skills.yml +++ b/.github/workflows/validate-skills.yml @@ -9,8 +9,28 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' + - run: npm ci - - run: npm run validate + + - name: Detect changed skill directories + id: changed-skills + run: | + # Collect top-level skill dirs touched by this PR (relative to skills/). + # Passing explicit dirs means pre-existing nested skill categories + # (to be flattened in a separate PR) don't block unrelated skill PRs. + DIRS=$(git diff --name-only origin/${{ github.base_ref }}...HEAD \ + | grep '^skills/' \ + | cut -d'/' -f2 \ + | sort -u \ + | tr '\n' ' ') + echo "dirs=$DIRS" >> $GITHUB_OUTPUT + echo "Validating skill dirs: $DIRS" + + - name: Validate changed skills + run: npx tsx scripts/validate-skills.ts ${{ steps.changed-skills.outputs.dirs }} diff --git a/package.json b/package.json index bb49637..9004d60 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,17 @@ "files": [ "skills/" ], + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org" + }, "devDependencies": { "tsx": "^4.21.0", "@salesforce/webapp-template-app-react-sample-b2e-experimental": "*", "@salesforce/webapp-template-app-react-sample-b2x-experimental": "*" }, "scripts": { - "validate": "tsx scripts/validate-skills.ts", + "validate:skills": "tsx scripts/validate-skills.ts", "sync-react-b2e-sample": "node scripts/sync-react-b2e-sample.js", "sync-react-b2x-sample": "node scripts/sync-react-b2x-sample.js" } diff --git a/scripts/validate-skills.ts b/scripts/validate-skills.ts index 93892e2..e51d30c 100644 --- a/scripts/validate-skills.ts +++ b/scripts/validate-skills.ts @@ -1,6 +1,10 @@ #!/usr/bin/env tsx // Validates the skills/ directory structure and SKILL.md format. // Exits with code 1 if any violations are found. +// +// Usage: +// tsx scripts/validate-skills.ts # validate all skills +// tsx scripts/validate-skills.ts apex-class # validate specific skill dirs import fs from "fs" import path from "path" @@ -143,7 +147,10 @@ function parseFrontmatter(content: string): Record | null { for (const line of match[1].split(/\r?\n/)) { const colonIdx = line.indexOf(":") if (colonIdx === -1) continue - result[line.slice(0, colonIdx).trim()] = line.slice(colonIdx + 1).trim() + const key = line.slice(0, colonIdx).trim() + // Strip wrapping single or double quotes to match how consumers read values + const raw = line.slice(colonIdx + 1).trim() + result[key] = raw.replace(/^(['"])([\s\S]*)\1$/, "$2") } return result } @@ -158,6 +165,10 @@ function getFrontmatterEnd(content: string): number { // --------------------------------------------------------------------------- function validateSkill(dirName: string, dirPath: string): string[] { + if (!fs.existsSync(dirPath)) { + return [`skills/${dirName}: directory not found`] + } + const errors: string[] = [] for (const check of STRUCTURE_CHECKS) { @@ -182,13 +193,18 @@ function validateSkill(dirName: string, dirPath: string): string[] { } function main(): void { - const allErrors: string[] = [] - let checked = 0 + // If skill dir names are passed as arguments, validate only those. + // Otherwise validate all entries in skills/. + const targets = process.argv.slice(2) + const entries = targets.length > 0 ? targets : fs.readdirSync(SKILLS_DIR) - for (const entry of fs.readdirSync(SKILLS_DIR)) { + const allErrors: string[] = [] + let passed = 0 + + for (const entry of entries) { const entryErrors = validateSkill(entry, path.join(SKILLS_DIR, entry)) allErrors.push(...entryErrors) - if (entryErrors.length === 0) checked++ + if (entryErrors.length === 0) passed++ } if (allErrors.length > 0) { @@ -199,7 +215,7 @@ function main(): void { console.error("") process.exit(1) } else { - console.log(`Skill validation passed: ${checked} skill(s) checked.`) + console.log(`Skill validation passed: ${passed} of ${entries.length} skill(s) checked.`) } }