diff --git a/tools/cli/.gitignore b/tools/cli/.gitignore index 869363fb..1ae37bbe 100644 --- a/tools/cli/.gitignore +++ b/tools/cli/.gitignore @@ -1,3 +1,6 @@ # Ignore output files oclif.manifest.json -tsconfig.tsbuildinfo \ No newline at end of file +tsconfig.tsbuildinfo + +# Ignore build folders +tmp/ \ No newline at end of file diff --git a/tools/cli/src/commands/dev/index.ts b/tools/cli/src/commands/dev/index.ts index a99104b1..b4b27f22 100644 --- a/tools/cli/src/commands/dev/index.ts +++ b/tools/cli/src/commands/dev/index.ts @@ -11,9 +11,9 @@ import { Args, Command, Flags } from "@oclif/core"; import { expandHomeDir, isRunnable } from "../../util/index.js"; import BuildCommand from "../build/index.js"; import path from "path"; -import { copyFileSync, existsSync, readdirSync, readFileSync, watch as watchFolder } from "fs"; +import { copyFileSync, existsSync, fstat, readdirSync, readFileSync, watch as watchFolder, writeFileSync } from "fs"; import chalk from "chalk"; -import { execSync, spawn, spawnSync } from "child_process"; +import { execSync, spawnSync } from "child_process"; import os from "node:os"; export default class Run extends Command { @@ -64,6 +64,10 @@ export default class Run extends Command { hidden: false, required: false, default: getLatestRuntime() + }), + legacy: Flags.boolean({ + description: "Run if you want a pre-modus release", + default: false }) }; @@ -73,8 +77,8 @@ export default class Run extends Command { async run(): Promise { const { args, flags } = await this.parse(Run); - const isDev = flags.runtime && (flags.runtime.startsWith("dev-") || flags.runtime.startsWith("link")); - const runtimePath = path.join(expandHomeDir("~/.modus/sdk/" + flags.runtime), (isDev ? "/runtime" : "/runtime" + (os.platform() === "win32" ? ".exe" : ""))); + const isDev = flags.legacy || flags.runtime && (flags.runtime.startsWith("dev-") || flags.runtime.startsWith("link")); + const runtimePath = path.join(expandHomeDir("~/.modus/sdk/" + (isDev ? "" : "v") + flags.runtime), flags.legacy ? "" : (isDev ? "/runtime" : "/runtime" + (os.platform() === "win32" ? ".exe" : ""))); const cwd = path.join(process.cwd(), args.path); const watch = flags.watch; @@ -111,7 +115,7 @@ export default class Run extends Command { } if (!existsSync(runtimePath)) { - this.logError("Modus Runtime " + (isDev ? "" : "v") + flags.runtime + " not installed!\n Run `modus sdk install " + install_cmd + "` and try again!"); + this.logError("Modus Runtime " + runtimePath + " " + (isDev ? "" : "v") + flags.runtime + " not installed!\n Run `modus sdk install v" + install_cmd + "` and try again!"); process.exit(0); } @@ -134,15 +138,27 @@ export default class Run extends Command { if (!isRunnable("go")) { this.logError("Cannot find any valid versions of Go! Please install go") } - execSync("go run ./tools/generate_version", { - cwd: runtimePath, - stdio: "ignore", - env: { - ...process.env, - MODUS_ENV: "dev", - MODUS_BUILD_VERSION: "modus-cli/" + flags.runtime - } - }); + if (flags.legacy) { + writeFileSync(path.join(runtimePath, "config/version.go"), `package config + +func GetProductVersion() string { + return "Hypermode Runtime " + GetVersionNumber() +} + +func GetVersionNumber() string { + return "modus-cli/${flags.runtime}" +}`); + } else { + execSync("go run ./tools/generate_version", { + cwd: runtimePath, + stdio: "ignore", + env: { + ...process.env, + MODUS_ENV: "dev", + MODUS_BUILD_VERSION: "modus-cli/" + flags.runtime + } + }); + } execSync("go run .", { cwd: runtimePath, diff --git a/tools/cli/src/commands/sdk/install/index.ts b/tools/cli/src/commands/sdk/install/index.ts index 6036c8c1..a2e56853 100644 --- a/tools/cli/src/commands/sdk/install/index.ts +++ b/tools/cli/src/commands/sdk/install/index.ts @@ -32,11 +32,22 @@ export default class SDKInstallCommand extends Command { static flags = { silent: Flags.boolean({ + char: "s", description: "Suppress output logs", hidden: false, required: false }), // These are meant for developers working on modus itself + // You can link a directory as your runtime + // You can also run a specific branch + // You can also run a specific branch and commit on that branch + repo: Flags.string({ + hidden: true, + char: "r", + description: "Change the GitHub owner and repo eg. hypermodeinc/modus", + required: false, + default: "hypermodeinc/modus" + }), branch: Flags.string({ hidden: true, char: "b", @@ -60,61 +71,108 @@ export default class SDKInstallCommand extends Command { async run(): Promise { const ctx = await this.parse(SDKInstallCommand); - if (ctx.flags.branch) this.installCommit(ctx); - else if (ctx.flags.link) this.linkDir(ctx); + if (ctx.args.version) await this.installVersion(ctx); + else if (ctx.flags.branch) await this.installCommit(ctx); + else if (ctx.flags.link) await this.linkDir(ctx); } - async installCommit(ctx: ParserCtx) { + async installVersion(ctx: ParserCtx) { const { args, flags } = ctx; + let version = args.version?.toLowerCase(); + const release_info = await fetchRelease(flags.repo, version!); + if (version == "latest") { + this.log("[1/4] Getting latest version"); + version = release_info["tag_name"]; + clearLine(); + this.log("[1/4] Latest version: " + chalk.dim(version)); + } else { + this.log("[1/4] Getting release info") + } + + version = version?.replace("v", ""); + + this.log("[2/4] Downloading release"); + const extension = os.platform() == "win32" ? ".zip" : ".tar.gz"; + const filename = "v" + version + extension; + const downloadLink = "https://github.com/" + flags.repo + "/archive/refs/tags/" + filename; + + const archiveName = ("modus-" + version + extension).replaceAll("/", "-"); + const tempDir = expandHomeDir("~/.modus/.modus-temp"); + const archivePath = path.join(tempDir, archiveName); + + mkdirSync(tempDir, { recursive: true }); + await downloadFile(downloadLink, archivePath); + + clearLine(); + this.log("[2/4] Downloaded release"); + + const unpackedDir = tempDir + "/" + archiveName.replace(extension, ""); + this.log("[3/4] Unpacking release" + "tar -xf " + archivePath + " -C " + unpackedDir); + await rm(unpackedDir, { recursive: true, force: true }); + mkdirSync(unpackedDir, { recursive: true }) + execSync("tar -xf " + archivePath + " -C " + unpackedDir); + + clearLine(); + this.log("[3/4] Unpacked release"); + + this.log("[4/4] Building"); + const installDir = expandHomeDir("~/.modus/sdk/" + version) + "/"; + cpSync(unpackedDir + "/modus-" + version?.replace("v", "") + "/", installDir, { recursive: true, force: true }); + clearLine(); + + await rm(tempDir, { recursive: true, force: true }); + this.log("[4/4] Successfully installed Modus " + version) + } + + async installCommit(ctx: ParserCtx) { + const { flags } = ctx; flags.commit = flags.commit.slice(0, 7); let sha = ""; - if (!flags.commit) { - flags.commit = "latest"; - this.log("[1/4] Getting latest commit"); - } else { - const commit_info = await fetchCommit("hypermodeinc", "modus", flags.commit); - sha = commit_info.sha; - this.log("[1/4] Getting commit " + flags.commit); - } - let { id, branch } = await get_branch_info("hypermodeinc", "modus", "main"); + + const commit_info = await fetchCommit(flags.repo, flags.commit); + sha = commit_info.sha; + this.log("[1/4] Getting commit " + flags.commit); + let { id, branch } = await fetchBranch(flags.repo, "main"); if (sha) id = sha.slice(0, 7); const version = branch + "/" + id; clearLine(); this.log("[1/4] Found latest commit"); this.log("[2/4] Fetching Modus from latest commit" + " " + chalk.dim("(" + version + ")")); - const downloadLink = flags.commit == "latest" ? "https://github.com/hypermodeinc/modus/archive/refs/heads/" + branch + ".zip" : "https://github.com/hypermodeinc/modus/archive/" + sha + ".zip"; + const downloadLink = flags.commit == "latest" ? "https://github.com/" + flags.repo + "/archive/refs/heads/" + branch + ".zip" : "https://github.com/" + flags.repo + "/archive/" + sha + ".zip"; const archiveName = ("modus-" + version + ".zip").replaceAll("/", "-"); const tempDir = expandHomeDir("~/.modus/.modus-temp"); - await downloadFile(downloadLink, archiveName); - //clearLine(); + const archivePath = path.join(tempDir, archiveName); + mkdirSync(tempDir, { recursive: true }); + + await downloadFile(downloadLink, archivePath); + clearLine(); this.log("[2/4] Fetched Modus"); this.log("[3/4] Unpacking archive"); - mkdirSync(tempDir, { recursive: true }); const unpackedDir = tempDir + "/" + archiveName.replace(".zip", ""); await rm(unpackedDir, { recursive: true, force: true }); + mkdirSync(unpackedDir, { recursive: true }); if (os.platform() === "win32") { - execSync("tar -xf " + archiveName + " -C " + unpackedDir); + execSync("tar -xf " + archivePath + " -C " + unpackedDir); } else { - execSync("unzip " + archiveName + " -d " + unpackedDir); + execSync("unzip " + archivePath + " -d " + unpackedDir); } clearLine(); this.log("[3/4] Unpacked archive"); - rmSync(archiveName); this.log("[4/4] Installing"); const installDir = expandHomeDir("~/.modus/sdk/dev-" + branch + "-" + id) + "/"; cpSync(unpackedDir + "/modus-" + (flags.commit == "latest" ? branch : sha) + "/", installDir, { recursive: true, force: true }); clearLine(); - + await rm(tempDir, { recursive: true, force: true }); this.log("[4/4] Successfully installed Modus " + version) } async linkDir(ctx: ParserCtx) { - const { args, flags } = ctx; + const { flags } = ctx; const srcDir = path.join(process.cwd(), flags.link!); if (!existsSync(srcDir)) { @@ -133,7 +191,7 @@ export default class SDKInstallCommand extends Command { if (!(await this.confirmAction(rl, "[2/4] Continue? [y/n]"))) { process.exit(0); } - + linkDirectories(srcDir, installDir) clearLine(); clearLine(); @@ -154,8 +212,10 @@ export default class SDKInstallCommand extends Command { } } -async function get_latest_release(): Promise { - const res = (await (await fetch("https://api.github.com/repos/hypermodeinc/modus/releases/latest")).json()); +async function fetchRelease(repo: string, version: string): Promise<{ + tag_name: string +}> { + const res = (await (await fetch("https://api.github.com/repos/" + repo + "/releases/" + version)).json()); if (!res) { console.log(chalk.red(" ERROR ") + chalk.dim(": Could not find latest release! Please check your internet connection and try again.")); process.exit(0); @@ -163,12 +223,12 @@ async function get_latest_release(): Promise { return res; } -async function get_branch_info(owner: string, repo: string, branch: string): Promise<{ +async function fetchBranch(repo: string, branch: string): Promise<{ sha: string, id: string, branch: string }> { - const res = (await (await fetch(`https://api.github.com/repos/${owner}/${repo}/branches/${branch}`)).json()); + const res = (await (await fetch(`https://api.github.com/repos/${repo}/branches/${branch}`)).json()); if (!res) { console.log(chalk.red(" ERROR ") + chalk.dim(": Could not find branch! Please check your internet connection and try again.")); process.exit(0); @@ -180,10 +240,10 @@ async function get_branch_info(owner: string, repo: string, branch: string): Pro } } -async function fetchCommit(owner: string, repo: string, commitSha: string): Promise<{ +async function fetchCommit(repo: string, commitSha: string): Promise<{ sha: string }> { - const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/commits/${commitSha}`); + const response = await fetch(`https://api.github.com/repos/${repo}/commits/${commitSha}`); if (!response.ok) { console.log(chalk.red(" ERROR ") + chalk.dim(": Could not find commit! Please check your internet connection and try again.")); process.exit(0);