screeps-deploy-action/index.js

209 lines
7.1 KiB
JavaScript

const { ScreepsAPI } = require("screeps-api");
const core = require("@actions/core");
const fs = require("fs");
const glob = require("glob");
const path = require("path");
/**
* 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.
*/
function replacePlaceholders(content) {
const deployTime = new Date().toISOString();
content = content
.replace(/{{gitHash}}/g, process.env.GITHUB_SHA)
.replace(/{{gitRef}}/g, process.env.GITHUB_REF)
.replace(/{{deployTime}}/g, deployTime);
core.info(content);
return content;
}
/**
* 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.
*/
async function readReplaceAndWriteFiles(pattern, prefix) {
return new Promise((resolve, reject) => {
const globPattern = prefix ? path.join(prefix, pattern) : pattern;
glob(globPattern, async (err, files) => {
if (err) {
return reject(err);
}
let processPromises = [];
files.forEach((file) => {
let processPromise = fs.promises
.readFile(file, "utf8")
.then((content) => {
content = replacePlaceholders(content, replacements);
return fs.promises.writeFile(file, content);
});
processPromises.push(processPromise);
});
try {
await Promise.all(processPromises);
resolve(files);
} catch (processError) {
reject(processError);
}
});
});
}
/**
* 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.
*/
function readFilesIntoDict(pattern, prefix) {
// Prepend the prefix to the glob pattern
const globPattern = prefix ? path.join(prefix, pattern) : pattern;
return new Promise((resolve, reject) => {
glob(globPattern, (err, files) => {
if (err) {
return reject(err);
}
let fileDict = {};
let readPromises = [];
files.forEach((file) => {
let readPromise = 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;
});
readPromises.push(readPromise);
});
// Use Promise.all to ensure all files are read before resolving
Promise.all(readPromises)
.then(() => resolve(fileDict))
.catch(reject);
});
});
}
/**
* 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.
*/
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
}
/**
* Posts code to Screeps server.
*/
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);
}
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));
console.log(`Code set successfully to ${branch}`);
} else {
let response;
core.info(`Logging in as user ${username}`);
response = await Promise.resolve()
.then(() => api.auth(username, password, login_arguments))
.then(() => {
api.code.set(branch, files_to_push);
})
.then(() => {
console.log(`Code set successfully to ${branch}`);
})
.catch((err) => {
console.error("Error:", err);
});
}
}
postCode();