Skip to content

Commit

Permalink
feat(cli): make cli actually be able to update
Browse files Browse the repository at this point in the history
  • Loading branch information
meisZWFLZ committed Dec 1, 2023
1 parent bf3bdc6 commit f848179
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 20 deletions.
20 changes: 20 additions & 0 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import yargs from "yargs";
import { standalone } from "../../config.json";
import { SelfUpdateUtility } from "./update";

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);
},
);
100 changes: 80 additions & 20 deletions src/cli/update.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,90 @@
#!/usr/bin/env ts-node

import { renameSync, unlinkSync, createWriteStream } from "fs";
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";
import { standalone } from "../../config.json";

/**
* self updates the cli executable
* @warning DO NOT USE THIS IF NOT PART OF STANDALONE EXECUTABLE
*/
export async function updateCLI(downloadUrl: string): Promise<void> {
if (!standalone)
throw new Error(
"LemLink is not part of a standalone executable, and thus cannot self update",
);
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;
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const filePath = process.env._!;
const newPath = filePath + "-new";
const oldPath = filePath + "-old";
try {
console.log("attempting to delete old file");
unlinkSync(oldPath);
} catch (err) {
/**
* 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(newPath);
const fileStream = createWriteStream(this.newPath);

const response = await new Promise<IncomingMessage>((resolve) =>
httpGet(downloadUrl, resolve),
Expand All @@ -37,8 +97,8 @@ export async function updateCLI(downloadUrl: string): Promise<void> {
console.log("File downloaded successfully.");

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

console.log("Restarting...");
Expand Down

0 comments on commit f848179

Please sign in to comment.