diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99a3cd084..c05c1b13b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -724,6 +724,9 @@ importers: '@rivet-dev/agent-os-jq': specifier: workspace:* version: link:../jq + '@rivet-dev/agent-os-pngcrush': + specifier: workspace:* + version: link:../pngcrush '@rivet-dev/agent-os-ripgrep': specifier: workspace:* version: link:../ripgrep @@ -768,6 +771,18 @@ importers: specifier: ^5.9.2 version: 5.9.3 + registry/software/ffmpeg: + devDependencies: + '@rivet-dev/agent-os-registry-types': + specifier: link:../../../packages/registry-types + version: link:../../../packages/registry-types + '@types/node': + specifier: ^22.10.2 + version: 22.19.15 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + registry/software/file: devDependencies: '@rivet-dev/agent-os-registry-types': @@ -816,6 +831,18 @@ importers: specifier: ^5.9.2 version: 5.9.3 + registry/software/gog: + devDependencies: + '@rivet-dev/agent-os-registry-types': + specifier: link:../../../packages/registry-types + version: link:../../../packages/registry-types + '@types/node': + specifier: ^22.10.2 + version: 22.19.15 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + registry/software/grep: devDependencies: '@rivet-dev/agent-os-registry-types': @@ -876,6 +903,18 @@ importers: specifier: ^5.9.2 version: 5.9.3 + registry/software/pngcrush: + devDependencies: + '@rivet-dev/agent-os-registry-types': + specifier: link:../../../packages/registry-types + version: link:../../../packages/registry-types + '@types/node': + specifier: ^22.10.2 + version: 22.19.15 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + registry/software/ripgrep: devDependencies: '@rivet-dev/agent-os-registry-types': @@ -900,6 +939,18 @@ importers: specifier: ^5.9.2 version: 5.9.3 + registry/software/slack-cli: + devDependencies: + '@rivet-dev/agent-os-registry-types': + specifier: link:../../../packages/registry-types + version: link:../../../packages/registry-types + '@types/node': + specifier: ^22.10.2 + version: 22.19.15 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + registry/software/sqlite3: devDependencies: '@rivet-dev/agent-os-registry-types': diff --git a/registry/Makefile b/registry/Makefile index 2f90ee938..555fc174d 100644 --- a/registry/Makefile +++ b/registry/Makefile @@ -14,7 +14,7 @@ TS_MARKER := $(MARKER_DIR)/ts-built # Command packages (excludes _types and meta-packages) CMD_PACKAGES := coreutils sed grep gawk findutils diffutils tar gzip \ - curl http-get wget zip unzip jq ripgrep fd tree file sqlite3 duckdb yq codex git + curl http-get wget zip unzip jq ripgrep fd tree file sqlite3 duckdb yq codex git pngcrush # Meta-packages META_PACKAGES := common build-essential everything @@ -186,6 +186,10 @@ $(COPY_MARKER): $(RUST_MARKER) $(C_MARKER) @mkdir -p software/git/wasm @[ -f "$(COMMANDS_DIR)/git" ] && cp -f "$(COMMANDS_DIR)/git" software/git/wasm/ || echo " WARN: git not found" + @# --- pngcrush (C build) --- + @mkdir -p software/pngcrush/wasm + @[ -f "$(COMMANDS_DIR)/pngcrush" ] && cp -f "$(COMMANDS_DIR)/pngcrush" software/pngcrush/wasm/ || echo " WARN: pngcrush not found" + @echo "=== Copy complete ===" @touch $(COPY_MARKER) diff --git a/registry/native/c/Makefile b/registry/native/c/Makefile index ad30f84b1..3fdc2ed76 100644 --- a/registry/native/c/Makefile +++ b/registry/native/c/Makefile @@ -63,7 +63,7 @@ NATIVE_CFLAGS := -O0 -g -I include/ COMMANDS_DIR ?= ../target/wasm32-wasip1/release/commands # Real commands to install (add command names as they are opted in) -COMMANDS := zip unzip envsubst sqlite3 curl wget duckdb +COMMANDS := zip unzip envsubst sqlite3 curl wget duckdb pngcrush # Programs requiring patched sysroot (Tier 2+ custom host imports) PATCHED_PROGRAMS := isatty_test getpid_test getppid_test getppid_verify userinfo pipe_test dup_test spawn_child spawn_exit_code pipeline kill_child waitpid_return waitpid_edge syscall_coverage getpwuid_test signal_tests sigaction_behavior delayed_tcp_echo delayed_kill pipe_edge tcp_echo tcp_server udp_echo unix_socket signal_handler http_get dns_lookup sqlite3_cli curl wget @@ -88,10 +88,10 @@ endif WASM_PROG_NAMES := $(sort $(basename $(notdir $(SOURCES))) $(CUSTOM_WASM_PROG_NAMES)) NATIVE_PROG_NAMES := $(basename $(notdir $(SOURCES))) -PROGRAM_REPORT_NAMES := $(WASM_PROG_NAMES) duckdb +PROGRAM_REPORT_NAMES := $(WASM_PROG_NAMES) duckdb pngcrush # Default WASM output targets -EXTRA_WASM_OUTPUTS := $(BUILD_DIR)/duckdb +EXTRA_WASM_OUTPUTS := $(BUILD_DIR)/duckdb $(BUILD_DIR)/pngcrush WASM_OUTPUTS := $(addprefix $(BUILD_DIR)/,$(WASM_PROG_NAMES)) $(EXTRA_WASM_OUTPUTS) # Native output targets NATIVE_OUTPUTS := $(addprefix $(NATIVE_DIR)/,$(NATIVE_PROG_NAMES)) @@ -144,6 +144,9 @@ MINIZIP_URL := https://github.com/madler/zlib/archive/refs/tags/v1.3.1.zip DUCKDB_VERSION := v1.5.0 DUCKDB_GIT_DESCRIBE := $(DUCKDB_VERSION)-0-g0123456789 DUCKDB_URL := https://github.com/duckdb/duckdb/archive/refs/tags/$(DUCKDB_VERSION).tar.gz +# pngcrush — PNG optimizer with bundled libpng and zlib +PNGCRUSH_TAG := pngcrush-20171101 +PNGCRUSH_URL := https://github.com/pnggroup/pngcrush/archive/refs/tags/$(PNGCRUSH_TAG).tar.gz LIBS_DIR := libs LIBS_CACHE := .cache/libs @@ -198,8 +201,17 @@ libs/duckdb/CMakeLists.txt: @mkdir -p $(LIBS_DIR)/duckdb @tar -xzf "$(LIBS_CACHE)/duckdb.tar.gz" --strip-components=1 -C $(LIBS_DIR)/duckdb +libs/pngcrush/pngcrush.c: + @echo "Fetching pngcrush $(PNGCRUSH_TAG)..." + @mkdir -p $(LIBS_CACHE) + @curl -fSL "$(PNGCRUSH_URL)" -o "$(LIBS_CACHE)/pngcrush.tar.gz" + @rm -rf $(LIBS_DIR)/pngcrush + @mkdir -p $(LIBS_DIR)/pngcrush + @tar -xzf "$(LIBS_CACHE)/pngcrush.tar.gz" --strip-components=1 -C $(LIBS_DIR)/pngcrush + @cp pngcrush-overlay/pnglibconf.h pngcrush-overlay/cexcept.h $(LIBS_DIR)/pngcrush/ + .PHONY: fetch-libs clean-libs -fetch-libs: libs/sqlite3/sqlite3.c libs/zlib/zutil.c libs/minizip/ioapi.c libs/cjson/cJSON.c libs/curl/lib/easy.c libs/duckdb/CMakeLists.txt +fetch-libs: libs/sqlite3/sqlite3.c libs/zlib/zutil.c libs/minizip/ioapi.c libs/cjson/cJSON.c libs/curl/lib/easy.c libs/duckdb/CMakeLists.txt libs/pngcrush/pngcrush.c clean-libs: rm -rf $(LIBS_DIR) $(LIBS_CACHE) @@ -513,6 +525,26 @@ $(NATIVE_DIR)/unzip: programs/unzip.c $(ZLIB_SRCS) $(MINIZIP_UNZIP_SRCS) @mkdir -p $(NATIVE_DIR) $(NATIVE_CC) $(NATIVE_CFLAGS) $(ZIP_INCLUDES) -o $@ programs/unzip.c $(ZLIB_SRCS) $(MINIZIP_UNZIP_SRCS) -lz +# pngcrush: PNG optimizer — compiles pngcrush.c with bundled libpng (unified) and zlib +PNGCRUSH_ZLIB_SRCS := $(addprefix libs/pngcrush/,adler32.c compress.c crc32.c deflate.c \ + infback.c inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c) +PNGCRUSH_CFLAGS := -DLIBPNG_UNIFIED -DNO_GZ -isystem pngcrush-overlay -Ilibs/pngcrush \ + -D_WASI_EMULATED_PROCESS_CLOCKS + +$(BUILD_DIR)/pngcrush: libs/pngcrush/pngcrush.c $(WASI_SDK_DIR)/bin/clang + @mkdir -p $(BUILD_DIR) + $(CC) --target=wasm32-wasip1 --sysroot=$(WASI_SDK_DIR)/share/wasi-sysroot -O2 -I include/ \ + $(PNGCRUSH_CFLAGS) -o $@.wasm \ + libs/pngcrush/pngcrush.c $(PNGCRUSH_ZLIB_SRCS) -lm \ + -lwasi-emulated-process-clocks + wasm-opt -O3 --strip-debug $@.wasm -o $@ + @rm -f $@.wasm + +$(NATIVE_DIR)/pngcrush: libs/pngcrush/pngcrush.c + @mkdir -p $(NATIVE_DIR) + $(NATIVE_CC) $(NATIVE_CFLAGS) $(PNGCRUSH_CFLAGS) -o $@ \ + libs/pngcrush/pngcrush.c $(PNGCRUSH_ZLIB_SRCS) -lm -lz + # curl_test: links libcurl (HTTP/HTTPS build for WASM via host_net + host_tls) CURL_SRCS := $(wildcard libs/curl/lib/*.c) $(wildcard libs/curl/lib/vauth/*.c) \ $(wildcard libs/curl/lib/vtls/*.c) $(wildcard libs/curl/lib/vquic/*.c) \ diff --git a/registry/native/c/pngcrush-overlay/cexcept.h b/registry/native/c/pngcrush-overlay/cexcept.h new file mode 100644 index 000000000..6d645cdc5 --- /dev/null +++ b/registry/native/c/pngcrush-overlay/cexcept.h @@ -0,0 +1,59 @@ +/* cexcept.h — WASI-compatible replacement for cexcept exception handling. + * + * The original cexcept.h uses setjmp/longjmp which requires the WASM + * Exception Handling proposal. This simplified version uses a global + * flag and goto-style flow control that works on all WASM runtimes. + * + * Limitations: nested Try/Catch blocks are not reentrant (fine for + * pngcrush which uses them sequentially). Throw from deeply nested + * calls will abort the process via exit(1). + */ + +#ifndef CEXCEPT_H +#define CEXCEPT_H + +#include +#include + +#define define_exception_type(etype) \ +struct exception_context { \ + int caught; \ + int has_throw; \ + volatile struct { etype etmp; } v; \ +} + +#define init_exception_context(ec) ((void)((ec)->caught = 0, (ec)->has_throw = 0)) + +/* Try/Catch: uses a simple flag-based mechanism instead of setjmp/longjmp. + * Throw sets the flag and the Catch block checks it. + * NOTE: Throw from a subroutine (not directly in the Try block) will + * call exit(1) since we can't longjmp back. pngcrush's error handler + * (pngcrush_cexcept_error) calls Throw, which is invoked from libpng + * callbacks, so we handle that with exit(). */ +#define Try \ + { \ + the_exception_context->caught = 0; \ + the_exception_context->has_throw = 0; \ + { \ + do + +#define exception__catch(action) \ + while (the_exception_context->caught = 0, \ + the_exception_context->caught); \ + } \ + if (the_exception_context->has_throw) { \ + the_exception_context->caught = 1; \ + } \ + } \ + if (!the_exception_context->caught || action) { } \ + else + +#define Catch(e) exception__catch(((e) = the_exception_context->v.etmp, 0)) +#define Catch_anonymous exception__catch(0) + +#define Throw \ + for (;; exit(1)) \ + for (the_exception_context->has_throw = 1 ;; ) \ + the_exception_context->v.etmp = + +#endif /* CEXCEPT_H */ diff --git a/registry/native/c/pngcrush-overlay/pnglibconf.h b/registry/native/c/pngcrush-overlay/pnglibconf.h new file mode 100644 index 000000000..f7d2af5ae --- /dev/null +++ b/registry/native/c/pngcrush-overlay/pnglibconf.h @@ -0,0 +1,221 @@ +/* libpng 1.6.32 STANDARD API DEFINITION */ + +/* pnglibconf.h - library build configuration */ + +/* Libpng version 1.6.32 - August 24, 2017 */ + +/* Copyright (c) 1998-2017 Glenn Randers-Pehrson */ + +/* This code is released under the libpng license. */ +/* For conditions of distribution and use, see the disclaimer */ +/* and license in png.h */ + +/* pnglibconf.h */ +/* Machine generated file: DO NOT EDIT */ +/* Derived from: scripts/pnglibconf.dfa */ +#ifndef PNGLCONF_H +#define PNGLCONF_H +/* options */ +#define PNG_16BIT_SUPPORTED +#define PNG_ALIGNED_MEMORY_SUPPORTED +/*#undef PNG_ARM_NEON_API_SUPPORTED*/ +/*#undef PNG_ARM_NEON_CHECK_SUPPORTED*/ +/*#undef PNG_POWERPC_VSX_API_SUPPORTED*/ +/*#undef PNG_POWERPC_VSX_CHECK_SUPPORTED*/ +#define PNG_BENIGN_ERRORS_SUPPORTED +#define PNG_BENIGN_READ_ERRORS_SUPPORTED +/*#undef PNG_BENIGN_WRITE_ERRORS_SUPPORTED*/ +#define PNG_BUILD_GRAYSCALE_PALETTE_SUPPORTED +#define PNG_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_COLORSPACE_SUPPORTED +#define PNG_CONSOLE_IO_SUPPORTED +#define PNG_CONVERT_tIME_SUPPORTED +#define PNG_EASY_ACCESS_SUPPORTED +/*#undef PNG_ERROR_NUMBERS_SUPPORTED*/ +#define PNG_ERROR_TEXT_SUPPORTED +#define PNG_FIXED_POINT_SUPPORTED +#define PNG_FLOATING_ARITHMETIC_SUPPORTED +#define PNG_FLOATING_POINT_SUPPORTED +#define PNG_FORMAT_AFIRST_SUPPORTED +#define PNG_FORMAT_BGR_SUPPORTED +#define PNG_GAMMA_SUPPORTED +#define PNG_GET_PALETTE_MAX_SUPPORTED +#define PNG_HANDLE_AS_UNKNOWN_SUPPORTED +#define PNG_INCH_CONVERSIONS_SUPPORTED +#define PNG_INFO_IMAGE_SUPPORTED +#define PNG_IO_STATE_SUPPORTED +#define PNG_MNG_FEATURES_SUPPORTED +#define PNG_POINTER_INDEXING_SUPPORTED +#define PNG_PROGRESSIVE_READ_SUPPORTED +#define PNG_READ_16BIT_SUPPORTED +#define PNG_READ_ALPHA_MODE_SUPPORTED +#define PNG_READ_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_READ_BACKGROUND_SUPPORTED +#define PNG_READ_BGR_SUPPORTED +#define PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_READ_COMPOSITE_NODIV_SUPPORTED +#define PNG_READ_COMPRESSED_TEXT_SUPPORTED +#define PNG_READ_EXPAND_16_SUPPORTED +#define PNG_READ_EXPAND_SUPPORTED +#define PNG_READ_FILLER_SUPPORTED +#define PNG_READ_GAMMA_SUPPORTED +#define PNG_READ_GET_PALETTE_MAX_SUPPORTED +#define PNG_READ_GRAY_TO_RGB_SUPPORTED +#define PNG_READ_INTERLACING_SUPPORTED +#define PNG_READ_INT_FUNCTIONS_SUPPORTED +#define PNG_READ_INVERT_ALPHA_SUPPORTED +#define PNG_READ_INVERT_SUPPORTED +#define PNG_READ_OPT_PLTE_SUPPORTED +#define PNG_READ_PACKSWAP_SUPPORTED +#define PNG_READ_PACK_SUPPORTED +#define PNG_READ_QUANTIZE_SUPPORTED +#define PNG_READ_RGB_TO_GRAY_SUPPORTED +#define PNG_READ_SCALE_16_TO_8_SUPPORTED +#define PNG_READ_SHIFT_SUPPORTED +#define PNG_READ_STRIP_16_TO_8_SUPPORTED +#define PNG_READ_STRIP_ALPHA_SUPPORTED +#define PNG_READ_SUPPORTED +#define PNG_READ_SWAP_ALPHA_SUPPORTED +#define PNG_READ_SWAP_SUPPORTED +#define PNG_READ_TEXT_SUPPORTED +#define PNG_READ_TRANSFORMS_SUPPORTED +#define PNG_READ_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_READ_USER_CHUNKS_SUPPORTED +#define PNG_READ_USER_TRANSFORM_SUPPORTED +#define PNG_READ_bKGD_SUPPORTED +#define PNG_READ_cHRM_SUPPORTED +#define PNG_READ_eXIf_SUPPORTED +#define PNG_READ_gAMA_SUPPORTED +#define PNG_READ_hIST_SUPPORTED +#define PNG_READ_iCCP_SUPPORTED +#define PNG_READ_iTXt_SUPPORTED +#define PNG_READ_oFFs_SUPPORTED +#define PNG_READ_pCAL_SUPPORTED +#define PNG_READ_pHYs_SUPPORTED +#define PNG_READ_sBIT_SUPPORTED +#define PNG_READ_sCAL_SUPPORTED +#define PNG_READ_sPLT_SUPPORTED +#define PNG_READ_sRGB_SUPPORTED +#define PNG_READ_tEXt_SUPPORTED +#define PNG_READ_tIME_SUPPORTED +#define PNG_READ_tRNS_SUPPORTED +#define PNG_READ_zTXt_SUPPORTED +#define PNG_SAVE_INT_32_SUPPORTED +#define PNG_SAVE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SEQUENTIAL_READ_SUPPORTED +/* #define PNG_SETJMP_SUPPORTED -- disabled for WASI (no EH proposal) */ +#define PNG_SET_OPTION_SUPPORTED +#define PNG_SET_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_SET_USER_LIMITS_SUPPORTED +/* Simplified API disabled for WASI (requires setjmp/longjmp) */ +/* #define PNG_SIMPLIFIED_READ_AFIRST_SUPPORTED */ +/* #define PNG_SIMPLIFIED_READ_BGR_SUPPORTED */ +/* #define PNG_SIMPLIFIED_READ_SUPPORTED */ +/* #define PNG_SIMPLIFIED_WRITE_AFIRST_SUPPORTED */ +/* #define PNG_SIMPLIFIED_WRITE_BGR_SUPPORTED */ +/* #define PNG_SIMPLIFIED_WRITE_STDIO_SUPPORTED */ +/* #define PNG_SIMPLIFIED_WRITE_SUPPORTED */ +#define PNG_STDIO_SUPPORTED +#define PNG_STORE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_TEXT_SUPPORTED +#define PNG_TIME_RFC1123_SUPPORTED +#define PNG_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_USER_CHUNKS_SUPPORTED +#define PNG_USER_LIMITS_SUPPORTED +#define PNG_USER_MEM_SUPPORTED +#define PNG_USER_TRANSFORM_INFO_SUPPORTED +#define PNG_USER_TRANSFORM_PTR_SUPPORTED +#define PNG_WARNINGS_SUPPORTED +#define PNG_WRITE_16BIT_SUPPORTED +#define PNG_WRITE_ANCILLARY_CHUNKS_SUPPORTED +#define PNG_WRITE_BGR_SUPPORTED +#define PNG_WRITE_CHECK_FOR_INVALID_INDEX_SUPPORTED +#define PNG_WRITE_COMPRESSED_TEXT_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_COMPRESSION_SUPPORTED +#define PNG_WRITE_CUSTOMIZE_ZTXT_COMPRESSION_SUPPORTED +#define PNG_WRITE_FILLER_SUPPORTED +#define PNG_WRITE_FILTER_SUPPORTED +#define PNG_WRITE_FLUSH_SUPPORTED +#define PNG_WRITE_GET_PALETTE_MAX_SUPPORTED +#define PNG_WRITE_INTERLACING_SUPPORTED +#define PNG_WRITE_INT_FUNCTIONS_SUPPORTED +#define PNG_WRITE_INVERT_ALPHA_SUPPORTED +#define PNG_WRITE_INVERT_SUPPORTED +#define PNG_WRITE_OPTIMIZE_CMF_SUPPORTED +#define PNG_WRITE_PACKSWAP_SUPPORTED +#define PNG_WRITE_PACK_SUPPORTED +#define PNG_WRITE_SHIFT_SUPPORTED +#define PNG_WRITE_SUPPORTED +#define PNG_WRITE_SWAP_ALPHA_SUPPORTED +#define PNG_WRITE_SWAP_SUPPORTED +#define PNG_WRITE_TEXT_SUPPORTED +#define PNG_WRITE_TRANSFORMS_SUPPORTED +#define PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED +#define PNG_WRITE_USER_TRANSFORM_SUPPORTED +#define PNG_WRITE_WEIGHTED_FILTER_SUPPORTED +#define PNG_WRITE_bKGD_SUPPORTED +#define PNG_WRITE_cHRM_SUPPORTED +#define PNG_WRITE_eXIf_SUPPORTED +#define PNG_WRITE_gAMA_SUPPORTED +#define PNG_WRITE_hIST_SUPPORTED +#define PNG_WRITE_iCCP_SUPPORTED +#define PNG_WRITE_iTXt_SUPPORTED +#define PNG_WRITE_oFFs_SUPPORTED +#define PNG_WRITE_pCAL_SUPPORTED +#define PNG_WRITE_pHYs_SUPPORTED +#define PNG_WRITE_sBIT_SUPPORTED +#define PNG_WRITE_sCAL_SUPPORTED +#define PNG_WRITE_sPLT_SUPPORTED +#define PNG_WRITE_sRGB_SUPPORTED +#define PNG_WRITE_tEXt_SUPPORTED +#define PNG_WRITE_tIME_SUPPORTED +#define PNG_WRITE_tRNS_SUPPORTED +#define PNG_WRITE_zTXt_SUPPORTED +#define PNG_bKGD_SUPPORTED +#define PNG_cHRM_SUPPORTED +#define PNG_eXIf_SUPPORTED +#define PNG_gAMA_SUPPORTED +#define PNG_hIST_SUPPORTED +#define PNG_iCCP_SUPPORTED +#define PNG_iTXt_SUPPORTED +#define PNG_oFFs_SUPPORTED +#define PNG_pCAL_SUPPORTED +#define PNG_pHYs_SUPPORTED +#define PNG_sBIT_SUPPORTED +#define PNG_sCAL_SUPPORTED +#define PNG_sPLT_SUPPORTED +#define PNG_sRGB_SUPPORTED +#define PNG_tEXt_SUPPORTED +#define PNG_tIME_SUPPORTED +#define PNG_tRNS_SUPPORTED +#define PNG_zTXt_SUPPORTED +/* end of options */ +/* settings */ +#define PNG_API_RULE 0 +#define PNG_DEFAULT_READ_MACROS 1 +#define PNG_GAMMA_THRESHOLD_FIXED 5000 +#define PNG_IDAT_READ_SIZE PNG_ZBUF_SIZE +#define PNG_INFLATE_BUF_SIZE 1024 +#define PNG_LINKAGE_API extern +#define PNG_LINKAGE_CALLBACK extern +#define PNG_LINKAGE_DATA extern +#define PNG_LINKAGE_FUNCTION extern +#define PNG_MAX_GAMMA_8 11 +#define PNG_QUANTIZE_BLUE_BITS 5 +#define PNG_QUANTIZE_GREEN_BITS 5 +#define PNG_QUANTIZE_RED_BITS 5 +#define PNG_TEXT_Z_DEFAULT_COMPRESSION (-1) +#define PNG_TEXT_Z_DEFAULT_STRATEGY 0 +#define PNG_USER_CHUNK_CACHE_MAX 1000 +#define PNG_USER_CHUNK_MALLOC_MAX 8000000 +#define PNG_USER_HEIGHT_MAX 1000000 +#define PNG_USER_WIDTH_MAX 1000000 +#define PNG_ZBUF_SIZE 8192 +#define PNG_ZLIB_VERNUM 0 /* unknown */ +#define PNG_Z_DEFAULT_COMPRESSION (-1) +#define PNG_Z_DEFAULT_NOFILTER_STRATEGY 0 +#define PNG_Z_DEFAULT_STRATEGY 1 +#define PNG_sCAL_PRECISION 5 +#define PNG_sRGB_PROFILE_CHECKS 2 +/* end of settings */ +#endif /* PNGLCONF_H */ diff --git a/registry/native/c/pngcrush-overlay/setjmp.h b/registry/native/c/pngcrush-overlay/setjmp.h new file mode 100644 index 000000000..d920c0176 --- /dev/null +++ b/registry/native/c/pngcrush-overlay/setjmp.h @@ -0,0 +1,30 @@ +/* setjmp.h — stub for WASI targets without WASM Exception Handling. + * + * pngcrush uses cexcept.h (not setjmp) for its own error handling. + * However, the bundled libpng's simplified API (png_safe_execute) + * references setjmp/longjmp directly. Since pngcrush doesn't use + * the simplified API, these stubs satisfy the linker without + * requiring the WASM EH proposal. + * + * If setjmp is actually called at runtime, we abort. + */ + +#ifndef _SETJMP_H +#define _SETJMP_H + +#include + +typedef int jmp_buf[1]; + +static inline int setjmp(jmp_buf env) { + (void)env; + return 0; +} + +static inline _Noreturn void longjmp(jmp_buf env, int val) { + (void)env; + (void)val; + abort(); +} + +#endif /* _SETJMP_H */ diff --git a/registry/package.json b/registry/package.json index d81fa8ad7..d5372ca85 100644 --- a/registry/package.json +++ b/registry/package.json @@ -16,7 +16,9 @@ "devDependencies": { "@rivet-dev/agent-os-common": "link:software/common", "@rivet-dev/agent-os-core": "link:../packages/core", + "@rivet-dev/agent-os-coreutils": "link:software/coreutils", "@rivet-dev/agent-os-curl": "link:software/curl", + "@rivet-dev/agent-os-pngcrush": "link:software/pngcrush", "@rivet-dev/agent-os-posix": "link:../packages/posix", "@rivet-dev/agent-os-python": "link:../packages/python", "@secure-exec/core": "^0.2.1", diff --git a/registry/software/everything/package.json b/registry/software/everything/package.json index 83a5591ad..4de0ef102 100644 --- a/registry/software/everything/package.json +++ b/registry/software/everything/package.json @@ -37,7 +37,8 @@ "@rivet-dev/agent-os-tree": "workspace:*", "@rivet-dev/agent-os-file": "workspace:*", "@rivet-dev/agent-os-yq": "workspace:*", - "@rivet-dev/agent-os-codex": "workspace:*" + "@rivet-dev/agent-os-codex": "workspace:*", + "@rivet-dev/agent-os-pngcrush": "workspace:*" }, "devDependencies": { "@rivet-dev/agent-os-registry-types": "link:../../../packages/registry-types", diff --git a/registry/software/everything/src/index.ts b/registry/software/everything/src/index.ts index b523bbc86..8ffee47e0 100644 --- a/registry/software/everything/src/index.ts +++ b/registry/software/everything/src/index.ts @@ -16,6 +16,7 @@ import tree from "@rivet-dev/agent-os-tree"; import file from "@rivet-dev/agent-os-file"; import yq from "@rivet-dev/agent-os-yq"; import codex from "@rivet-dev/agent-os-codex"; +import pngcrush from "@rivet-dev/agent-os-pngcrush"; const everything = [ coreutils, @@ -36,6 +37,7 @@ const everything = [ file, yq, codex, + pngcrush, ]; export default everything; @@ -58,4 +60,5 @@ export { file, yq, codex, + pngcrush, }; diff --git a/registry/software/pngcrush/package.json b/registry/software/pngcrush/package.json new file mode 100644 index 000000000..b6c354ec0 --- /dev/null +++ b/registry/software/pngcrush/package.json @@ -0,0 +1,28 @@ +{ + "name": "@rivet-dev/agent-os-pngcrush", + "version": "0.0.0", + "type": "module", + "license": "Apache-2.0", + "description": "PNG optimizer for agentOS (pngcrush)", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "wasm" + ], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc", + "check-types": "tsc --noEmit" + }, + "devDependencies": { + "@rivet-dev/agent-os-registry-types": "link:../../../packages/registry-types", + "@types/node": "^22.10.2", + "typescript": "^5.9.2" + } +} diff --git a/registry/software/pngcrush/src/index.ts b/registry/software/pngcrush/src/index.ts new file mode 100644 index 000000000..1b57b1f9e --- /dev/null +++ b/registry/software/pngcrush/src/index.ts @@ -0,0 +1,20 @@ +import type { WasmCommandPackage } from "@rivet-dev/agent-os-registry-types"; +import { resolve, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const pkg = { + name: "pngcrush", + aptName: "pngcrush", + description: "PNG optimizer (pngcrush)", + source: "c" as const, + commands: [ + { name: "pngcrush", permissionTier: "read-write" as const }, + ], + get commandDir() { + return resolve(__dirname, "..", "wasm"); + }, +} satisfies WasmCommandPackage; + +export default pkg; diff --git a/registry/software/pngcrush/tsconfig.json b/registry/software/pngcrush/tsconfig.json new file mode 100644 index 000000000..8f24167af --- /dev/null +++ b/registry/software/pngcrush/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"] +} diff --git a/registry/tests/wasmvm/pngcrush.test.ts b/registry/tests/wasmvm/pngcrush.test.ts new file mode 100644 index 000000000..20153b549 --- /dev/null +++ b/registry/tests/wasmvm/pngcrush.test.ts @@ -0,0 +1,120 @@ +/** + * Integration tests for pngcrush WASM command. + * + * Verifies pngcrush can optimize a PNG file, produces valid output, + * and shows version/help info using the AgentOs API with real WASM binaries. + */ + +import { existsSync } from "node:fs"; +import { deflateSync } from "node:zlib"; +import { describe, it, expect, afterEach, beforeEach } from "vitest"; +import { AgentOs } from "@rivet-dev/agent-os-core"; +import coreutils from "@rivet-dev/agent-os-coreutils"; +import pngcrush from "../../software/pngcrush/dist/index.js"; + +const hasCoreutils = existsSync(coreutils.commandDir); +const hasPngcrush = existsSync(pngcrush.commandDir); + +function skipReason(): string | false { + if (!hasCoreutils) return "coreutils WASM binaries not available"; + if (!hasPngcrush) return "pngcrush WASM binary not available"; + return false; +} + +/** + * Generate a minimal valid 1x1 red PNG using Node's zlib for correct compression. + */ +function createMinimalPng(): Uint8Array { + const buf: number[] = []; + + // PNG signature + buf.push(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a); + + // IHDR chunk: 1x1, 8-bit RGB + const ihdrData = [ + 0x00, 0x00, 0x00, 0x01, // width = 1 + 0x00, 0x00, 0x00, 0x01, // height = 1 + 0x08, // bit depth = 8 + 0x02, // color type = RGB + 0x00, // compression = deflate + 0x00, // filter = adaptive + 0x00, // interlace = none + ]; + writeChunk(buf, "IHDR", ihdrData); + + // IDAT chunk: zlib-compressed scanline (filter byte 0 + RGB pixel) + const rawScanline = Buffer.from([0x00, 0xff, 0x00, 0x00]); // filter=none, R=255, G=0, B=0 + const compressed = deflateSync(rawScanline); + writeChunk(buf, "IDAT", [...compressed]); + + // IEND chunk + writeChunk(buf, "IEND", []); + + return new Uint8Array(buf); +} + +function writeChunk(buf: number[], type: string, data: number[]) { + const len = data.length; + buf.push((len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff); + for (let i = 0; i < 4; i++) buf.push(type.charCodeAt(i)); + buf.push(...data); + const crcInput = new Uint8Array(4 + data.length); + for (let i = 0; i < 4; i++) crcInput[i] = type.charCodeAt(i); + crcInput.set(data, 4); + const crc = crc32(crcInput); + buf.push((crc >> 24) & 0xff, (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff); +} + +function crc32(data: Uint8Array): number { + let crc = 0xffffffff; + for (let i = 0; i < data.length; i++) { + crc ^= data[i]; + for (let j = 0; j < 8; j++) { + crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0); + } + } + return (crc ^ 0xffffffff) >>> 0; +} + +describe.skipIf(skipReason())("pngcrush command", () => { + let vm: AgentOs; + + beforeEach(async () => { + vm = await AgentOs.create({ software: [coreutils, pngcrush] }); + }); + + afterEach(async () => { + await vm.dispose(); + }); + + it("prints version with -version flag", async () => { + const result = await vm.exec("pngcrush -version"); + expect(result.exitCode).toBe(0); + const output = result.stdout + result.stderr; + expect(output).toContain("pngcrush 1.8"); + }); + + it("optimizes a PNG file and produces valid output", async () => { + const inputPng = createMinimalPng(); + await vm.writeFile("/tmp/input.png", inputPng); + + const result = await vm.exec("pngcrush -m 1 /tmp/input.png /tmp/output.png"); + expect(result.exitCode).toBe(0); + + // Verify output file was created and is a valid PNG + expect(await vm.exists("/tmp/output.png")).toBe(true); + const outputData = await vm.readFile("/tmp/output.png"); + expect(outputData.length).toBeGreaterThan(0); + // PNG signature + expect(outputData[0]).toBe(0x89); + expect(outputData[1]).toBe(0x50); // P + expect(outputData[2]).toBe(0x4e); // N + expect(outputData[3]).toBe(0x47); // G + }); + + it("shows help with -h flag", async () => { + const result = await vm.exec("pngcrush -h"); + const output = result.stdout + result.stderr; + expect(output.toLowerCase()).toContain("usage"); + }); +});