Skip to content

Commit

Permalink
feat(cli): add flag ai removal command
Browse files Browse the repository at this point in the history
  • Loading branch information
cstrnt committed Jul 29, 2024
1 parent 40055b1 commit 5e950f9
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .changeset/ninety-countries-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tryabby/core": patch
"@tryabby/cli": patch
---

add feature flag removal command
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"dotenv": "^16.0.3",
"esbuild": "0.18.17",
"figlet": "^1.6.0",
"globby": "^14.0.2",
"magicast": "^0.3.2",
"msw": "^1.2.2",
"node-fetch": "^3.3.1",
Expand Down
58 changes: 58 additions & 0 deletions packages/cli/src/ai.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { loadLocalConfig } from "./util";
import { globby } from "globby";
import { getUseFeatureFlagRegex } from "@tryabby/core";
import { readFile } from "fs/promises";
import chalk from "chalk";
import { HttpService } from "./http";

export async function removeFlagInstance(options: {
flagName: string;
apiKey: string;
path: string;
host?: string;
configPath?: string;
}) {
const files = await globby("**/*.tsx", {
cwd: options.path ?? process.cwd(),
absolute: true,
gitignore: true,
onlyFiles: true,
});

const regex = getUseFeatureFlagRegex(options.flagName);

const filesToUse = (
await Promise.all(
files.flatMap(async (filePath) => {
const content = await readFile(filePath, "utf-8").then((content) => {
const matches = content.match(regex);
return matches ? content : null;
});
if (!content) return [];

return {
filePath,
fileContent: content,
};
})
)
).flat();

await HttpService.getFilesWithFlagsRemoved({
apiKey: options.apiKey,
files: filesToUse,
flagName: options.flagName,
apiUrl: options.host,
});

try {
const { mutableConfig, saveMutableConfig } = await loadLocalConfig(
options.configPath
);
mutableConfig.flags = mutableConfig.flags.filter((flag: string) => flag !== options.flagName);
await saveMutableConfig();
} catch (e) {
// fail silently
}
console.log(chalk.green("Flag removed successfully"));
}
51 changes: 51 additions & 0 deletions packages/cli/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ABBY_BASE_URL } from "./consts";
import fetch from "node-fetch";
import { multiLineLog } from "./util";
import chalk from "chalk";
import { writeFile } from "fs/promises";

export abstract class HttpService {
static async getConfigFromServer({
Expand Down Expand Up @@ -69,4 +70,54 @@ export abstract class HttpService {
throw e;
}
}

static async getFilesWithFlagsRemoved({
apiKey,
files,
flagName,
apiUrl,
}: {
apiKey: string;
files: Array<{ filePath: string; fileContent: string }>;
flagName: string;
apiUrl?: string;
}) {
const url = apiUrl ?? ABBY_BASE_URL;

try {
const response = await fetch(`${url}/api/ee/v1/abby-ai/flag-removal`, {
method: "POST",
headers: {
Authorization: "Bearer " + apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
flagName,
files,
}),
});

const status = response.status;

if (status === 200) {
const res = await response.json();
if (!Array.isArray(res)) {
throw new Error("Invalid response from server");
}
console.log({ res, files });
await Promise.all(res.map((file) => writeFile(file.filePath, file.fileContent)));
console.log(chalk.green("All files have been updated successfully"));
} else if (status === 500) {
throw new Error("Internal server error trying to update files");
} else if (status === 401) {
throw new Error("Invalid API Key");
} else {
console.log(response);
throw new Error("Push failed");
}
} catch (e) {
console.log(chalk.red(multiLineLog("Error: " + e)));
throw e;
}
}
}
21 changes: 21 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { initAbbyConfig } from "./init";
import { addCommandTypeSchema } from "./schemas";
import { addFlag } from "./add-flag";
import { addRemoteConfig } from "./add-remote-config";
import { removeFlagInstance } from "./ai";

const program = new Command();

Expand Down Expand Up @@ -184,4 +185,24 @@ program
}
});

const aiCommand = program.command("ai").description("Abby AI helpers");

aiCommand
.command("remove")
.description("remove a flag from your code")
.argument("<dir>", "The directory to scan for")
.argument("<flag>", "The flag name to remove")
.addOption(ConfigOption)
.addOption(HostOption)
.action(async (dir: string, flagName: string, options: { config?: string; host?: string }) => {
const files = await removeFlagInstance({
apiKey: await getToken(),
flagName,
path: dir,
configPath: options.config,
host: options.host,
});
console.log(files);
});

program.parse(process.argv);
3 changes: 3 additions & 0 deletions packages/core/src/shared/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,6 @@ export function stringifyRemoteConfigValue(value: RemoteConfigValue) {
assertUnreachable(value);
}
}

export const getUseFeatureFlagRegex = (flagName: string) =>
new RegExp(`useFeatureFlag\\s*\\(\\s*['"\`]${flagName}['"\`]\\s*\\)`);
57 changes: 51 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5e950f9

Please sign in to comment.