Skip to content
This repository has been archived by the owner on Dec 13, 2024. It is now read-only.

Commit

Permalink
feat: ✨ Add self updating for standalone executable
Browse files Browse the repository at this point in the history
Copied from eea905e3fe9a7b1507af0fac27939e73417db76a of meiszwflz/LemLink
  • Loading branch information
meisZWFLZ committed Dec 5, 2023
1 parent 5c11fb6 commit e75750c
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 7 deletions.
3 changes: 3 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"standalone": true
}
25 changes: 19 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
var argv = require('yargs/yargs')(process.argv.slice(2)).argv;
import yargs from "yargs";
import { standalone } from "../config.json";
import { SelfUpdateUtility } from "./update";

if (argv.ships > 3 && argv.distance < 53.5) {
console.log('Plunder more riffiwobbles!');
} else {
console.log('Retreat from the xupptumblers!');
}
if (standalone) SelfUpdateUtility.init();

yargs(process.argv.slice(2)).command(
"update",
"update",
{
url: {
alias: "u",
describe: "the URL to download the new executable from",
},
},
async (argv) => {
if (typeof argv.url === "string")
await SelfUpdateUtility.get().updateCLI(argv.url);
},
);
117 changes: 117 additions & 0 deletions src/update.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { renameSync, unlinkSync, createWriteStream, existsSync } from "fs";
import child_process = require("child_process");
import { get as httpGet, type IncomingMessage } from "http";
import { standalone } from "../config.json";

/**
* self updates the cli executable
* @warning DO NOT USE THIS IF NOT PART OF STANDALONE EXECUTABLE
*/
export class SelfUpdateUtility {
/**
* So how the heck does this CLI update itself?
*
* 1. Attempt to download new executable from downloadUrl to newPath
* 2. Rename current executable to oldPath
* 3. Rename newly downloaded executable to filePath
* 4. On startup of the cli, we attempt to remove oldPath
*/

protected readonly filePath: string; // normal executable path
protected readonly newPath: string; // the new executable that will be downloaded
protected readonly oldPath: string;

private static singleton?: SelfUpdateUtility;
/**
* self updates the cli executable
* @warning DO NOT USE THIS IF NOT PART OF STANDALONE EXECUTABLE
*/
private constructor() {
if (!standalone)
throw new Error(
"LemLink is not part of a standalone executable, and thus cannot self update",
);
const nodeExecutablePath = process.env._;
if (nodeExecutablePath === undefined) {
throw new Error("Could not find node executable path");
}

this.filePath = nodeExecutablePath;
this.newPath = this.filePath + "-new";
this.oldPath = this.filePath + "-old";

this.attemptToDeleteOldPathFile();
}

/**
* Should be run at the start of CLI program
*/
public static init(): void {
if (this.singleton !== undefined) {
console.error(
"Self Update Utility has already been initialized, canceling init..",
);
return;
}
this.singleton = new SelfUpdateUtility();
}

public static get(): SelfUpdateUtility {
if (this.singleton === undefined)
throw new Error(
"Self Update Utility was not initialized at the start of CLI program!!",
);
return this.singleton;
}

/**
* @returns true if oldPath exists, false if otherwise
*/
private attemptToDeleteOldPathFile(): boolean {
if (existsSync(this.oldPath)) {
unlinkSync(this.oldPath);
return true;
}
return false;
}

/**
* Replaces the current CLI executable with the one found at {@link downloadUrl}
* @param downloadUrl url from which a new executable should be downloaded
*/
public async updateCLI(downloadUrl: string): Promise<void> {
/**
* See top of this file for info on how this works
*/
console.log("Downloading new file...");
const fileStream = createWriteStream(this.newPath);

const response = await new Promise<IncomingMessage>((resolve) =>
httpGet(downloadUrl, resolve),
);

response.pipe(fileStream);
await new Promise((resolve) => fileStream.on("finish", resolve));
fileStream.close();

console.log("File downloaded successfully.");

console.log("Replacing current file...");
renameSync(this.filePath, this.oldPath);
renameSync(this.newPath, this.filePath);
console.log("Replaced current file successfully!");

console.log("Restarting...");
child_process.spawn(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
process.argv.shift()!,
process.argv,
{
cwd: process.cwd(),
detached: true,
stdio: "inherit",
shell: true,
},
);
}
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

Expand Down

0 comments on commit e75750c

Please sign in to comment.