Skip to content

Commit

Permalink
fix(cli): init command target and installation progress (codemod-com#…
Browse files Browse the repository at this point in the history
  • Loading branch information
r4zendev authored Jul 25, 2024
1 parent 042437a commit 0d9d834
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 95 deletions.
70 changes: 45 additions & 25 deletions apps/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { randomBytes } from "node:crypto";
import fs from "node:fs";
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
import {
constants,
access,
mkdir,
readFile,
unlink,
writeFile,
} from "node:fs/promises";
import { basename, dirname, join } from "node:path";
import inquirer from "inquirer";
import terminalLink from "terminal-link";
Expand All @@ -17,6 +23,7 @@ import {
parseCodemodConfig,
} from "@codemod-com/utilities";
import { getCurrentUserData } from "#auth-utils.js";
import { isFile, oraCheckmark } from "#utils.js";

const CODEMOD_ENGINE_CHOICES: (KnownEngines | "recipe")[] = [
"jscodeshift",
Expand All @@ -30,27 +37,24 @@ const CODEMOD_ENGINE_CHOICES: (KnownEngines | "recipe")[] = [
export const handleInitCliCommand = async (options: {
printer: Printer;
target: string;
noPrompt?: boolean;
source?: string;
noLogs?: boolean;
writeDirectory?: string | null;
useDefaultName?: boolean;
}) => {
const {
printer,
target,
source,
useDefaultName = false,
writeDirectory = null,
noLogs = false,
target,
} = options;

if (!fs.existsSync(target)) {
throw new Error(`Target path ${target} does not exist.`);
if (source) {
await access(source).catch(() => {
throw new Error(`Source path ${source} does not exist.`);
});
}

const isTargetAFile = await fs.promises
.lstat(target)
.then((pathStat) => pathStat.isFile());

const userData = await getCurrentUserData();

const defaultInput = {
Expand All @@ -61,19 +65,23 @@ export const handleInitCliCommand = async (options: {
} as const;

let engineChoices: string[];
if (isJavaScriptName(basename(target))) {
if (!source) {
engineChoices = CODEMOD_ENGINE_CHOICES;
} else if (isJavaScriptName(basename(source))) {
engineChoices = ["jscodeshift", "ts-morph", "filemod", "workflow"];
} else if (basename(target) === ".codemodrc.json") {
} else if (basename(source) === ".codemodrc.json") {
engineChoices = ["recipe"];
} else if (
basename(target).endsWith(".yaml") ||
basename(target).endsWith(".yml")
basename(source).endsWith(".yaml") ||
basename(source).endsWith(".yml")
) {
engineChoices = ["ast-grep"];
} else {
engineChoices = CODEMOD_ENGINE_CHOICES;
}

const isSourceAFile = source ? await isFile(source) : false;

const userAnswers = await inquirer.prompt<{
name: string;
engine: KnownEngines;
Expand All @@ -88,7 +96,7 @@ export const handleInitCliCommand = async (options: {
{
type: "list",
name: "engine",
message: `Select a codemod engine ${isTargetAFile ? "your codemod is built with" : "you want to build your codemod with"}:`,
message: `Select a codemod engine ${isSourceAFile ? "your codemod is built with" : "you want to build your codemod with"}:`,
pageSize: engineChoices.length,
choices: engineChoices,
},
Expand All @@ -99,11 +107,11 @@ export const handleInitCliCommand = async (options: {
...userAnswers,
};

if (isTargetAFile) {
if (source && isSourceAFile) {
if (downloadInput.engine === "recipe") {
try {
downloadInput.codemodRcBody = parseCodemodConfig(
await readFile(target, "utf-8"),
await readFile(source, "utf-8"),
);
} catch (err) {
throw new Error(
Expand All @@ -117,14 +125,14 @@ export const handleInitCliCommand = async (options: {
}
}
// Can be read because we handle this error at the start
downloadInput.codemodBody = await readFile(target, "utf-8");
downloadInput.codemodBody = await readFile(source, "utf-8");
}

const files = getCodemodProjectFiles(downloadInput);

const codemodBaseDir = join(
writeDirectory ?? process.cwd(),
downloadInput.name,
const codemodBaseDir = join(target ?? process.cwd(), downloadInput.name);
await access(codemodBaseDir, constants.F_OK).catch(() =>
mkdir(codemodBaseDir, { recursive: true }),
);

const created: string[] = [];
Expand All @@ -133,10 +141,10 @@ export const handleInitCliCommand = async (options: {

try {
await mkdir(dirname(filePath), { recursive: true });
if (!fs.existsSync(filePath)) {
await access(filePath, constants.F_OK).catch(async () => {
await writeFile(filePath, content);
created.push(path);
}
});
} catch (err) {
for (const createdPath of created) {
try {
Expand Down Expand Up @@ -166,20 +174,32 @@ export const handleInitCliCommand = async (options: {
chalk.cyan("Codemod package created at", `${chalk.bold(codemodBaseDir)}.`),
);

const installSpinner = printer.withLoaderMessage(
chalk.cyan("Installing npm dependencies..."),
);

// Install packages
try {
await execPromise("pnpm i", { cwd: codemodBaseDir });
} catch (err) {
try {
await execPromise("npm i", { cwd: codemodBaseDir });
} catch (err) {
installSpinner.fail();
printer.printConsoleMessage(
"error",
`Failed to install npm dependencies:\n${(err as Error).message}.`,
);
}
}

if (installSpinner.isSpinning) {
installSpinner.stopAndPersist({
symbol: oraCheckmark,
text: chalk.green("Dependencies installed."),
});
}

const howToRunText = chalk(
`Run ${chalk.bold(doubleQuotify(`codemod --source ${codemodBaseDir}`))}`,
"to run the codemod on current working directory",
Expand Down
12 changes: 4 additions & 8 deletions apps/cli/src/commands/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { extractPrintableApiError, getCodemod, publish } from "#api.js";
import { getCurrentUserOrLogin } from "#auth-utils.js";
import { handleInitCliCommand } from "#commands/init.js";
import type { TelemetryEvent } from "#telemetry.js";
import { codemodDirectoryPath } from "#utils.js";
import { codemodDirectoryPath, isFile } from "#utils.js";

export const handlePublishCliCommand = async (options: {
printer: Printer;
Expand All @@ -48,15 +48,11 @@ export const handlePublishCliCommand = async (options: {
"**/.gitignore",
];

const isSourceAFile = await fs.promises
.lstat(source)
.then((pathStat) => pathStat.isFile());

if (isSourceAFile) {
if (await isFile(source)) {
source = await handleInitCliCommand({
printer,
target: source,
writeDirectory: join(codemodDirectoryPath, "temp"),
source,
target: join(codemodDirectoryPath, "temp"),
noLogs: true,
});

Expand Down
4 changes: 2 additions & 2 deletions apps/cli/src/fetch-codemod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ export const fetchCodemod = async (options: {
// Standalone codemod
const codemodPackagePath = await handleInitCliCommand({
printer,
target: nameOrPath,
writeDirectory: join(codemodDirectoryPath, "temp"),
source: nameOrPath,
target: join(codemodDirectoryPath, "temp"),
useDefaultName: true,
noLogs: true,
});
Expand Down
21 changes: 7 additions & 14 deletions apps/cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,27 +310,20 @@ export const main = async () => {
"init",
"initialize a codemod package",
(y) =>
y
.option("target", {
alias: "t",
type: "string",
description: "Path to init codemod in",
default: process.cwd(),
})
.option("no-prompt", {
alias: "y",
type: "boolean",
description: "skip all prompts and use default values",
}),
y.option("target", {
alias: "t",
type: "string",
description: "Path to init codemod in",
default: process.cwd(),
}),
async (args) => {
const { executeCliCommand, printer } =
await initializeDependencies(args);

return executeCliCommand(() =>
handleInitCliCommand({
printer,
noPrompt: args.noPrompt,
target: args.target,
target: args.target ?? process.cwd(),
}),
);
},
Expand Down
6 changes: 6 additions & 0 deletions apps/cli/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { version } from "#/../package.json";

export const codemodDirectoryPath = join(os.homedir(), ".codemod");

export const isFile = async (path: string) =>
fs.promises
.lstat(path)
.then((pathStat) => pathStat.isFile())
.catch(() => false);

export const unpackZipCodemod = async (options: {
source: string;
target: string;
Expand Down
Loading

0 comments on commit 0d9d834

Please sign in to comment.