@roboplay/sage
Version:
Codemod for Robo.js
321 lines (319 loc) • 11.8 kB
JavaScript
import { findPackagePath } from 'robo.js/dist/cli/utils/utils.js';
import { Command } from 'commander';
import { logger } from '../core/logger.js';
import { getPackageManager, checkSageUpdates, checkUpdates, IS_WINDOWS, exec } from '../core/utils.js';
import { loadConfig } from 'robo.js/dist/core/config.js';
import { color, composeColors } from '../core/color.js';
import fs from 'node:fs';
import path from 'node:path';
import { Separator, checkbox, select } from '@inquirer/prompts';
import { readFile } from 'node:fs/promises';
import { Flashcore } from 'robo.js';
const command = new Command("upgrade").description("Upgrades your Robo to the latest version").option("-y --yes", "installs updates without showing changelogs").option("-f --force", "forcefully install").option("-ns --no-self-check", "do not check for updates to Sage CLI").option("-s --silent", "do not print anything").option("-v --verbose", "print more information for debugging").action(upgradeAction);
var upgrade_default = command;
async function upgradeAction(options) {
logger({
enabled: !options.silent,
level: options.verbose ? "debug" : "info"
}).info(`Checking for updates...`);
logger.debug(`CLI Options:`, options);
logger.debug(`Package manager:`, getPackageManager());
logger.debug(`Current working directory:`, process.cwd());
if (options.selfCheck) {
await checkSageUpdates(options.yes);
}
const config = await loadConfig();
await Flashcore.$init({ keyvOptions: config.flashcore?.keyv });
const plugins = config.plugins;
plugins.push(["robo.js", {}]);
const packageJsonPath = path.join(process.cwd(), await findPackagePath("robo.js", process.cwd()), "package.json");
logger.debug(`Package JSON path:`, packageJsonPath);
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
logger.debug(`Package JSON:`, packageJson);
const update = await checkUpdates(packageJson, config, true);
logger.debug(`Update payload:`, update);
const autoAccept = options.yes;
await updateRobo(plugins, config, autoAccept);
}
async function getChangelog(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.text();
let currentChangelog = null;
let currentChangeType = null;
for (const line of data.split("\n")) {
const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
if (versionMatch) {
if (currentChangelog)
break;
currentChangelog = { version: versionMatch[1], patch: [], major: [], minor: [] };
continue;
}
const changeTypeMatch = line.match(/^### (Patch|Minor|Major) Changes/);
if (changeTypeMatch) {
currentChangeType = changeTypeMatch[1].toLowerCase();
continue;
}
if (currentChangelog && currentChangeType) {
const changeMatch = line.match(/^- [a-f0-9]+: (.+)/);
if (changeMatch) {
currentChangelog[currentChangeType].push(changeMatch[1]);
}
}
}
return currentChangelog;
} catch (error) {
logger.error("Failed to fetch and process the changelog", error);
return null;
}
}
function printChangelog(changelog) {
logger.log(composeColors(color.bold, color.blue, color.underline)(`Version: ${changelog.version}`));
if (changelog.major.length > 0) {
logger.log(composeColors(color.red, color.bold)("Major Changes:"));
changelog.major.forEach((change) => logger.log(`- ${change}`));
}
if (changelog.minor.length > 0) {
logger.log(composeColors(color.bold, color.yellow)("Minor Changes:"));
changelog.minor.forEach((change) => logger.log(`- ${change}`));
}
if (changelog.patch.length > 0) {
logger.log(composeColors(color.bold, color.green)("Patch Changes:"));
changelog.patch.forEach((change) => logger.log(`- ${change}`));
}
logger.log("\n");
}
let _id = 0;
const CHANGES = {
configDirectory: {
id: ++_id,
name: "Config directory",
description: "The config directory has been renamed from `.config` to `config`."
}
};
async function check(name, version) {
logger.info(`Checking version ${version}...`);
const breaking = [];
const suggestions = [];
if (name !== "robo.js") {
logger.debug(`No changes to check for`, name);
return { breaking, suggestions };
}
if (fs.existsSync(path.join(process.cwd(), ".config"))) {
breaking.push(CHANGES.configDirectory);
}
return { breaking, suggestions };
}
async function execute(changes) {
logger.info(`Applying changes:`, changes.map((change) => change.name).join(", "));
for (const change of changes) {
if (change.id === CHANGES.configDirectory.id) {
logger.debug(`Renaming config directory...`);
fs.renameSync(path.join(process.cwd(), ".config"), path.join(process.cwd(), "config"));
}
}
logger.info(`Successfully applied changes!`);
}
const CustomSeparator = "----- \u{1F389} -----";
async function updateRobo(plugins, config, autoAccept) {
const u_options = [];
const hasUpdate = [];
for (const plugin of plugins) {
const plugingName = IS_WINDOWS ? plugin[0].replaceAll("\\", "/") : plugin[0];
const packagePath = await findPackagePath(plugingName, process.cwd());
logger.debug("Checking updates for", color.bold(plugingName), "at path", color.bold(packagePath));
const packageJsonPath = path.join(packagePath, "package.json");
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
const update = await checkUpdates(packageJson, config, true);
logger.debug(`Update payload for ${plugingName}:`, update);
const pluginOnRegistry = await fetch(`https://registry.npmjs.org/${packageJson.name}/latest`);
if (!pluginOnRegistry.ok) {
logger.info(
composeColors(
color.yellowBright,
color.bold
)(`Skipping ${plugingName}, not found on registry, probably local plugin!`)
);
continue;
}
if (!update.hasUpdate) {
logger.info(composeColors(color.green, color.bold)(`${plugingName} is up to date!`));
continue;
}
if (autoAccept && update.hasUpdate) {
hasUpdate.push({ data: { name: plugingName, extra: { ...update, name: "" } } });
continue;
}
const upgradeOptions = [
{ name: plugingName, value: JSON.stringify({ data: { name: plugingName, extra: update } }), short: "pl" }
];
if (update.changelogUrl) {
upgradeOptions.splice(1, 0, {
name: "Read changelog",
value: JSON.stringify({
...update,
name: plugingName
}),
short: "cl"
});
hasUpdate.push(
JSON.stringify({
...update,
name: plugingName
})
);
}
u_options.push(new Separator(plugingName));
u_options.push(...upgradeOptions);
logger.info(
composeColors(color.green, color.bold)(`A new version of ${plugingName} is available!`),
color.dim(`(v${update.currentVersion} -> v${update.latestVersion})`)
);
logger.log("");
}
if (autoAccept) {
await upgradeSelectedPlugins(hasUpdate);
logger.info(composeColors(color.green, color.bold)(`Your Robo is up to date!`));
return;
}
if (u_options.length > 0) {
await showListOfPlugins(u_options, hasUpdate);
} else {
logger.info(composeColors(color.green, color.bold)(`Your Robo is up to date!`));
}
}
async function showListOfPlugins(u_options, hasUpdate) {
const selectedPlugins = await checkbox(
{
message: "Select plugins that you want to update:",
choices: u_options.filter((option) => option instanceof Separator === false && option.short !== "cl"),
loop: false
},
{
clearPromptOnDone: false
}
);
if (selectedPlugins.length > 0) {
u_options.push(new Separator(CustomSeparator));
u_options.push({ name: "Proceed update", value: "update" });
u_options.push({ name: "Cancel", value: "abort" });
await showChangelogList(selectedPlugins, u_options, hasUpdate);
}
}
async function showChangelogList(pluginData, u_options, hasUpdate) {
console.clear();
const pluginNames = pluginData.map((plugin) => {
const parsed = JSON.parse(plugin);
if (isValidPlugin(parsed)) {
return parsed.data.name;
}
});
const selectedChangelog = await select(
{
message: "See the change logs for the plug-ins you selected or proceed with the upgrade",
choices: u_options.filter((option) => {
if (option instanceof Separator) {
if (option.separator === CustomSeparator) {
return option;
}
if (pluginNames.includes(option.separator)) {
return {
...option,
separate: option.separator + ":"
};
}
}
if (option instanceof Separator === false) {
if (option.value === "abort" || option.value === "update") {
return option;
}
if (option.short === "cl") {
const value = JSON.parse(option.value);
if (pluginNames.includes(value.name)) {
return option;
}
}
}
}),
loop: false
},
{
clearPromptOnDone: false
}
);
if (typeof selectedChangelog === "string" && hasUpdate.includes(selectedChangelog)) {
const JSONParseChangeLog = JSON.parse(selectedChangelog);
const changelog = await getChangelog(JSONParseChangeLog.changelogUrl);
printChangelog(changelog);
const upgrade = await select(
{
message: ``,
choices: [{ name: "back", value: false }],
loop: false
},
{
clearPromptOnDone: true
}
);
logger.log("");
if (!upgrade) {
await showChangelogList(pluginData, u_options, hasUpdate);
return;
}
}
if (selectedChangelog === "update") {
if (Array.isArray(pluginData)) {
const map = pluginData.map((plugin) => {
if (typeof plugin === "string") {
const parsed = JSON.parse(plugin);
if (isValidPlugin(parsed))
return parsed;
}
});
await upgradeSelectedPlugins(map);
return;
}
logger.error("An error happened while treating the data...");
return;
}
if (selectedChangelog === "abort") {
logger.info("Aborting plugin upgrade!");
return;
}
}
async function upgradeSelectedPlugins(selectedPlugins, autoAccept) {
const packageManager = getPackageManager();
const command2 = packageManager === "npm" ? "install" : "add";
logger.debug(`Package manager:`, packageManager);
const pluginStringFromArray = selectedPlugins.map((plugin) => `${plugin.data.name}@${plugin.data.extra.latestVersion}`).join(" ");
await exec(`${packageManager} ${command2} ${pluginStringFromArray}`);
for (const plugin of selectedPlugins) {
const { extra, name } = plugin.data;
const data = await check(name, extra.latestVersion);
logger.debug(`Check data:`, data);
if (data.breaking.length > 0 || data.suggestions.length > 0 && !autoAccept) {
const changes = await checkbox({
message: "Which changes would you like to apply?",
choices: [
...data.breaking.map((change) => ({ name: change.name, value: change })),
new Separator(),
...data.suggestions.map((change) => ({ name: change.name, value: change }))
]
});
logger.log("");
await execute(changes);
}
logger.ready(`Successfully upgraded ${name} to v${extra.latestVersion}! \u{1F389}`);
}
}
function isValidPlugin(plugin) {
if (plugin?.data !== void 0) {
return true;
}
}
export { upgrade_default as default };
//# sourceMappingURL=out.js.map
//# sourceMappingURL=upgrade.js.map