mirror of
https://github.com/antonbabenko/terraform-skill.git
synced 2026-09-18 20:07:05 +08:00
349024da7d
Adds Kiro Power distribution: root POWER.md generated from SKILL.md (build-power.js, CI --check), root mcp.json (terraform-mcp-server), release pre-commit regeneration, and CI validation. Single source of truth stays SKILL.md.
134 lines
4.5 KiB
JavaScript
134 lines
4.5 KiB
JavaScript
'use strict'
|
|
|
|
/**
|
|
* conventional-changelog-action@v5 pre-commit hook.
|
|
*
|
|
* Runs AFTER the changelog/version-file bump and BEFORE the action's
|
|
* git add/commit/tag. Files staged here land in the release commit AND
|
|
* the tag, so tag-pinned consumers (agent-plugins pins ref:vX.Y.Z) get a
|
|
* tree whose SKILL.md / .codex-plugin/plugin.json versions match the tag.
|
|
*
|
|
* This replaces the old post-tag amend + force-push, which left the tag
|
|
* pointing at a pre-sync tree.
|
|
*
|
|
* Node built-ins only (fs, path, child_process). No npm deps. The action
|
|
* does NOT auto-stage hook-modified files (only version-file + the
|
|
* changelog), so the hook stages them explicitly.
|
|
*
|
|
* SKILL.md metadata.version stays the single CI-owned version source;
|
|
* .codex-plugin/plugin.json mirrors it for the Codex plugin host.
|
|
*/
|
|
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const { execSync } = require('child_process')
|
|
const { buildPower } = require('./build-power')
|
|
|
|
const SKILL_REL = 'skills/terraform-skill/SKILL.md'
|
|
const MANIFEST_REL = '.codex-plugin/plugin.json'
|
|
const POWER_REL = 'POWER.md'
|
|
const SENTINEL_REL = 'version.json'
|
|
|
|
function repoRoot() {
|
|
const root = process.env.GITHUB_WORKSPACE || process.cwd()
|
|
if (!fs.existsSync(path.join(root, SENTINEL_REL))) {
|
|
throw new Error(
|
|
`pre-commit: sentinel ${SENTINEL_REL} not found in repo root ` +
|
|
`${root}; refusing to run (wrong working directory?)`
|
|
)
|
|
}
|
|
return root
|
|
}
|
|
|
|
// Rewrite the SKILL.md YAML frontmatter " version: X.Y.Z" line in place,
|
|
// preserving the exact 2-space indent (version is a child of metadata:).
|
|
function updateSkillVersion(root, version) {
|
|
const file = path.join(root, SKILL_REL)
|
|
if (!fs.existsSync(file)) {
|
|
throw new Error(`pre-commit: ${SKILL_REL} not found`)
|
|
}
|
|
const lines = fs.readFileSync(file, 'utf8').split('\n')
|
|
let updated = false
|
|
// Frontmatter is the leading block; the version line sits within the
|
|
// first handful of lines. Bound the search to avoid a body match.
|
|
const limit = Math.min(lines.length, 15)
|
|
for (let i = 0; i < limit; i++) {
|
|
if (lines[i].trimStart().startsWith('version:')) {
|
|
lines[i] = ` version: ${version}`
|
|
updated = true
|
|
break
|
|
}
|
|
}
|
|
if (!updated) {
|
|
throw new Error(
|
|
`pre-commit: version line not found in ${SKILL_REL} frontmatter`
|
|
)
|
|
}
|
|
fs.writeFileSync(file, lines.join('\n'))
|
|
return SKILL_REL
|
|
}
|
|
|
|
// Value-only replace of the top-level " "version": "..."" line.
|
|
// Anchored to a 2-space indent; rewrites only the quoted value so the
|
|
// trailing comma / key position / byte layout are untouched (no JSON
|
|
// re-serialize, no diff noise, position-independent).
|
|
function updateCodexManifest(root, version) {
|
|
const file = path.join(root, MANIFEST_REL)
|
|
if (!fs.existsSync(file)) {
|
|
return null
|
|
}
|
|
const src = fs.readFileSync(file, 'utf8')
|
|
const re = /^( {2}"version":\s*)"[^"]*"/m
|
|
if (!re.test(src)) {
|
|
throw new Error(
|
|
`pre-commit: top-level "version" line not found in ${MANIFEST_REL}`
|
|
)
|
|
}
|
|
fs.writeFileSync(file, src.replace(re, `$1"${version}"`))
|
|
return MANIFEST_REL
|
|
}
|
|
|
|
// Regenerate the Kiro POWER.md from the (already version-synced) SKILL.md so
|
|
// the release commit and tag carry an in-sync tree, same as the codex
|
|
// manifest. Must run AFTER updateSkillVersion + updateCodexManifest.
|
|
function updatePowerFile(root) {
|
|
fs.writeFileSync(path.join(root, POWER_REL), buildPower(root))
|
|
return POWER_REL
|
|
}
|
|
|
|
async function preCommit(props) {
|
|
const version = props && props.version
|
|
if (!version || typeof version !== 'string') {
|
|
throw new Error(`pre-commit: invalid version from action: ${version}`)
|
|
}
|
|
|
|
let root
|
|
try {
|
|
root = repoRoot()
|
|
const skill = updateSkillVersion(root, version)
|
|
console.log(`pre-commit: synced ${skill} -> ${version}`)
|
|
|
|
const manifest = updateCodexManifest(root, version)
|
|
if (manifest) {
|
|
console.log(`pre-commit: synced ${manifest} -> ${version}`)
|
|
} else {
|
|
console.log(`pre-commit: ${MANIFEST_REL} absent; skipped`)
|
|
}
|
|
|
|
const power = updatePowerFile(root)
|
|
console.log(`pre-commit: regenerated ${power} -> ${version}`)
|
|
|
|
// The action stages only version-file + changelog. Stage ours so
|
|
// they are in the release commit and the tag.
|
|
const toStage = [skill, power]
|
|
if (manifest) toStage.push(manifest)
|
|
execSync(`git add ${toStage.join(' ')}`, { cwd: root, stdio: 'inherit' })
|
|
} catch (err) {
|
|
// Fail loud so the release job stops rather than tagging a stale tree.
|
|
console.error(`pre-commit: ${err && err.message ? err.message : err}`)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
module.exports = { preCommit }
|