diff --git a/README.md b/README.md index 36bd51c..ab0c7f0 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,12 @@ Pushes both SHA-tagged and `latest` versions. **Inputs:** `chart-path`, `version`, `app-version`, `username`, `password` +### `factorio-mod-push` +Set version, zip, and upload a Factorio mod to mods.factorio.com. Creates README.md if missing. Fails on any API error (auth, unknown mod, invalid release). + +**Inputs:** `mod-folder` (default `.`), `version`, `factorio-token` +**Outputs:** `zip-path` + ### `helm-deploy` Deploy via helmbot SSH to vmd127288. Validates all inputs server-side. diff --git a/factorio-mod-push/action.yml b/factorio-mod-push/action.yml new file mode 100644 index 0000000..2e0d5fa --- /dev/null +++ b/factorio-mod-push/action.yml @@ -0,0 +1,146 @@ +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 }}"