diff --git a/migrate/skill/scripts/analyze.sh b/migrate/skill/scripts/analyze.sh index f1e32928f..b8e3b64b0 100755 --- a/migrate/skill/scripts/analyze.sh +++ b/migrate/skill/scripts/analyze.sh @@ -134,9 +134,9 @@ CRON_CMDS_FILE="${OUTPUT_DIR}/cron-commands.txt" CRON_FILE="${STATE_DIR}/cron/jobs.json" if [ -f "${CRON_FILE}" ] && command -v jq &>/dev/null; then # Extract text from agentTurn payloads and scan for command references - jq -r '.[].payload.agentTurn.parts[]?.text // empty' "${CRON_FILE}" 2>/dev/null | \ + jq -r '(if type == "object" then (.jobs // []) else . end)[] | .payload.agentTurn.parts[]?.text // empty' "${CRON_FILE}" 2>/dev/null | \ grep -oE '`[a-zA-Z_][a-zA-Z0-9_-]*`' | tr -d '`' >> "${CRON_CMDS_FILE}" || true - jq -r '.[].payload.agentTurn.parts[]?.text // empty' "${CRON_FILE}" 2>/dev/null | \ + jq -r '(if type == "object" then (.jobs // []) else . end)[] | .payload.agentTurn.parts[]?.text // empty' "${CRON_FILE}" 2>/dev/null | \ grep -oE '^\s*[a-zA-Z_][a-zA-Z0-9_-]*' | sed 's/^[[:space:]]*//' >> "${CRON_CMDS_FILE}" || true fi @@ -249,10 +249,10 @@ fi log "Step 7: Generating tool-analysis.json..." # Build JSON using jq -APT_JSON=$(printf '%s\n' "${APT_PACKAGES[@]}" 2>/dev/null | jq -R . | jq -s . 2>/dev/null || echo '[]') -PIP_JSON=$(printf '%s\n' "${PIP_PACKAGES[@]}" 2>/dev/null | jq -R . | jq -s . 2>/dev/null || echo '[]') -NPM_JSON=$(printf '%s\n' "${NPM_PACKAGES[@]}" 2>/dev/null | jq -R . | jq -s . 2>/dev/null || echo '[]') -UNKNOWN_JSON=$(printf '%s\n' "${UNKNOWN_BINARIES[@]}" 2>/dev/null | jq -R . | jq -s . 2>/dev/null || echo '[]') +APT_JSON=$(jq -cn --args '$ARGS.positional' "${APT_PACKAGES[@]}" 2>/dev/null || echo '[]') +PIP_JSON=$(jq -cn --args '$ARGS.positional' "${PIP_PACKAGES[@]}" 2>/dev/null || echo '[]') +NPM_JSON=$(jq -cn --args '$ARGS.positional' "${NPM_PACKAGES[@]}" 2>/dev/null || echo '[]') +UNKNOWN_JSON=$(jq -cn --args '$ARGS.positional' "${UNKNOWN_BINARIES[@]}" 2>/dev/null || echo '[]') # Count commands per source SKILL_COUNT=$(wc -l < "${SKILL_CMDS_FILE}" 2>/dev/null | tr -d ' ') diff --git a/migrate/skill/tests/test-analyze-cron.sh b/migrate/skill/tests/test-analyze-cron.sh new file mode 100755 index 000000000..73b263805 --- /dev/null +++ b/migrate/skill/tests/test-analyze-cron.sh @@ -0,0 +1,95 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ANALYZER="${ANALYZER:-${SCRIPT_DIR}/../scripts/analyze.sh}" + +if ! command -v jq >/dev/null 2>&1; then + echo "SKIP: jq is required" + exit 0 +fi + +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "${TMP_DIR}"' EXIT + +HOME_DIR="${TMP_DIR}/home" +STATE_DIR="${TMP_DIR}/state" +WORKSPACE_DIR="${STATE_DIR}/workspace" +OUTPUT_DIR="${TMP_DIR}/output" +BIN_DIR="${TMP_DIR}/bin" +mkdir -p "${HOME_DIR}" "${WORKSPACE_DIR}" "${STATE_DIR}/cron" "${OUTPUT_DIR}" "${BIN_DIR}" + +jq -n --arg workspace "${WORKSPACE_DIR}" '{ + agents: {defaults: {workspace: $workspace}} +}' > "${STATE_DIR}/openclaw.json" + +jq -n '{ + version: 1, + jobs: [{ + payload: { + agentTurn: { + parts: [{text: "`customcron`"}] + } + } + }] +}' > "${STATE_DIR}/cron/jobs.json" + +printf '%s\n' '#!/bin/sh' 'exit 0' > "${BIN_DIR}/customcron" +printf '%s\n' '#!/bin/sh' 'exit 0' > "${BIN_DIR}/legacycron" +chmod +x "${BIN_DIR}/customcron" +chmod +x "${BIN_DIR}/legacycron" + +HOME="${HOME_DIR}" PATH="${BIN_DIR}:${PATH}" bash "${ANALYZER}" \ + --state-dir "${STATE_DIR}" \ + --output "${OUTPUT_DIR}" >/dev/null + +if ! jq -e ' + .unknown_binaries == ["customcron"] and + .analysis_sources.cron_payload_commands == 1 +' "${OUTPUT_DIR}/tool-analysis.json" >/dev/null; then + echo "FAIL: current-format cron dependency was not analyzed" >&2 + cat "${OUTPUT_DIR}/tool-analysis.json" >&2 + exit 1 +fi + +jq -n '[{ + payload: { + agentTurn: { + parts: [{text: "`legacycron`"}] + } + } +}]' > "${STATE_DIR}/cron/jobs.json" + +HOME="${HOME_DIR}" PATH="${BIN_DIR}:${PATH}" bash "${ANALYZER}" \ + --state-dir "${STATE_DIR}" \ + --output "${OUTPUT_DIR}" >/dev/null + +if ! jq -e ' + .unknown_binaries == ["legacycron"] and + .analysis_sources.cron_payload_commands == 1 +' "${OUTPUT_DIR}/tool-analysis.json" >/dev/null; then + echo "FAIL: legacy-format cron dependency was not analyzed" >&2 + cat "${OUTPUT_DIR}/tool-analysis.json" >&2 + exit 1 +fi + +jq -n '{version: 1, jobs: []}' > "${STATE_DIR}/cron/jobs.json" + +HOME="${HOME_DIR}" PATH="${BIN_DIR}:${PATH}" bash "${ANALYZER}" \ + --state-dir "${STATE_DIR}" \ + --output "${OUTPUT_DIR}" >/dev/null + +if ! jq -e ' + .apt_packages == [] and + .pip_packages == [] and + .npm_packages == [] and + .unknown_binaries == [] and + .analysis_sources.cron_payload_commands == 0 +' "${OUTPUT_DIR}/tool-analysis.json" >/dev/null; then + echo "FAIL: empty dependency categories were not emitted as empty arrays" >&2 + cat "${OUTPUT_DIR}/tool-analysis.json" >&2 + exit 1 +fi + +echo "PASS: cron formats and empty dependency arrays are analyzed correctly"