Add publish workflow (#798)

* Change build-mod to belong to packages

* Add publish script and workflow

* Fix typo in variable name
This commit is contained in:
Cooldude2606 authored and GitHub committed 2025-11-01 16:10:41 +00:00
1 parent 3fbd9a995d
commit 41151d47f0
6 files changed
+201 -6

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
name: Publish
on:
release:
types:
- released
jobs:
npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 22.x
registry-url: https://registry.npmjs.org
- run: pnpm install --no-frozen-lockfile
- run: pnpm -r --filter='./{plugins,packages}/*' publish
env:
NODE_AUTH_TOKEN: $${{ secrets.NPM_TOKEN }}
- uses: softprops/action-gh-release@v2
with:
files: pnpm-lock.yaml
factorio:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 22.x
- run: pnpm install --no-frozen-lockfile
- run: pnpm -r --filter='./{plugins,packages}/*' run build-mod --output-dir ../../dist
- run: node ./publish.js factorio ./dist
env:
FACTORIO_TOKEN: ${{ secrets.FACTORIO_TOKEN }}
- uses: softprops/action-gh-release@v2
with:
files: ./dist/*
+3 -3
View File
@@ -86,8 +86,8 @@ async function removeExecutionArtifacts() {
async function main() {
await yargs(process.argv.slice(2))
.scriptName("clean")
.usage("$0 <command> [options]")
.option("dry", {
.option("dry-run", {
alias: "dry",
nargs: 0,
describe: "Will not delete any files",
default: false,
@@ -108,7 +108,7 @@ async function main() {
DRY = argv.dry;
removeBuildArtifacts();
})
.demandCommand(1, "You need to specify a command to run")
.demandCommand(1, "A command must be specified")
.strict()
.parse();
}
+1 -1
View File
@@ -5,7 +5,7 @@
"scripts": {
"build": "tsc --build",
"watch": "tsc --build --watch",
"build-mod": "node ./packages/lib/build_mod.js --source-dir ./packages/host/lua/clusterio_lib",
"build-mod": "pnpm run -r build-mod --output-dir ../../dist",
"test": "pnpm run build && mocha --enable-source-maps --check-leaks -R spec --recursive test \"plugins/*/test/**\" --exclude \"test/manual/**\"",
"silent-test": "SILENT_TEST=y pnpm test",
"fast-test": "FAST_TEST=y pnpm test",
+1
View File
@@ -7,6 +7,7 @@
"main": "dist/node/index.js",
"scripts": {
"prepare": "tsc --build",
"build-mod": "node ../lib/build_mod.js --source-dir ./lua/clusterio_lib",
"test": "echo \"Error: no test specified\" && exit 1"
},
"bin": {
+14 -2
View File
@@ -115,9 +115,21 @@ async function main() {
"clean": { describe: "Remove previous builds", type: "boolean", default: false },
"build": { describe: "Build mod(s)", type: "boolean", default: true },
"pack": { describe: "Pack into zip file", type: "boolean", default: true },
"source-dir": { describe: "Path to mod source files", nargs: 1, type: "string", demandOption: true },
"output-dir": { describe: "Path to output built mod(s)", nargs: 1, type: "string", default: "dist" },
"factorio-version": { describe: "Override factorio_version", type: "string" },
"source-dir": {
describe: "Path to mod source files",
type: "string",
nargs: 1,
normalize: true,
demandOption: true,
},
"output-dir": {
describe: "Path to output built mod(s)",
type: "string",
nargs: 1,
normalize: true,
default: "dist/factorio",
},
})
.strict()
.argv
+134
View File
@@ -0,0 +1,134 @@
/**
* Publish any mods that need to be published
*/
"use strict";
const FormData = require("form-data");
const fs = require("fs-extra");
const yargs = require("yargs");
let DRY = false;
const modReleaseCache = new Map();
/**
* Gets a list of all releases for a mod
*/
async function requestModReleases(mod) {
const cache = modReleaseCache.get(mod);
if (cache) {
return cache;
}
// eslint-disable-next-line no-console
console.log(`Requesting current releases of ${mod}`);
const response = await fetch(`https://mods.factorio.com/api/mods/${mod}`);
if (!response.ok) {
throw new Error(`Failed request to mod portal api: ${response.statusText}\n${await response.text()}`);
}
const json = await response.json();
modReleaseCache.set(mod, json.releases);
return json.releases;
}
/**
* Upload a mod to the mod portal
*/
async function uploadModRelease(file, name) {
// eslint-disable-next-line no-console
console.log(`Uploading new release ${file}`);
if (DRY) {
return;
}
const headers = {
Authorization: `Bearer ${process.env.FACTORIO_TOKEN}`,
};
// Start the upload process by requesting an upload url
const initFormData = new FormData();
initFormData.append("mod", name);
const initResponse = await fetch("https://mods.factorio.com/api/v2/mods/releases/init_upload", {
method: "POST",
headers: headers,
body: initFormData,
});
if (!initResponse.ok) {
throw new Error(`Failed init mod upload: ${initResponse.statusText}\n${await initResponse.text()}`);
}
// Upload the new version of the mod
const uploadUrl = (await initResponse.json()).upload_url;
const uploadFormData = new FormData();
uploadFormData.append("file", fs.createReadStream(file));
const uploadResponse = await fetch(uploadUrl, {
method: "POST",
headers: headers,
body: uploadFormData,
});
if (!uploadResponse.ok) {
throw new Error(`Failed mod upload: ${uploadResponse.statusText}\n${await uploadResponse.text()}`);
}
}
const regexModZip = /(\w+)_\d+.\d+.\d+\.zip/;
/**
* Publishes all mods that require publishing
*/
async function publishMods(dir) {
const files = await fs.readdir(dir);
const mods = files
.map(file => regexModZip.exec(file))
.filter(file => Boolean(file));
const uploading = [];
for (const mod of mods) {
const [file, name] = mod;
const releases = await requestModReleases(name);
if (!releases.some(release => release.file_name === file)) {
uploading.push(uploadModRelease(file, name));
} else {
// eslint-disable-next-line no-console
console.log(`Skipped ${file}`);
}
}
await Promise.all(uploading);
}
/**
* Main function for this script
*/
async function main() {
await yargs(process.argv.slice(2))
.scriptName("publish")
.option("dry-run", {
alias: "dry",
nargs: 0,
describe: "Will not publish anything",
default: false,
type: "boolean",
})
.command(["factorio <mods-dir>", "$0 <mods-dir>"], "Publish factorio mods", yargs => {
yargs
.positional("mods-dir", {
describe: "Path to packed mods to publish",
normalize: true,
type: "string",
})
.demandOption("mods-dir", "A path to the mods dir must be specified");
}, async argv => {
DRY = argv.dry;
publishMods(argv.modsDir);
})
.strict()
.parse();
}
// Run main if started from command line
if (module === require.main) {
main();
}