Files
ai-workflow-skill/scripts/package_skill_runtimes.sh
T

166 lines
4.2 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
readonly MANIFEST_PATH="${REPO_ROOT}/scripts/skill-bundles.json"
BUILD_DIR=""
usage() {
cat <<'EOF'
Usage:
package_skill_runtimes.sh plan
package_skill_runtimes.sh validate
package_skill_runtimes.sh package
The bundle manifest is package-oriented. Bundles in state=planned are skipped by
the package command until their package-owned entrypoints exist.
EOF
}
require_command() {
local cmd="$1"
if ! command -v "${cmd}" >/dev/null 2>&1; then
printf 'missing required command: %s\n' "${cmd}" >&2
exit 1
fi
}
emit_bundles() {
node -e '
const fs = require("node:fs");
const manifestPath = process.argv[1];
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
for (const bundle of manifest.bundles ?? []) {
const row = [
bundle.skill,
bundle.type,
bundle.runtimePackage,
bundle.entrypoint,
bundle.output,
bundle.buildState
].join("\t");
process.stdout.write(`${row}\n`);
}
' "${MANIFEST_PATH}"
}
cleanup() {
if [[ -n "${BUILD_DIR}" && -d "${BUILD_DIR}" ]]; then
rm -rf "${BUILD_DIR}"
fi
}
plan() {
printf 'skill bundle plan from %s\n' "${MANIFEST_PATH}"
while IFS=$'\t' read -r skill type runtime_package entrypoint output build_state; do
printf -- '- skill=%s type=%s state=%s runtime=%s entrypoint=%s output=%s\n' \
"${skill}" "${type}" "${build_state}" "${runtime_package}" "${entrypoint}" "${output}"
done < <(emit_bundles)
}
validate() {
local failures=0
while IFS=$'\t' read -r skill type runtime_package entrypoint output build_state; do
local runtime_path="${REPO_ROOT}/${runtime_package#./}"
local entrypoint_path="${REPO_ROOT}/${entrypoint#./}"
local output_path="${REPO_ROOT}/${output}"
if [[ "${type}" != "go-binary" ]]; then
printf 'invalid bundle type for %s: %s\n' "${skill}" "${type}" >&2
failures=1
fi
if [[ ! -d "${runtime_path}" ]]; then
printf 'missing runtime package dir for %s: %s\n' "${skill}" "${runtime_package}" >&2
failures=1
fi
if [[ ! -f "${runtime_path}/go.mod" ]]; then
printf 'missing go.mod for %s runtime package: %s\n' "${skill}" "${runtime_package}" >&2
failures=1
fi
if [[ "${build_state}" != "planned" && ! -d "${entrypoint_path}" ]]; then
printf 'missing ready entrypoint for %s: %s\n' "${skill}" "${entrypoint}" >&2
failures=1
fi
if [[ "${output}" != skills/* ]]; then
printf 'output for %s must stay under skills/: %s\n' "${skill}" "${output}" >&2
failures=1
fi
if [[ "${build_state}" != "planned" && ! -d "$(dirname "${output_path}")" ]]; then
printf 'missing output parent for %s: %s\n' "${skill}" "$(dirname "${output}")" >&2
failures=1
fi
done < <(emit_bundles)
if [[ "${failures}" -ne 0 ]]; then
exit 1
fi
printf 'skill bundle manifest validated\n'
}
package_bundles() {
validate
require_command go
require_command install
require_command mktemp
BUILD_DIR="$(mktemp -d "${TMPDIR:-/tmp}/skill-runtimes.XXXXXX")"
trap cleanup EXIT INT TERM
while IFS=$'\t' read -r skill type runtime_package entrypoint output build_state; do
if [[ "${build_state}" != "ready" ]]; then
printf 'skipping %s (%s)\n' "${skill}" "${build_state}"
continue
fi
local output_path="${REPO_ROOT}/${output}"
local artifact_path="${BUILD_DIR}/${skill}"
printf 'building %s from %s\n' "${skill}" "${entrypoint}"
(
cd "${REPO_ROOT}"
go build -trimpath -o "${artifact_path}" "${entrypoint}"
)
mkdir -p "$(dirname "${output_path}")"
install -m 0755 "${artifact_path}" "${output_path}"
printf 'installed %s\n' "${output}"
done < <(emit_bundles)
}
main() {
require_command node
local command="${1:-}"
case "${command}" in
plan)
plan
;;
validate)
validate
;;
package)
package_bundles
;;
""|-h|--help|help)
usage
;;
*)
printf 'unknown command: %s\n' "${command}" >&2
usage >&2
exit 1
;;
esac
}
main "$@"