name: Factorio Mod Push description: Set version, zip, and upload a Factorio mod to mods.factorio.com inputs: mod-folder: description: Path to the Factorio mod root directory (containing info.json) required: false default: "." version: description: Semver version string to set in info.json, e.g. 1.0.1 required: true factorio-token: description: Factorio API key with ModPortal: Upload Mods permission required: true outputs: zip-path: description: Absolute path to the generated zip file value: ${{ steps.package.outputs.zip-path }} runs: using: composite steps: - name: Validate inputs shell: bash run: | MOD_FOLDER="${{ inputs.mod-folder }}" VERSION="${{ inputs.version }}" if [ ! -f "$MOD_FOLDER/info.json" ]; then echo "ERROR: info.json not found in $MOD_FOLDER" exit 1 fi if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then echo "ERROR: version '$VERSION' does not match semver x.y.z" exit 1 fi - name: Set version in info.json shell: bash run: | MOD_FOLDER="${{ inputs.mod-folder }}" VERSION="${{ inputs.version }}" sed -i 's/"version": *"[^"]*"/"version": "'"$VERSION"'"/' "$MOD_FOLDER/info.json" - name: Create README.md if missing shell: bash run: | MOD_FOLDER="${{ inputs.mod-folder }}" if [ ! -f "$MOD_FOLDER/README.md" ]; then TITLE=$(sed -n 's/.*"title": *"\([^"]*\)".*/\1/p' "$MOD_FOLDER/info.json") DESC=$(sed -n 's/.*"description": *"\([^"]*\)".*/\1/p' "$MOD_FOLDER/info.json") cat > "$MOD_FOLDER/README.md" <<- EOF # $TITLE $DESC EOF echo "Created $MOD_FOLDER/README.md" fi - name: Read mod name from info.json id: modinfo shell: bash run: | MOD_FOLDER="${{ inputs.mod-folder }}" NAME=$(sed -n 's/.*"name": *"\([^"]*\)".*/\1/p' "$MOD_FOLDER/info.json") echo "name=$NAME" >> $GITHUB_OUTPUT - name: Package mod zip id: package shell: bash run: | MOD_FOLDER="${{ inputs.mod-folder }}" NAME=${{ steps.modinfo.outputs.name }} VERSION="${{ inputs.version }}" ZIP_NAME="${NAME}_${VERSION}.zip" ZIP_PATH="${GITHUB_WORKSPACE}/${ZIP_NAME}" TMPDIR=$(mktemp -d) cp -r "$MOD_FOLDER/." "$TMPDIR/$NAME/" find "$TMPDIR/$NAME" -type d -name node_modules -exec rm -rf {} + 2>/dev/null || true find "$TMPDIR/$NAME" -type d -name .git -exec rm -rf {} + 2>/dev/null || true cd "$TMPDIR" zip -r "$ZIP_PATH" "$NAME/" rm -rf "$TMPDIR" echo "zip-path=$ZIP_PATH" >> $GITHUB_OUTPUT echo "Created $ZIP_PATH" - name: Init upload to Factorio mod portal id: init shell: bash run: | NAME=${{ steps.modinfo.outputs.name }} RESPONSE=$(curl -s -w "\n%{http_code}" \ -X POST "https://mods.factorio.com/api/v2/mods/releases/init_upload" \ -H "Authorization: Bearer ${{ inputs.factorio-token }}" \ -F "mod=$NAME") HTTP_CODE=$(echo "$RESPONSE" | tail -n1) BODY=$(echo "$RESPONSE" | sed '$d') if [ "$HTTP_CODE" -ne 200 ]; then echo "ERROR: init_upload failed (HTTP $HTTP_CODE)" echo "Response: $BODY" exit 1 fi UPLOAD_URL=$(echo "$BODY" | sed -n 's/.*"upload_url": *"\([^"]*\)".*/\1/p') if [ -z "$UPLOAD_URL" ]; then echo "ERROR: init_upload failed - no upload_url in response" echo "Response: $BODY" exit 1 fi echo "upload_url=$UPLOAD_URL" >> $GITHUB_OUTPUT - name: Upload zip to Factorio mod portal shell: bash run: | ZIP_PATH=${{ steps.package.outputs.zip-path }} UPLOAD_URL=${{ steps.init.outputs.upload_url }} RESPONSE=$(curl -s -w "\n%{http_code}" \ -X POST "$UPLOAD_URL" \ -F "file=@$ZIP_PATH") HTTP_CODE=$(echo "$RESPONSE" | tail -n1) BODY=$(echo "$RESPONSE" | sed '$d') if [ "$HTTP_CODE" -ne 200 ]; then echo "ERROR: upload failed (HTTP $HTTP_CODE)" echo "Response: $BODY" exit 1 fi if ! echo "$BODY" | grep -q '"success": *true'; then echo "ERROR: upload reported failure" echo "Response: $BODY" exit 1 fi echo "Upload successful: ${{ steps.modinfo.outputs.name }} ${{ inputs.version }}"