240 lines
8.5 KiB
JavaScript
240 lines
8.5 KiB
JavaScript
import { ScreepsAPI } from "screeps-api";
|
|
import * as core from "@actions/core";
|
|
import fs from "fs";
|
|
import { glob } from "glob";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { monitorConsole } from "./monitor.js";
|
|
|
|
/**
|
|
* Replaces specific placeholder strings within the provided content with corresponding dynamic values.
|
|
*
|
|
* This function specifically targets three placeholders:
|
|
* - {{gitHash}} is replaced with the current Git commit hash, obtained from the GITHUB_SHA environment variable.
|
|
* - {{gitRef}} is replaced with the Git reference (branch or tag) that triggered the workflow, obtained from the GITHUB_REF environment variable.
|
|
* - {{deployTime}} is replaced with the current ISO timestamp.
|
|
*
|
|
* Note: This function is designed for use within a GitHub Actions workflow where GITHUB_SHA and GITHUB_REF environment variables are automatically set.
|
|
*
|
|
* @param {string} content - The string content in which placeholders are to be replaced.
|
|
* @returns {string} The content with placeholders replaced by their respective dynamic values.
|
|
*/
|
|
export function replacePlaceholders(content, hostname) {
|
|
const deployTime = new Date().toISOString();
|
|
return content
|
|
.replace(/{{gitHash}}/g, process.env.GITHUB_SHA)
|
|
.replace(/{{gitRef}}/g, process.env.GITHUB_REF)
|
|
.replace(/{{deployTime}}/g, deployTime)
|
|
.replace(/{{hostname}}/g, hostname);
|
|
}
|
|
|
|
/**
|
|
* Reads all files matching a specified pattern, replaces certain placeholders in their content, and writes the updated content back to the files.
|
|
*
|
|
* This function searches for files in the filesystem using the provided glob pattern, optionally prefixed. It reads each file,
|
|
* uses the `replacePlaceholders` function to replace specific placeholders in the file's content, and then writes the modified content
|
|
* back to the original file. This is useful for dynamically updating file contents in a batch process, such as during a build or deployment.
|
|
*
|
|
* @param {string} pattern - The glob pattern used to find files. Example: '*.js' for all JavaScript files.
|
|
* @param {string} [prefix] - An optional directory prefix to prepend to the glob pattern. This allows searching within a specific directory.
|
|
* @returns {Promise<string[]>} A promise that resolves with an array of file paths that were processed, or rejects with an error if the process fails.
|
|
*/
|
|
export async function readReplaceAndWriteFiles(pattern, prefix, hostname) {
|
|
let globPattern = prefix ? path.join(prefix, pattern) : pattern;
|
|
globPattern = globPattern.replace(/\\/g, "/");
|
|
const files = await glob(globPattern);
|
|
|
|
let processPromises = files.map((file) => {
|
|
return fs.promises.readFile(file, "utf8").then((content) => {
|
|
content = replacePlaceholders(content, hostname);
|
|
return fs.promises.writeFile(file, content);
|
|
});
|
|
});
|
|
|
|
await Promise.all(processPromises);
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* Reads files matching a glob pattern into a dictionary.
|
|
* @param {string} pattern - Glob pattern to match files.
|
|
* @param {string} prefix - Directory prefix for file paths.
|
|
* @returns {Promise<Object>} - Promise resolving to a dictionary of file contents keyed by filenames.
|
|
*/
|
|
export async function readFilesIntoDict(pattern, prefix) {
|
|
// Prepend the prefix to the glob pattern
|
|
let globPattern = prefix ? path.join(prefix, pattern) : pattern;
|
|
globPattern = globPattern.replace(/\\/g, "/");
|
|
const files = await glob(globPattern);
|
|
|
|
let fileDict = {};
|
|
let readPromises = files.map((file) => {
|
|
return fs.promises.readFile(file, "utf8").then((content) => {
|
|
// Remove the prefix from the filename and drop the file suffix
|
|
let key = file;
|
|
if (prefix && file.startsWith(prefix)) {
|
|
key = key.slice(prefix.length);
|
|
}
|
|
key = path.basename(key, path.extname(key)); // Drop the file suffix
|
|
|
|
fileDict[key] = content;
|
|
});
|
|
});
|
|
|
|
await Promise.all(readPromises);
|
|
return fileDict;
|
|
}
|
|
|
|
/**
|
|
* Validates the provided authentication credentials.
|
|
* @param {string} token - The authentication token.
|
|
* @param {string} username - The username.
|
|
* @param {string} password - The password.
|
|
* @returns {string|null} - Returns an error message if validation fails, otherwise null.
|
|
*/
|
|
export function validateAuthentication(token, username, password) {
|
|
if (token) {
|
|
if (username || password) {
|
|
return "Token is defined along with username and/or password.";
|
|
}
|
|
} else {
|
|
if (!username && !password) {
|
|
return "Neither token nor password and username are defined.";
|
|
}
|
|
if (username && !password) {
|
|
return "Username is defined but no password is provided.";
|
|
}
|
|
if (!username && password) {
|
|
return "Password is defined but no username is provided.";
|
|
}
|
|
}
|
|
return null; // No errors found
|
|
}
|
|
|
|
/**
|
|
* Applies the 'ignore' | 'warn' | 'fail' enum action when the given flag is true.
|
|
* Exported so it can be unit-tested independently.
|
|
*
|
|
* @param {'ignore'|'warn'|'fail'} action
|
|
* @param {boolean} flag - Only acts when true
|
|
* @param {string} message - Passed to core.warning / core.setFailed
|
|
*/
|
|
export function applyOnAction(action, flag, message) {
|
|
if (!flag) return;
|
|
if (action === "warn") {
|
|
core.warning(message);
|
|
return;
|
|
}
|
|
if (action === "fail") {
|
|
core.setFailed(message);
|
|
}
|
|
// 'ignore' → no-op
|
|
}
|
|
|
|
/**
|
|
* Posts code to Screeps server.
|
|
*/
|
|
export async function postCode() {
|
|
const protocol = core.getInput("protocol") || "https";
|
|
const hostname = core.getInput("hostname") || "screeps.com";
|
|
const port = core.getInput("port") || "443";
|
|
const path = core.getInput("path") || "/";
|
|
|
|
const token = core.getInput("token") || undefined;
|
|
const username = core.getInput("username") || undefined;
|
|
const password = core.getInput("password") || undefined;
|
|
const prefix = core.getInput("source-prefix");
|
|
const pattern = core.getInput("pattern") || "*.js";
|
|
const branch = core.getInput("branch") || "default";
|
|
|
|
const gitReplace = core.getInput("git-replace") || null;
|
|
|
|
if (gitReplace) {
|
|
await readReplaceAndWriteFiles(gitReplace, prefix, hostname);
|
|
}
|
|
|
|
const files_to_push = await readFilesIntoDict(pattern, prefix);
|
|
|
|
core.info(`Trying to upload the following files to ${branch}:`);
|
|
Object.keys(files_to_push).forEach((key) => {
|
|
core.info(`Key: ${key}`);
|
|
});
|
|
|
|
const login_arguments = {
|
|
token: token,
|
|
username: username,
|
|
password: password,
|
|
protocol: protocol,
|
|
hostname: hostname,
|
|
port: port,
|
|
path: path,
|
|
};
|
|
|
|
core.info("login_arguments:");
|
|
core.info(JSON.stringify(login_arguments, null, 2));
|
|
|
|
const errorMessage = validateAuthentication(token, username, password);
|
|
if (errorMessage) {
|
|
core.error(errorMessage);
|
|
return;
|
|
}
|
|
let api = new ScreepsAPI(login_arguments);
|
|
if (token) {
|
|
const response = await api.code.set(branch, files_to_push);
|
|
core.info(JSON.stringify(response, null, 2));
|
|
core.info(`Code set successfully to ${branch}`);
|
|
} else {
|
|
core.info(`Logging in as user ${username}`);
|
|
await Promise.resolve()
|
|
.then(() => api.auth(username, password, login_arguments))
|
|
.then(() => api.code.set(branch, files_to_push))
|
|
.then(() => {
|
|
core.info(`Code set successfully to ${branch}`);
|
|
})
|
|
.catch((err) => {
|
|
core.error(`Upload error: ${err}`);
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
// Console monitoring (optional)
|
|
const monitorTicks = parseInt(core.getInput("monitor") || "0", 10);
|
|
if (monitorTicks > 0) {
|
|
const result = await monitorConsole(api, {
|
|
monitor: monitorTicks,
|
|
logToFile: core.getBooleanInput("log_to_file"),
|
|
onTraceback: core.getInput("on_traceback") || "fail",
|
|
onErrorLog: core.getInput("on_error_log") || "warn",
|
|
onWarningLog: core.getInput("on_warning_log") || "ignore",
|
|
monitorInterval: parseInt(core.getInput("monitor_interval") || "10", 10),
|
|
hostname,
|
|
shard: core.getInput("shard") || undefined,
|
|
});
|
|
|
|
core.setOutput("saw_traceback", String(result.sawTraceback));
|
|
core.setOutput("saw_error_log", String(result.sawErrorLog));
|
|
core.setOutput("saw_warning_log", String(result.sawWarningLog));
|
|
|
|
applyOnAction(
|
|
core.getInput("on_traceback"),
|
|
result.sawTraceback,
|
|
"Screeps console: traceback detected",
|
|
);
|
|
applyOnAction(
|
|
core.getInput("on_error_log"),
|
|
result.sawErrorLog,
|
|
"Screeps console: error log output detected",
|
|
);
|
|
applyOnAction(
|
|
core.getInput("on_warning_log"),
|
|
result.sawWarningLog,
|
|
"Screeps console: warning log output detected",
|
|
);
|
|
}
|
|
}
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
if (process.argv[1] === __filename) {
|
|
postCode();
|
|
}
|