@rowlabs/ev 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +191 -39
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6577,6 +6577,9 @@ var init_api_client = __esm({
6577
6577
  githubRepo
6578
6578
  });
6579
6579
  }
6580
+ async deleteProject(projectId) {
6581
+ return this.request("DELETE", `/projects/${projectId}`);
6582
+ }
6580
6583
  async updateProjectName(projectId, name) {
6581
6584
  return this.request("PATCH", `/projects/${projectId}`, {
6582
6585
  name
@@ -6650,6 +6653,9 @@ var init_api_client = __esm({
6650
6653
  async deleteEnvironment(appId, envId) {
6651
6654
  return this.request("DELETE", `/apps/${appId}/environments/${envId}`);
6652
6655
  }
6656
+ async deleteApp(projectId, appId) {
6657
+ return this.request("DELETE", `/projects/${projectId}/apps/${appId}`);
6658
+ }
6653
6659
  async rotateKeys(projectId, body) {
6654
6660
  return this.request("POST", `/projects/${projectId}/rotate`, body);
6655
6661
  }
@@ -6729,7 +6735,7 @@ var init_auth = __esm({
6729
6735
 
6730
6736
  // src/index.ts
6731
6737
  init_dist();
6732
- import { Command as Command19 } from "commander";
6738
+ import { Command as Command20 } from "commander";
6733
6739
 
6734
6740
  // src/commands/login.ts
6735
6741
  init_dist();
@@ -6917,11 +6923,11 @@ var initCommand = new Command2("init").description("Initialize ev in the current
6917
6923
  console.log(` ${app.name} \u2192 ${app.path}`);
6918
6924
  }
6919
6925
  const rl = await import("readline");
6920
- const readline2 = rl.createInterface({ input: process.stdin, output: process.stdout });
6926
+ const readline3 = rl.createInterface({ input: process.stdin, output: process.stdout });
6921
6927
  const answer = await new Promise((resolve) => {
6922
- readline2.question("\nAdd these as app mappings to ev.yaml? (y/N) ", resolve);
6928
+ readline3.question("\nAdd these as app mappings to ev.yaml? (y/N) ", resolve);
6923
6929
  });
6924
- readline2.close();
6930
+ readline3.close();
6925
6931
  if (answer.toLowerCase() === "y") {
6926
6932
  const appsConfig = {};
6927
6933
  for (const app of detectedApps) {
@@ -7579,16 +7585,16 @@ envCommand.command("create <name>").description("Create a new environment").acti
7579
7585
  });
7580
7586
  envCommand.command("delete <name>").description("Delete an environment").action(async (name) => {
7581
7587
  const rl = await import("readline");
7582
- const readline2 = rl.createInterface({ input: process.stdin, output: process.stdout });
7588
+ const readline3 = rl.createInterface({ input: process.stdin, output: process.stdout });
7583
7589
  const answer = await new Promise((resolve) => {
7584
- readline2.question(
7590
+ readline3.question(
7585
7591
  chalk10.red(
7586
7592
  `Delete environment "${name}"? This removes all secrets and releases. Type the environment name to confirm: `
7587
7593
  ),
7588
7594
  resolve
7589
7595
  );
7590
7596
  });
7591
- readline2.close();
7597
+ readline3.close();
7592
7598
  if (answer !== name) {
7593
7599
  console.log(chalk10.yellow("Cancelled."));
7594
7600
  process.exit(0);
@@ -7722,11 +7728,11 @@ accessCommand.command("grant <email>").description("Invite a user and share the
7722
7728
  });
7723
7729
  accessCommand.command("revoke <email>").description("Remove a member's access").action(async (email) => {
7724
7730
  const rl = await import("readline");
7725
- const readline2 = rl.createInterface({ input: process.stdin, output: process.stdout });
7731
+ const readline3 = rl.createInterface({ input: process.stdin, output: process.stdout });
7726
7732
  const answer = await new Promise((resolve) => {
7727
- readline2.question(chalk12.yellow(`Revoke access for ${email}? (y/N) `), resolve);
7733
+ readline3.question(chalk12.yellow(`Revoke access for ${email}? (y/N) `), resolve);
7728
7734
  });
7729
- readline2.close();
7735
+ readline3.close();
7730
7736
  if (answer.toLowerCase() !== "y") {
7731
7737
  console.log(chalk12.yellow("Cancelled."));
7732
7738
  process.exit(0);
@@ -7947,6 +7953,18 @@ init_auth();
7947
7953
  import { Command as Command12 } from "commander";
7948
7954
  import { execSync } from "child_process";
7949
7955
  import chalk14 from "chalk";
7956
+ async function getSecretValue(projectId, appName, envName, key, isEvBackend, backendConfig) {
7957
+ const envId = await resolveEnvironmentId(projectId, appName, envName);
7958
+ const client = await createApiClient(backendConfig);
7959
+ const response = await client.pullSecrets(envId);
7960
+ const secret = response.secrets.find((s2) => s2.key === key);
7961
+ if (!secret) return null;
7962
+ if (isEvBackend) {
7963
+ const projectKey = await getDecryptedProjectKey(projectId);
7964
+ return decryptSecret(secret.encryptedValue, projectKey);
7965
+ }
7966
+ return secret.encryptedValue;
7967
+ }
7950
7968
  var getCommand = new Command12("get").description("Get a single secret value").argument("<key>", "Secret key name").argument("[target]", "app:env or env").option("-c, --copy", "Copy to clipboard").action(async (key, target, options) => {
7951
7969
  try {
7952
7970
  const resolved = await resolveCurrentContext(target);
@@ -7954,40 +7972,94 @@ var getCommand = new Command12("get").description("Get a single secret value").a
7954
7972
  console.error(chalk14.red("No ev.yaml found. Run `ev init` first."));
7955
7973
  process.exit(1);
7956
7974
  }
7957
- const { context, backendConfig } = resolved;
7975
+ const { context, backendConfig, repoRoot } = resolved;
7958
7976
  const isEvBackend = !backendConfig || backendConfig.type === "ev";
7959
- const envId = await resolveEnvironmentId(context.project, context.app, context.env);
7960
- const client = await createApiClient(backendConfig);
7961
- const response = await client.pullSecrets(envId);
7962
- const secret = response.secrets.find((s2) => s2.key === key);
7963
- if (!secret) {
7964
- console.error(
7965
- chalk14.red(`Secret "${key}" not found in ${context.app ?? "default"}:${context.env}`)
7966
- );
7967
- process.exit(1);
7968
- }
7969
- let value;
7970
- if (isEvBackend) {
7971
- const projectKey = await getDecryptedProjectKey(context.project);
7972
- value = decryptSecret(secret.encryptedValue, projectKey);
7973
- } else {
7974
- value = secret.encryptedValue;
7975
- }
7976
- if (options?.copy) {
7977
- try {
7978
- execSync("pbcopy", { input: value });
7979
- console.error(chalk14.green(`Copied ${key} to clipboard`));
7980
- } catch {
7981
- process.stdout.write(value);
7977
+ const config = await loadEvConfig(process.cwd());
7978
+ const cwd = process.cwd();
7979
+ const isAtRoot = cwd === repoRoot;
7980
+ if (isAtRoot && !target && config?.apps && Object.keys(config.apps).length > 0) {
7981
+ const results = [];
7982
+ for (const appName of Object.keys(config.apps)) {
7983
+ try {
7984
+ const value = await getSecretValue(
7985
+ context.project,
7986
+ appName,
7987
+ context.env,
7988
+ key,
7989
+ isEvBackend,
7990
+ backendConfig
7991
+ );
7992
+ if (value !== null) {
7993
+ results.push({ app: appName, value });
7994
+ }
7995
+ } catch {
7996
+ }
7997
+ }
7998
+ if (results.length === 0) {
7999
+ console.error(chalk14.red(`Secret "${key}" not found in any app`));
8000
+ process.exit(1);
8001
+ }
8002
+ if (results.length === 1) {
8003
+ outputValue(results[0].value, key, options?.copy);
8004
+ } else {
8005
+ const allSame = results.every((r2) => r2.value === results[0].value);
8006
+ if (allSame && options?.copy) {
8007
+ outputValue(results[0].value, key, true);
8008
+ } else {
8009
+ console.error(chalk14.bold(`
8010
+ ${key} found in ${results.length} apps:
8011
+ `));
8012
+ for (const { app, value } of results) {
8013
+ const masked = value.length <= 8 ? "****" : value.slice(0, 4) + "****" + value.slice(-4);
8014
+ console.error(` ${chalk14.cyan(app.padEnd(20))} ${chalk14.dim(masked)}`);
8015
+ }
8016
+ console.error();
8017
+ if (allSame) {
8018
+ console.error(chalk14.dim(" All values are identical."));
8019
+ outputValue(results[0].value, key, options?.copy);
8020
+ } else {
8021
+ console.error(
8022
+ chalk14.dim(" Values differ. Specify an app: ev get " + key + " AppName:dev")
8023
+ );
8024
+ }
8025
+ }
7982
8026
  }
7983
8027
  } else {
7984
- process.stdout.write(value);
8028
+ const value = await getSecretValue(
8029
+ context.project,
8030
+ context.app,
8031
+ context.env,
8032
+ key,
8033
+ isEvBackend,
8034
+ backendConfig
8035
+ );
8036
+ if (value === null) {
8037
+ console.error(
8038
+ chalk14.red(
8039
+ `Secret "${key}" not found in ${context.app ?? "default"}:${context.env}`
8040
+ )
8041
+ );
8042
+ process.exit(1);
8043
+ }
8044
+ outputValue(value, key, options?.copy);
7985
8045
  }
7986
8046
  } catch (err) {
7987
8047
  console.error(chalk14.red(`Failed: ${err.message}`));
7988
8048
  process.exit(1);
7989
8049
  }
7990
8050
  });
8051
+ function outputValue(value, key, copy) {
8052
+ if (copy) {
8053
+ try {
8054
+ execSync("pbcopy", { input: value });
8055
+ console.error(chalk14.green(`Copied ${key} to clipboard`));
8056
+ } catch {
8057
+ process.stdout.write(value);
8058
+ }
8059
+ } else {
8060
+ process.stdout.write(value);
8061
+ }
8062
+ }
7991
8063
 
7992
8064
  // src/commands/backend.ts
7993
8065
  init_config();
@@ -8415,6 +8487,7 @@ _ev() {
8415
8487
  'update:Update ev to latest version'
8416
8488
  'doctor:Check setup for issues'
8417
8489
  'completions:Generate shell completions'
8490
+ 'delete:Delete an app or environment'
8418
8491
  )
8419
8492
 
8420
8493
  _arguments -C \\
@@ -8467,7 +8540,7 @@ var BASH_COMPLETION = `_ev() {
8467
8540
  cur="\${COMP_WORDS[COMP_CWORD]}"
8468
8541
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
8469
8542
 
8470
- commands="login init push pull diff log get rollback env promote access backend import status scan doctor completions update"
8543
+ commands="login init push pull diff log get rollback env promote access backend import status scan doctor completions update delete"
8471
8544
 
8472
8545
  case "\${prev}" in
8473
8546
  ev)
@@ -8521,6 +8594,7 @@ complete -c ev -n '__fish_use_subcommand' -a scan -d 'Scan for env var reference
8521
8594
  complete -c ev -n '__fish_use_subcommand' -a update -d 'Update ev to latest version'
8522
8595
  complete -c ev -n '__fish_use_subcommand' -a doctor -d 'Check setup for issues'
8523
8596
  complete -c ev -n '__fish_use_subcommand' -a completions -d 'Generate shell completions'
8597
+ complete -c ev -n '__fish_use_subcommand' -a delete -d 'Delete an app or environment'
8524
8598
 
8525
8599
  # env subcommands
8526
8600
  complete -c ev -n '__fish_seen_subcommand_from env' -a create -d 'Create environment'
@@ -8857,7 +8931,7 @@ var updateCommand = new Command18("update").description("Update ev to the latest
8857
8931
  const spinner = ora12("Checking for updates...").start();
8858
8932
  try {
8859
8933
  const latest = execSync2("npm view @rowlabs/ev version", { encoding: "utf-8" }).trim();
8860
- const current = "0.4.1";
8934
+ const current = "0.4.3";
8861
8935
  if (current === latest) {
8862
8936
  spinner.succeed(chalk20.green(`Already on the latest version (${current})`));
8863
8937
  return;
@@ -8871,9 +8945,86 @@ var updateCommand = new Command18("update").description("Update ev to the latest
8871
8945
  }
8872
8946
  });
8873
8947
 
8948
+ // src/commands/delete.ts
8949
+ init_api_client();
8950
+ init_config();
8951
+ import { Command as Command19 } from "commander";
8952
+ import chalk21 from "chalk";
8953
+ import ora13 from "ora";
8954
+ import readline2 from "readline";
8955
+ var deleteCommand = new Command19("delete").description("Delete an app or environment").argument("<target>", "App name to delete, or app:env to delete a specific environment").action(async (target) => {
8956
+ try {
8957
+ const resolved = await resolveCurrentContext();
8958
+ if (!resolved) {
8959
+ console.error(chalk21.red("No ev.yaml found. Run `ev init` first."));
8960
+ process.exit(1);
8961
+ }
8962
+ const { context } = resolved;
8963
+ const client = await createApiClient();
8964
+ const project = await client.getProject(context.project);
8965
+ if (target.includes(":")) {
8966
+ const [appName, envName] = target.split(":");
8967
+ const app = project.apps.find((a2) => a2.name === appName);
8968
+ if (!app) {
8969
+ console.error(chalk21.red(`App "${appName}" not found`));
8970
+ process.exit(1);
8971
+ }
8972
+ const envs = await client.listEnvironments(app.id);
8973
+ const env = envs.find((e) => e.name === envName);
8974
+ if (!env) {
8975
+ console.error(chalk21.red(`Environment "${envName}" not found in app "${appName}"`));
8976
+ process.exit(1);
8977
+ }
8978
+ const answer = await prompt2(
8979
+ chalk21.red(`Delete environment "${appName}:${envName}"? This removes all secrets and releases. Type the environment name to confirm: `)
8980
+ );
8981
+ if (answer !== envName) {
8982
+ console.log(chalk21.yellow("Cancelled."));
8983
+ process.exit(0);
8984
+ }
8985
+ const spinner = ora13(`Deleting ${appName}:${envName}...`).start();
8986
+ await client.deleteEnvironment(app.id, env.id);
8987
+ spinner.succeed(chalk21.green(`Deleted environment "${appName}:${envName}"`));
8988
+ } else {
8989
+ const appName = target;
8990
+ const app = project.apps.find((a2) => a2.name === appName);
8991
+ if (!app) {
8992
+ console.error(chalk21.red(`App "${appName}" not found`));
8993
+ process.exit(1);
8994
+ }
8995
+ const envs = await client.listEnvironments(app.id);
8996
+ const secretCount = envs.reduce((sum, e) => sum + e.secretCount, 0);
8997
+ const answer = await prompt2(
8998
+ chalk21.red(
8999
+ `Delete app "${appName}"? This removes ${envs.length} environment${envs.length === 1 ? "" : "s"} and ${secretCount} secret${secretCount === 1 ? "" : "s"}. Type the app name to confirm: `
9000
+ )
9001
+ );
9002
+ if (answer !== appName) {
9003
+ console.log(chalk21.yellow("Cancelled."));
9004
+ process.exit(0);
9005
+ }
9006
+ const spinner = ora13(`Deleting ${appName}...`).start();
9007
+ await client.deleteApp(context.project, app.id);
9008
+ spinner.succeed(chalk21.green(`Deleted app "${appName}"`));
9009
+ }
9010
+ } catch (err) {
9011
+ console.error(chalk21.red(`Delete failed: ${err.message}`));
9012
+ process.exit(1);
9013
+ }
9014
+ });
9015
+ function prompt2(question) {
9016
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
9017
+ return new Promise((resolve) => {
9018
+ rl.question(question, (answer) => {
9019
+ rl.close();
9020
+ resolve(answer.trim());
9021
+ });
9022
+ });
9023
+ }
9024
+
8874
9025
  // src/index.ts
8875
- var program = new Command19();
8876
- program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.4.1");
9026
+ var program = new Command20();
9027
+ program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.4.3");
8877
9028
  program.addCommand(loginCommand);
8878
9029
  program.addCommand(initCommand);
8879
9030
  program.addCommand(pushCommand);
@@ -8892,6 +9043,7 @@ program.addCommand(doctorCommand);
8892
9043
  program.addCommand(completionsCommand);
8893
9044
  program.addCommand(scanCommand);
8894
9045
  program.addCommand(updateCommand);
9046
+ program.addCommand(deleteCommand);
8895
9047
  async function main() {
8896
9048
  await initCrypto();
8897
9049
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowlabs/ev",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Git for env vars — sync environment variables across teams securely",
5
5
  "type": "module",
6
6
  "bin": {