project re-organisation, and rename

This commit is contained in:
Adam 2021-12-01 16:57:46 +01:00
parent 53862ef1b5
commit 633e898f3c
52 changed files with 525 additions and 387 deletions

View file

@ -0,0 +1,235 @@
import com.github.gradle.node.npm.task.NpmTask
import dev.adamko.factoriowebmap.configurations.asConsumer
import dev.adamko.factoriowebmap.configurations.asProvider
import dev.adamko.factoriowebmap.configurations.factorioModAttributes
import dev.adamko.factoriowebmap.configurations.typescriptAttributes
import groovy.json.JsonOutput
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.kotlin.dsl.support.useToRun
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
import org.jetbrains.kotlin.util.suffixIfNot
plugins {
idea
id("dev.adamko.factoriowebmap.archetype.node")
}
val tokens: Map<String, String> by project.extra
val licenseFile: RegularFile by project.extra
val tsSrcDir: Directory = layout.projectDirectory.dir("src/main/typescript")
node {
nodeProjectDir.set(tsSrcDir)
}
val dataModelTs by configurations.registering {
asConsumer()
typescriptAttributes(objects)
defaultDependencies {
project.dependencies.create(projects.modules.eventsSchema)
}
}
val typescriptToLua by tasks.registering(NpmTask::class) {
description = "Convert Typescript To Lua"
group = project.name
dependsOn(tasks.npmInstall)
execOverrides { standardOutput = System.out }
npmCommand.set(listOf("run", "build"))
inputs.dir(project.node.nodeProjectDir)
.skipWhenEmpty()
.withPropertyName("sourceFiles")
.withPathSensitivity(PathSensitivity.RELATIVE)
val intermediateOutputDir = temporaryDir
args.set(parseSpaceSeparatedArgs("-- --outDir $intermediateOutputDir"))
val outputDir = layout.buildDirectory.dir("typescriptToLua")
outputs.dir(outputDir)
.withPropertyName("outputDir")
ignoreExitValue.set(false)
doFirst("clean") {
delete(intermediateOutputDir)
}
doLast("syncTstlOutput") {
sync {
from(intermediateOutputDir)
into(outputDir)
}
}
}
val updatePackageJsonVersion by tasks.registering {
group = project.name
description = """
Read the package.json file and set the version to be the project's version.
""".trimIndent()
val projectVersion = "${project.version}"
inputs.properties(
"projectVersion" to projectVersion,
)
val packageJsonFile = layout.projectDirectory.file("src/main/typescript/package.json")
outputs.file(packageJsonFile)
val jsonFormatter = Json {
prettyPrint = true
prettyPrintIndent = " "
}
onlyIf {
// check to see if the version is already up-to-date
val packageJsonContent = packageJsonFile.asFile.readText()
val packageJson = jsonFormatter.parseToJsonElement(packageJsonContent).jsonObject
packageJson["version"]?.jsonPrimitive?.content != projectVersion
}
doLast {
val packageJsonContent = packageJsonFile.asFile.readText()
val packageJson = jsonFormatter.parseToJsonElement(packageJsonContent).jsonObject
val packageJsonUpdate = JsonObject(
packageJson + ("version" to JsonPrimitive(projectVersion))
)
val packageJsonContentUpdated =
jsonFormatter
.encodeToString(packageJsonUpdate)
.suffixIfNot("\n")
packageJsonFile.asFile.writer().useToRun {
write(packageJsonContentUpdated)
}
}
}
val updateDataModel by tasks.registering(Sync::class) {
group = project.name
description = "Fetch the latest shared data-model"
dependsOn(dataModelTs)
from(
dataModelTs.map { c ->
c.incoming
.artifactView { lenient(true) }
.artifacts
.artifactFiles
.filter { file -> file.exists() }
}
)
into(layout.projectDirectory.dir("src/main/typescript/model"))
}
val packageMod by tasks.registering(Zip::class) {
description = "Package mod files into ZIP"
group = project.name
dependsOn(typescriptToLua)
inputs.properties(tokens)
from(layout.projectDirectory.dir("src/main/resources/mod-data")) {
include("**/**")
filter<ReplaceTokens>("tokens" to tokens)
}
dependsOn(typescriptToLua)
from(licenseFile)
into(rootProject.name)
// Factorio required format is:
// - filename: `mod-name_version.zip`
// - zip contains one directory, `mod-name`
archiveFileName.set("${rootProject.name}_${project.version}.zip")
destinationDirectory.set(layout.buildDirectory.dir("dist"))
doLast {
val outDir = destinationDirectory.asFile.get().toRelativeString(layout.projectDirectory.asFile)
val outZip = "${archiveFileName.orNull}"
logger.lifecycle("Packaged mod into $outDir/$outZip")
}
}
val downloadFactorioApiDocs by tasks.registering {
group = project.name
val target = uri("https://lua-api.factorio.com/latest/runtime-api.json")
val apiFilename = File(target.path).name
val downloadedFile = file("$temporaryDir/$apiFilename")
val apiFile = layout.buildDirectory.file(apiFilename)
outputs.file(apiFile)
doLast {
ant.invokeMethod(
"get", mapOf(
"src" to target,
"dest" to downloadedFile,
"verbose" to true,
)
)
val json = downloadedFile.readText()
val prettyJson = JsonOutput.prettyPrint(json)
apiFile.get().asFile.writeText(prettyJson)
logger.lifecycle("Downloaded Factorio API json: $apiFile")
}
}
val factorioModProvider by configurations.registering {
asProvider()
factorioModAttributes(objects)
outgoing.artifact(packageMod)
}
tasks.assemble { dependsOn(updateDataModel, updatePackageJsonVersion) }
tasks.build { dependsOn(packageMod) }
//
//idea {
// // this doesn't work - no source sets?
// module {
// sourceDirs.add(mkdir(layout.projectDirectory.dir("src/main/typescript").asFile))
// excludeDirs.add(mkdir(layout.projectDirectory.dir("src/main/typescript/node_modules").asFile))
// resourceDirs.add(mkdir(layout.projectDirectory.dir("src/main/resources").asFile))
// resourceDirs.add(mkdir(layout.projectDirectory.dir("infra").asFile))
// excludeDirs.add(mkdir(layout.projectDirectory.dir("infra/factorio-server").asFile))
// }
//}
//
//val typescriptSrcSet =
// project.objects.sourceDirectorySet("typescript", "typescript").apply {
// srcDir(layout.projectDirectory.dir("src/main/typescript"))
//
// destinationDirectory.set(modBuildDir.dir("typescriptToLua").asFile)
// compiledBy(tstlTask) {
//
// project.objects.directoryProperty().apply {
// set(modBuildDir.dir("typescriptToLua"))
// }
// }
// }

View file

Before

Width:  |  Height:  |  Size: 702 B

After

Width:  |  Height:  |  Size: 702 B

Before After
Before After

View file

@ -43,4 +43,4 @@ interface SurfaceData extends FactorioObjectData {
index: uint;
name: string;
object_name: string;
}
}

View file

@ -1,6 +1,6 @@
{
"name": "factorio-web-map-mod",
"version": "0.0.6",
"version": "0.0.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View file

@ -9,8 +9,7 @@
"engines": {
"node": ">=10.13.0"
},
"devDependencies": {
},
"devDependencies": {},
"dependencies": {
"lua-types": "^2.11.0",
"@types/node": "^12.0.8",
@ -21,10 +20,7 @@
"//": {
"typed-factorio": "0.10.0"
},
"peerDependencies": {
},
"optionalDependencies": {
},
"bundledDependencies": [
]
"peerDependencies": {},
"optionalDependencies": {},
"bundledDependencies": []
}

View file

@ -11,14 +11,13 @@ description = """
""".trimIndent()
val projectId: String by project.extra
val buildDir: Directory = layout.buildDirectory.dir(projectId).get()
@Suppress("UnstableApiUsage")
dependencies {
implementation("org.apache.kafka:kafka-streams:3.0.0")
implementation(projects.modules.factorioEventsDataModel)
implementation(projects.modules.eventsSchema)
// implementation("com.ionspin.kotlin:bignum:0.3.3")

View file

@ -6,9 +6,6 @@ plugins {
kotlin("plugin.serialization")
}
val projectId: String by project.extra
val buildDir: Directory = layout.buildDirectory.dir(projectId).get()
dependencies {
implementation(platform(libs.kotlinx.serialization.bom))
implementation(libs.kotlinx.serialization.core)

View file

@ -1,12 +0,0 @@
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
plugins {
id("dev.adamko.factoriowebmap.archetype.base")
}
description =
"""
Send events from a Factorio server to a Kafka topic
""".trimIndent()
val projectId: String by project.extra

View file

@ -0,0 +1,81 @@
import dev.adamko.factoriowebmap.configurations.asConsumer
import dev.adamko.factoriowebmap.configurations.factorioModAttributes
import dev.adamko.isProcessRunning
import dev.adamko.not
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
plugins {
id("dev.adamko.factoriowebmap.archetype.process-runner")
}
val tokens: Map<String, String> by project.extra
val clientModsDirectory = file("""D:\Users\Adam\AppData\Roaming\Factorio\mods""")
val factorioGameId = "427520"
val steamExe = """C:\Program Files (x86)\Steam\steam.exe"""
val factorioMod: Configuration by configurations.creating {
asConsumer()
factorioModAttributes(objects)
}
dependencies {
factorioMod(projects.modules.eventsMod)
}
//<editor-fold desc="Mod deployment tasks">
val deployModToClient by tasks.registering(Copy::class) {
description = "Copy the mod to the Factorio client"
group = project.name
from(factorioMod)
into(clientModsDirectory)
doLast {
logger.lifecycle("Copying mod from ${source.files} to $destinationDir")
}
}
//</editor-fold>
//<editor-fold desc="Factorio client lifecycle tasks">
fun isFactorioRunning(): Spec<Task> = isProcessRunning("factorio.exe")
val clientLaunch by tasks.registering(Exec::class) {
description = "Run Factorio Steam game client"
group = project.name
onlyIf(!isFactorioRunning())
dependsOn(deployModToClient)
mustRunAfter(clientKill)
commandLine = parseSpaceSeparatedArgs(
// """explorer "steam://rungameid/$factorioGameId// --mp-connect localhost/" """ // not working
// """explorer "steam://run/$factorioGameId//--mp-connect localhost/" """ // works! But has annoying pop-up
""" $steamExe -applaunch $factorioGameId --mp-connect localhost """
)
doFirst { logger.lifecycle("Launching factorio.exe") }
}
val clientKill by tasks.registering(Exec::class) {
description = "Run Factorio Steam game client"
group = project.name
onlyIf(isFactorioRunning())
commandLine = parseSpaceSeparatedArgs(""" taskkill /im factorio.exe """)
doFirst { logger.lifecycle("Killing factorio.exe") }
}
//</editor-fold>
tasks.build { dependsOn(deployModToClient) }
tasks.processRun {
dependsOn(
deployModToClient,
clientLaunch,
)
}
tasks.processKill {
dependsOn(clientKill)
}

View file

@ -0,0 +1,77 @@
import dev.adamko.factoriowebmap.configurations.asConsumer
import dev.adamko.factoriowebmap.configurations.factorioModAttributes
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
plugins {
id("dev.adamko.factoriowebmap.archetype.process-runner")
}
val tokens: Map<String, String> by project.extra
val modInfraDir: Directory = layout.projectDirectory.dir("infra")
val factorioServerDataDir: Directory = modInfraDir.dir("factorio-server")
val factorioMod: Configuration by configurations.creating {
asConsumer()
factorioModAttributes(objects)
}
dependencies {
factorioMod(projects.modules.eventsMod)
}
val deployModToServer by tasks.registering(Copy::class) {
description = "Copy the mod to the Factorio Docker server"
group = project.name
from(factorioMod)
into(factorioServerDataDir.dir("mods"))
doLast {
logger.lifecycle("Copying mods ${source.files} to $destinationDir")
}
}
//<editor-fold desc="Factorio server lifecycle tasks">
val factorioServerStop = tasks.register<Exec>("factorioServerStop") {
group = project.name
mustRunAfter(deployModToServer)
workingDir(modInfraDir)
commandLine = parseSpaceSeparatedArgs("docker-compose stop factorio-server")
}
val factorioServerUp = tasks.register<Exec>("factorioServerUp") {
group = project.name
mustRunAfter(deployModToServer)
dependsOn(factorioServerStop)
workingDir(modInfraDir)
commandLine = parseSpaceSeparatedArgs("docker-compose up -d factorio-server")
}
val factorioServerRestart: Task by tasks.creating {
group = project.name
dependsOn(factorioServerStop, factorioServerUp)
}
//</editor-fold>
tasks.build { dependsOn(deployModToServer) }
tasks.processRun {
description = "Build the mod, upload to Server and Client, and start both"
dependsOn(
deployModToServer,
factorioServerRestart,
)
}
tasks.processKill {
dependsOn(
factorioServerStop,
)
}

View file

@ -0,0 +1,39 @@
version: "3.9"
services:
factorio-server:
image: factoriotools/factorio:1.1.48
# logging:
# driver: "json-file"
container_name: "factorio-server"
restart: "unless-stopped"
networks:
- "factorio-events-nw"
ports:
- "34197:34197/udp" # factorio
- "27015:27015/tcp" # rcon
volumes:
- "./factorio-server:/factorio"
# logging:
# driver: "mickyg/kafka-logdriver:latest"
# options:
# KAFKA_BROKER_ADDR: "kafka-kraft"
# LOG_TOPIC: "dockerlogs-factorio-server"
# LOG_TAG: "factorio-server"
# environment:
# RCON_PORT: 27715
# environment:
# - PUID=1000
# - PGID=1000
# - UPDATE_MODS_ON_START=true
# - USERNAME=FactorioUsername
# - TOKEN=FactorioToken
networks:
kafka-nw:
name: "kafka-nw"
external: true
factorio-events-nw:
name: "factorio-events-nw"

View file

@ -2,35 +2,6 @@ version: "3.9"
services:
factorio-server:
image: factoriotools/factorio:1.1.48
# logging:
# driver: "json-file"
container_name: "factorio-server"
restart: "unless-stopped"
networks:
- "factorio-events-nw"
ports:
- "34197:34197/udp" # factorio
- "27015:27015/tcp" # rcon
volumes:
- "./factorio-server:/factorio"
# logging:
# driver: "mickyg/kafka-logdriver:latest"
# options:
# KAFKA_BROKER_ADDR: "kafka-kraft"
# LOG_TOPIC: "dockerlogs-factorio-server"
# LOG_TAG: "factorio-server"
# environment:
# RCON_PORT: 27715
# environment:
# - PUID=1000
# - PGID=1000
# - UPDATE_MODS_ON_START=true
# - USERNAME=FactorioUsername
# - TOKEN=FactorioToken
kafka:
image: dev-adamko/kafka-kraft
container_name: "kafka"
@ -74,10 +45,9 @@ services:
# - "8083:8083"
#
networks:
kafka-nw:
name: "kafka-nw"
factorio-events-nw:
name: "factorio-events-nw"
external: true

View file

@ -0,0 +1,6 @@
plugins {
id("dev.adamko.factoriowebmap.archetype.process-runner")
}
description = "Send events from a Factorio server to a Kafka topic "

View file

@ -1,318 +0,0 @@
import com.github.gradle.node.npm.task.NpmTask
import dev.adamko.factoriowebmap.configurations.asConsumer
import dev.adamko.factoriowebmap.configurations.typescriptAttributes
import groovy.json.JsonOutput
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.apache.tools.ant.filters.ReplaceTokens
import org.gradle.kotlin.dsl.support.useToRun
import org.jetbrains.kotlin.util.parseSpaceSeparatedArgs
import org.jetbrains.kotlin.util.suffixIfNot
plugins {
idea
id("dev.adamko.factoriowebmap.archetype.node")
}
val projectId: String by project.extra
val tokens: Map<String, String> by project.extra
val tsSrcDir: Directory = layout.projectDirectory.dir("src/main/typescript")
val modBuildDir: Directory = layout.buildDirectory.dir(projectId).get()
node {
nodeProjectDir.set(tsSrcDir)
}
val tstlTask = tasks.register<NpmTask>("typescriptToLua") {
description = "Convert Typescript To Lua"
group = projectId
dependsOn(tasks.npmInstall)
execOverrides { standardOutput = System.out }
npmCommand.set(listOf("run", "build"))
inputs.dir(project.node.nodeProjectDir)
.skipWhenEmpty()
.withPropertyName("sourceFiles")
.withPathSensitivity(PathSensitivity.RELATIVE)
val intermediateOutputDir = temporaryDir
args.set(parseSpaceSeparatedArgs("-- --outDir $intermediateOutputDir"))
val outputDir: Directory = modBuildDir.dir("typescriptToLua")
outputs.dir(outputDir)
.withPropertyName("outputDir")
ignoreExitValue.set(false)
doFirst("clean") {
delete(intermediateOutputDir)
}
doLast("syncTstlOutput") {
sync {
from(intermediateOutputDir)
into(outputDir)
}
}
}
val updatePackageJsonVersion by tasks.registering {
group = project.name
description = """
Read the package.json file and set the version to be the project's version.
""".trimIndent()
val projectVersion = "${project.version}"
inputs.properties(
"projectVersion" to projectVersion,
)
val packageJsonFile = layout.projectDirectory.file("src/main/typescript/package.json")
outputs.file(packageJsonFile)
val jsonFormatter = Json {
prettyPrint = true
prettyPrintIndent = " "
}
onlyIf {
val packageJsonContent = packageJsonFile.asFile.readText()
val packageJson = jsonFormatter.parseToJsonElement(packageJsonContent).jsonObject
packageJson["version"]?.jsonPrimitive?.content != projectVersion
}
doFirst {
delete(temporaryDir)
mkdir(temporaryDir)
}
doLast {
val packageJsonContent = packageJsonFile.asFile.readText()
val packageJson = jsonFormatter.parseToJsonElement(packageJsonContent).jsonObject
val packageJsonUpdate = JsonObject(
packageJson + ("version" to JsonPrimitive(projectVersion))
)
val packageJsonContentUpdated =
jsonFormatter
.encodeToString(packageJsonUpdate)
.suffixIfNot("\n")
packageJsonFile.asFile.writer().useToRun {
write(packageJsonContentUpdated)
}
}
}
tasks.assemble { dependsOn(updatePackageJsonVersion) }
val dataModelTs: Configuration by configurations.creating {
asConsumer()
typescriptAttributes(objects)
}
dependencies {
dataModelTs(projects.modules.factorioEventsDataModel)
}
val updateDataModel by tasks.registering(Sync::class) {
group = project.name
dependsOn(dataModelTs)
from(
dataModelTs.incoming
.artifactView { lenient(true) }
.artifacts
.artifactFiles
.filter { it.exists() }
)
into(layout.projectDirectory.dir("src/main/typescript/generated"))
}
tasks.assemble { dependsOn(updateDataModel) }
idea {
module {
sourceDirs.add(mkdir(layout.projectDirectory.dir("src/main/typescript").asFile))
excludeDirs.add(mkdir(layout.projectDirectory.dir("src/main/typescript/node_modules").asFile))
resourceDirs.add(mkdir(layout.projectDirectory.dir("src/main/resources").asFile))
resourceDirs.add(mkdir(layout.projectDirectory.dir("infra").asFile))
excludeDirs.add(mkdir(layout.projectDirectory.dir("infra/factorio-server").asFile))
}
}
val typescriptSrcSet =
project.objects.sourceDirectorySet("typescript", "typescript").apply {
srcDir(layout.projectDirectory.dir("src/main/typescript"))
destinationDirectory.set(modBuildDir.dir("typescriptToLua").asFile)
compiledBy(tstlTask) {
project.objects.directoryProperty().apply {
set(modBuildDir.dir("typescriptToLua"))
}
}
}
val modPackageTask = tasks.register<Zip>("packageMod") {
description = "Package mod files into ZIP"
group = projectId
dependsOn(tstlTask)
inputs.properties(tokens)
from(layout.projectDirectory.dir("src/main/resources/mod-data")) {
include("**/**")
filter<ReplaceTokens>("tokens" to tokens)
}
from(
rootProject.layout.projectDirectory.file("LICENSE"),
tstlTask,
)
into(rootProject.name)
// Factorio required format is:
// - filename: `mod-name_version.zip`
// - zip contains one directory, `mod-name`
archiveFileName.set("${rootProject.name}_${project.version}.zip")
destinationDirectory.set(modBuildDir.dir("dist"))
doLast {
val outDir = destinationDirectory.asFile.get().toRelativeString(layout.projectDirectory.asFile)
val outZip = "${archiveFileName.orNull}"
logger.lifecycle("Creating mod zip $outDir/$outZip")
}
}
//tasks.build { dependsOn(tstlTask, modPackageTask) }
val modInfraDir: Directory = layout.projectDirectory.dir("infra")
val factorioServerDataDir: Directory = modInfraDir.dir("factorio-server")
val copyModToServerTask = tasks.register<Copy>("copyModToServer") {
description = "Copy the mod to the Factorio Docker server"
group = "$projectId.factorioServer"
dependsOn(modPackageTask)
from(modPackageTask)
into(factorioServerDataDir.dir("mods"))
doLast {
logger.lifecycle("Copying mod from ${source.files} to $destinationDir")
}
}
val factorioServerStop = tasks.register<Exec>("factorioServerStop") {
group = "$projectId.factorioServer"
mustRunAfter(copyModToServerTask)
workingDir(modInfraDir)
commandLine = parseSpaceSeparatedArgs("docker-compose stop factorio-server")
}
val factorioServerUp = tasks.register<Exec>("factorioServerUp") {
group = "$projectId.factorioServer"
mustRunAfter(copyModToServerTask)
dependsOn(factorioServerStop)
workingDir(modInfraDir)
commandLine = parseSpaceSeparatedArgs("docker-compose up -d factorio-server")
}
val factorioServerRestart: Task by tasks.creating {
group = "$projectId.factorioServer"
dependsOn(factorioServerStop, factorioServerUp)
}
//val serverRestartTask = tasks.register<Exec>("dockerRestart") {
// group = "$projectId.factorioServer"
//
// workingDir = layout.projectDirectory.dir("src/test/resources/server/").asFile
// commandLine = parseSpaceSeparatedArgs("docker-compose restart")
//}
val copyModToClientTask = tasks.register<Copy>("copyModToClient") {
description = "Copy the mod to the Factorio client"
group = "$projectId.factorioClient"
dependsOn(modPackageTask)
from(modPackageTask)
into("""D:\Users\Adam\AppData\Roaming\Factorio\mods""")
doLast {
logger.lifecycle("Copying mod from ${source.files} to $destinationDir")
}
}
//val factorioClientLaunch = tasks.register<Exec>("launch") {
// description = "Run Factorio client"
// group = "$projectId.factorioClient"
//
// dependsOn(
// serverRestartTask,
// copyModToClientTask
// )
//
//// workingDir = layout.projectDirectory.dir("src/test/resources/server/").asFile
// commandLine = parseSpaceSeparatedArgs(
// """explorer "steam://rungameid/427520 --mp-connect localhost" """
// )
//}
val launch = tasks.register("modLauncher") {
description = "Build the mod, upload to Server and Client, and start both"
group = projectId
dependsOn(
copyModToServerTask,
copyModToClientTask,
factorioServerRestart,
// factorioClientLaunch,
)
}
tasks.register("downloadFactorioApiDocs") {
group = projectId
val target = uri("https://lua-api.factorio.com/latest/runtime-api.json")
val apiFilename = File(target.path).name
val downloadedFile = file("$temporaryDir/$apiFilename")
val apiFile = modBuildDir.file(apiFilename)
outputs.file(apiFile)
doLast {
ant.invokeMethod(
"get", mapOf(
"src" to target,
"dest" to downloadedFile,
"verbose" to true,
)
)
val json = downloadedFile.readText()
val prettyJson = JsonOutput.prettyPrint(json)
apiFile.asFile.writeText(prettyJson)
logger.lifecycle("Downloaded Factorio API json: $apiFile")
}
}