|
|
@@ -1,6 +1,6 @@
|
|
|
#!/usr/bin/env bash
|
|
|
#
|
|
|
-# Package the Superpowers Codex plugin as a rootless .tar.gz for portal upload.
|
|
|
+# Package the Superpowers Codex plugin as a rootless archive for portal upload.
|
|
|
#
|
|
|
# The Codex portal artifact differs from the old openai/plugins sync flow:
|
|
|
# it is a standalone archive, but it still needs the OpenAI-owned
|
|
|
@@ -14,6 +14,7 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
|
|
|
REF="HEAD"
|
|
|
OUTPUT=""
|
|
|
+FORMAT=""
|
|
|
METADATA_SOURCE=""
|
|
|
ALLOW_DIRTY=0
|
|
|
KEEP_STAGE=0
|
|
|
@@ -25,18 +26,21 @@ Usage:
|
|
|
|
|
|
Options:
|
|
|
--output PATH Write archive to PATH.
|
|
|
- Default: ../_tmp/sup-codex-packaging/superpowers-VERSION.tar.gz
|
|
|
- --metadata-source PATH Prior official package directory or .tar.gz used to
|
|
|
+ Default: ../_tmp/sup-codex-packaging/superpowers-VERSION.zip
|
|
|
+ --format FORMAT Archive format: zip or tar.gz. Default: zip.
|
|
|
+ If --output ends in .zip, .tar.gz, or .tgz, that
|
|
|
+ extension is used when --format is omitted.
|
|
|
+ --metadata-source PATH Prior official package directory, .zip, or .tar.gz used to
|
|
|
seed skills/*/agents/openai.yaml.
|
|
|
Default: ../_tmp/sup-codex-packaging/superpowers,
|
|
|
- falling back to ../_tmp/sup-codex-packaging/superpowers.tar.gz
|
|
|
+ falling back to superpowers.zip, then superpowers.tar.gz
|
|
|
--ref REF Git ref to package. Default: HEAD.
|
|
|
--allow-dirty Permit a dirty working tree. The archive still uses --ref.
|
|
|
--keep-stage Print and keep the temporary staging directory.
|
|
|
-h, --help Show this help.
|
|
|
|
|
|
The archive is rootless: .codex-plugin/, assets/, skills/, README.md, LICENSE,
|
|
|
-and CODE_OF_CONDUCT.md sit at the tar root. Source-only repo files, hooks, tests,
|
|
|
+and CODE_OF_CONDUCT.md sit at the archive root. Source-only repo files, hooks, tests,
|
|
|
docs, and other harness manifests are intentionally not shipped.
|
|
|
EOF
|
|
|
}
|
|
|
@@ -53,6 +57,21 @@ while [[ $# -gt 0 ]]; do
|
|
|
OUTPUT="$2"
|
|
|
shift 2
|
|
|
;;
|
|
|
+ --format)
|
|
|
+ [[ $# -ge 2 ]] || die "--format requires a value"
|
|
|
+ case "$2" in
|
|
|
+ zip)
|
|
|
+ FORMAT="zip"
|
|
|
+ ;;
|
|
|
+ tar.gz|tgz)
|
|
|
+ FORMAT="tar.gz"
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ die "--format must be zip or tar.gz"
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+ shift 2
|
|
|
+ ;;
|
|
|
--metadata-source)
|
|
|
[[ $# -ge 2 ]] || die "--metadata-source requires a path"
|
|
|
METADATA_SOURCE="$2"
|
|
|
@@ -83,11 +102,43 @@ while [[ $# -gt 0 ]]; do
|
|
|
esac
|
|
|
done
|
|
|
|
|
|
+infer_format_from_output() {
|
|
|
+ local output_path="$1"
|
|
|
+
|
|
|
+ case "$output_path" in
|
|
|
+ *.tar.gz|*.tgz)
|
|
|
+ printf '%s\n' "tar.gz"
|
|
|
+ ;;
|
|
|
+ *.zip)
|
|
|
+ printf '%s\n' "zip"
|
|
|
+ ;;
|
|
|
+ *)
|
|
|
+ return 1
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
+}
|
|
|
+
|
|
|
+if [[ -z "$FORMAT" ]]; then
|
|
|
+ FORMAT="$(infer_format_from_output "$OUTPUT" || true)"
|
|
|
+ if [[ -z "$FORMAT" ]]; then
|
|
|
+ FORMAT="zip"
|
|
|
+ fi
|
|
|
+else
|
|
|
+ output_format="$(infer_format_from_output "$OUTPUT" || true)"
|
|
|
+ if [[ -n "$output_format" && "$output_format" != "$FORMAT" ]]; then
|
|
|
+ die "--output extension does not match --format $FORMAT: $OUTPUT"
|
|
|
+ fi
|
|
|
+fi
|
|
|
+
|
|
|
command -v git >/dev/null || die "git not found in PATH"
|
|
|
command -v jq >/dev/null || die "jq not found in PATH"
|
|
|
command -v tar >/dev/null || die "tar not found in PATH"
|
|
|
command -v gzip >/dev/null || die "gzip not found in PATH"
|
|
|
command -v shasum >/dev/null || die "shasum not found in PATH"
|
|
|
+if [[ "$FORMAT" == "zip" ]]; then
|
|
|
+ command -v zip >/dev/null || die "zip not found in PATH"
|
|
|
+ command -v unzip >/dev/null || die "unzip not found in PATH"
|
|
|
+fi
|
|
|
|
|
|
[[ -d "$REPO_ROOT/.git" ]] || die "repo root is not a git checkout: $REPO_ROOT"
|
|
|
git -C "$REPO_ROOT" rev-parse --verify "$REF^{commit}" >/dev/null ||
|
|
|
@@ -105,17 +156,19 @@ fi
|
|
|
if [[ -z "$METADATA_SOURCE" ]]; then
|
|
|
if [[ -d "$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers" ]]; then
|
|
|
METADATA_SOURCE="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers"
|
|
|
+ elif [[ -f "$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.zip" ]]; then
|
|
|
+ METADATA_SOURCE="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.zip"
|
|
|
elif [[ -f "$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.tar.gz" ]]; then
|
|
|
METADATA_SOURCE="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers.tar.gz"
|
|
|
else
|
|
|
- die "no metadata source found; pass --metadata-source <prior package dir or tar.gz>"
|
|
|
+ die "no metadata source found; pass --metadata-source <prior package dir, zip, or tar.gz>"
|
|
|
fi
|
|
|
fi
|
|
|
|
|
|
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/superpowers-codex-package.XXXXXX")"
|
|
|
STAGE="$WORK_DIR/payload"
|
|
|
METADATA_WORK="$WORK_DIR/metadata"
|
|
|
-TAR_LIST="$WORK_DIR/tar-list"
|
|
|
+ARCHIVE_LIST="$WORK_DIR/archive-list"
|
|
|
|
|
|
cleanup() {
|
|
|
if [[ "$KEEP_STAGE" -eq 1 ]]; then
|
|
|
@@ -158,8 +211,13 @@ prepare_metadata_root() {
|
|
|
tar -xzf "$source" -C "$METADATA_WORK"
|
|
|
root="$METADATA_WORK"
|
|
|
;;
|
|
|
+ *.zip)
|
|
|
+ command -v unzip >/dev/null || die "unzip not found in PATH"
|
|
|
+ unzip -q "$source" -d "$METADATA_WORK"
|
|
|
+ root="$METADATA_WORK"
|
|
|
+ ;;
|
|
|
*)
|
|
|
- die "metadata source must be a directory or .tar.gz: $source"
|
|
|
+ die "metadata source must be a directory, .zip, or .tar.gz: $source"
|
|
|
;;
|
|
|
esac
|
|
|
else
|
|
|
@@ -189,7 +247,14 @@ if jq -e 'has("hooks")' "$STAGE/.codex-plugin/plugin.json" >/dev/null; then
|
|
|
fi
|
|
|
|
|
|
if [[ -z "$OUTPUT" ]]; then
|
|
|
- OUTPUT="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers-$VERSION.tar.gz"
|
|
|
+ case "$FORMAT" in
|
|
|
+ zip)
|
|
|
+ OUTPUT="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers-$VERSION.zip"
|
|
|
+ ;;
|
|
|
+ tar.gz)
|
|
|
+ OUTPUT="$REPO_ROOT/../_tmp/sup-codex-packaging/superpowers-$VERSION.tar.gz"
|
|
|
+ ;;
|
|
|
+ esac
|
|
|
fi
|
|
|
mkdir -p "$(dirname "$OUTPUT")"
|
|
|
OUTPUT="$(cd "$(dirname "$OUTPUT")" && pwd)/$(basename "$OUTPUT")"
|
|
|
@@ -218,27 +283,51 @@ metadata_count="$(find "$STAGE/skills" -path '*/agents/openai.yaml' -type f | wc
|
|
|
[[ "$skill_count" == "$metadata_count" ]] ||
|
|
|
die "metadata count mismatch: $metadata_count metadata files for $skill_count skills"
|
|
|
|
|
|
-# Match the prior official archive's deterministic tar entry metadata.
|
|
|
-TZ=UTC find "$STAGE" -exec touch -t 197001010000 {} +
|
|
|
-
|
|
|
(
|
|
|
cd "$STAGE"
|
|
|
{
|
|
|
find . -mindepth 1 -type d | sed 's#^\./##' | LC_ALL=C sort
|
|
|
find . -mindepth 1 -type f | sed 's#^\./##' | LC_ALL=C sort
|
|
|
- } >"$TAR_LIST"
|
|
|
-
|
|
|
- rm -f "$OUTPUT"
|
|
|
- COPYFILE_DISABLE=1 tar -cf - --no-recursion --format ustar --uid 0 --gid 0 --uname '' --gname '' -T "$TAR_LIST" |
|
|
|
- gzip -9n >"$OUTPUT"
|
|
|
+ } >"$ARCHIVE_LIST"
|
|
|
)
|
|
|
|
|
|
+case "$FORMAT" in
|
|
|
+ zip)
|
|
|
+ # ZIP cannot represent dates earlier than 1980.
|
|
|
+ TZ=UTC find "$STAGE" -exec touch -t 198001010000 {} +
|
|
|
+ (
|
|
|
+ cd "$STAGE"
|
|
|
+ rm -f "$OUTPUT"
|
|
|
+ COPYFILE_DISABLE=1 zip -X -q - -@ <"$ARCHIVE_LIST" >"$OUTPUT"
|
|
|
+ )
|
|
|
+ ;;
|
|
|
+ tar.gz)
|
|
|
+ # Match the prior official archive's deterministic tar entry metadata.
|
|
|
+ TZ=UTC find "$STAGE" -exec touch -t 197001010000 {} +
|
|
|
+ (
|
|
|
+ cd "$STAGE"
|
|
|
+ rm -f "$OUTPUT"
|
|
|
+ COPYFILE_DISABLE=1 tar -cf - --no-recursion --format ustar --uid 0 --gid 0 --uname '' --gname '' -T "$ARCHIVE_LIST" |
|
|
|
+ gzip -9n >"$OUTPUT"
|
|
|
+ )
|
|
|
+ ;;
|
|
|
+esac
|
|
|
+
|
|
|
if command -v xattr >/dev/null 2>&1; then
|
|
|
xattr -c "$OUTPUT" 2>/dev/null || true
|
|
|
fi
|
|
|
|
|
|
+case "$FORMAT" in
|
|
|
+ zip)
|
|
|
+ archive_paths="$(unzip -Z1 "$OUTPUT" | sed 's#/$##')"
|
|
|
+ ;;
|
|
|
+ tar.gz)
|
|
|
+ archive_paths="$(tar -tzf "$OUTPUT")"
|
|
|
+ ;;
|
|
|
+esac
|
|
|
+
|
|
|
unexpected_paths="$(
|
|
|
- tar -tzf "$OUTPUT" |
|
|
|
+ printf '%s\n' "$archive_paths" |
|
|
|
grep -E '(^superpowers/|^\.agents/|^hooks/|package\.json$|^\.git|^\.pytest_cache|^\.ruff_cache|^scripts/|^tests/|^docs/|^evals/|^lib/|^\.claude|^\.cursor|^\.kimi|^\.opencode|^\.pi|^AGENTS\.md$|^CLAUDE\.md$|^GEMINI\.md$|^RELEASE-NOTES\.md$|^CHANGELOG\.md$)' || true
|
|
|
)"
|
|
|
if [[ -n "$unexpected_paths" ]]; then
|
|
|
@@ -246,10 +335,11 @@ if [[ -n "$unexpected_paths" ]]; then
|
|
|
die "archive contains source-only paths"
|
|
|
fi
|
|
|
|
|
|
-entry_count="$(tar -tzf "$OUTPUT" | wc -l | tr -d ' ')"
|
|
|
+entry_count="$(printf '%s\n' "$archive_paths" | wc -l | tr -d ' ')"
|
|
|
checksum="$(shasum -a 256 "$OUTPUT" | awk '{print $1}')"
|
|
|
|
|
|
echo "Archive: $OUTPUT"
|
|
|
+echo "Format: $FORMAT"
|
|
|
echo "Version: $VERSION"
|
|
|
echo "Entries: $entry_count"
|
|
|
echo "Skills: $skill_count"
|