@rowlabs/ev 0.3.2 → 0.4.0

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 +320 -145
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6697,16 +6697,18 @@ async function getDecryptedProjectKey(projectId) {
6697
6697
  async function resolveEnvironmentId(projectId, appName, envName) {
6698
6698
  const client = await createApiClient();
6699
6699
  const project = await client.getProject(projectId);
6700
- const app = appName ? project.apps.find((a2) => a2.name === appName) : project.apps.find((a2) => a2.name === "default") ?? project.apps[0];
6700
+ let app = appName ? project.apps.find((a2) => a2.name === appName) : project.apps.find((a2) => a2.name === "default") ?? project.apps[0];
6701
+ if (!app && appName) {
6702
+ app = await client.createApp(projectId, appName);
6703
+ }
6701
6704
  if (!app) {
6702
6705
  console.error(chalk3.red(`App "${appName ?? "default"}" not found`));
6703
6706
  process.exit(1);
6704
6707
  }
6705
6708
  const envs = await client.listEnvironments(app.id);
6706
- const env = envs.find((e) => e.name === envName);
6709
+ let env = envs.find((e) => e.name === envName);
6707
6710
  if (!env) {
6708
- console.error(chalk3.red(`Environment "${envName}" not found in app "${app.name}"`));
6709
- process.exit(1);
6711
+ env = await client.createEnvironment(app.id, envName);
6710
6712
  }
6711
6713
  return env.id;
6712
6714
  }
@@ -6953,6 +6955,7 @@ init_api_client();
6953
6955
  init_config();
6954
6956
  init_auth();
6955
6957
  import { Command as Command3 } from "commander";
6958
+ import { join as join5 } from "path";
6956
6959
  import chalk4 from "chalk";
6957
6960
  import ora3 from "ora";
6958
6961
 
@@ -6993,6 +6996,70 @@ async function readEnvFiles(dir, fileNames) {
6993
6996
  }
6994
6997
 
6995
6998
  // src/commands/push.ts
6999
+ async function pushSingleApp(projectId, appName, envName, dir, envFileNames, options, isEvBackend, backendConfig) {
7000
+ const { vars: localEnv, files } = await readEnvFiles(dir, envFileNames);
7001
+ const keys = Object.keys(localEnv);
7002
+ if (keys.length === 0) return false;
7003
+ const label = `${appName ?? "default"}:${envName}`;
7004
+ const envId = await resolveEnvironmentId(projectId, appName, envName);
7005
+ const client = await createApiClient(backendConfig);
7006
+ const remoteResponse = await client.pullSecrets(envId);
7007
+ const remoteEnv = {};
7008
+ let projectKey;
7009
+ if (isEvBackend) {
7010
+ projectKey = await getDecryptedProjectKey(projectId);
7011
+ for (const secret of remoteResponse.secrets) {
7012
+ remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
7013
+ }
7014
+ } else {
7015
+ for (const secret of remoteResponse.secrets) {
7016
+ remoteEnv[secret.key] = secret.encryptedValue;
7017
+ }
7018
+ }
7019
+ const diff = diffEnvs(remoteEnv, localEnv);
7020
+ const hasChanges = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
7021
+ if (!hasChanges) {
7022
+ console.log(chalk4.dim(` ${label} \u2014 no changes (${files.join(", ")})`));
7023
+ return true;
7024
+ }
7025
+ console.log(chalk4.bold(`
7026
+ ${label}`) + chalk4.dim(` (${files.join(", ")})`));
7027
+ for (const { key, value } of diff.added) {
7028
+ console.log(chalk4.green(` + ${key}=${maskValue(value)}`));
7029
+ }
7030
+ for (const { key, oldValue, newValue } of diff.changed) {
7031
+ console.log(chalk4.yellow(` ~ ${key}`));
7032
+ console.log(chalk4.red(` - ${maskValue(oldValue)}`));
7033
+ console.log(chalk4.green(` + ${maskValue(newValue)}`));
7034
+ }
7035
+ if (options.prune) {
7036
+ for (const { key, value } of diff.removed) {
7037
+ console.log(
7038
+ chalk4.red(` - ${key}=${maskValue(value)}`) + chalk4.dim(" (will be removed)")
7039
+ );
7040
+ }
7041
+ }
7042
+ console.log(
7043
+ ` ${chalk4.green(`+${diff.added.length}`)} ${chalk4.red(`-${diff.removed.length}`)} ${chalk4.yellow(`~${diff.changed.length}`)}`
7044
+ );
7045
+ let secrets;
7046
+ if (isEvBackend) {
7047
+ if (!projectKey) projectKey = await getDecryptedProjectKey(projectId);
7048
+ const resolvedKey = projectKey;
7049
+ secrets = keys.map((key) => ({
7050
+ key,
7051
+ encryptedValue: encryptSecret(localEnv[key], resolvedKey)
7052
+ }));
7053
+ } else {
7054
+ secrets = keys.map((key) => ({ key, encryptedValue: localEnv[key] }));
7055
+ }
7056
+ await client.pushSecrets(envId, {
7057
+ secrets,
7058
+ message: options.message,
7059
+ prune: options.prune
7060
+ });
7061
+ return true;
7062
+ }
6996
7063
  var pushCommand = new Command3("push").description("Push local .env files to remote").argument("[target]", "app:env or env to push to").option("--prune", "Remove remote keys not present locally", false).option("-m, --message <message>", "Push message").option("-y, --yes", "Skip confirmation prompt", false).action(async (target, options) => {
6997
7064
  const spinner = ora3("Pushing secrets...").start();
6998
7065
  try {
@@ -7001,94 +7068,136 @@ var pushCommand = new Command3("push").description("Push local .env files to rem
7001
7068
  spinner.fail(chalk4.red("No ev.yaml found. Run `ev init` first."));
7002
7069
  process.exit(1);
7003
7070
  }
7004
- const { context, backendConfig, envFiles } = resolved;
7071
+ const { context, backendConfig, envFiles, repoRoot } = resolved;
7005
7072
  const isEvBackend = !backendConfig || backendConfig.type === "ev";
7006
- const { vars: localEnv, files } = await readEnvFiles(process.cwd(), envFiles);
7007
- const keys = Object.keys(localEnv);
7008
- if (keys.length === 0) {
7009
- spinner.fail(chalk4.red("No env files found (.env, .env.local)"));
7010
- process.exit(1);
7011
- }
7012
- spinner.text = `Reading from ${files.join(", ")}...`;
7013
- spinner.text = "Fetching remote secrets...";
7014
- const envId = await resolveEnvironmentId(context.project, context.app, context.env);
7015
- const client = await createApiClient(backendConfig);
7016
- const remoteResponse = await client.pullSecrets(envId);
7017
- const remoteEnv = {};
7018
- let projectKey;
7019
- if (isEvBackend) {
7020
- projectKey = await getDecryptedProjectKey(context.project);
7021
- for (const secret of remoteResponse.secrets) {
7022
- remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
7073
+ const config = await loadEvConfig(process.cwd());
7074
+ const cwd = process.cwd();
7075
+ const isAtRoot = cwd === repoRoot;
7076
+ if (isAtRoot && config?.apps && Object.keys(config.apps).length > 0) {
7077
+ spinner.text = "Pushing all apps...";
7078
+ spinner.stop();
7079
+ let totalPushed = 0;
7080
+ const opts = options ?? { prune: false, yes: false };
7081
+ for (const [appName, appConfig] of Object.entries(config.apps)) {
7082
+ const appDir = join5(repoRoot, appConfig.path);
7083
+ const pushed = await pushSingleApp(
7084
+ context.project,
7085
+ appName,
7086
+ context.env,
7087
+ appDir,
7088
+ envFiles,
7089
+ opts,
7090
+ isEvBackend,
7091
+ backendConfig
7092
+ );
7093
+ if (pushed) totalPushed++;
7094
+ }
7095
+ if (totalPushed === 0) {
7096
+ console.log(chalk4.yellow("\nNo env files found in any app directory."));
7097
+ } else if (!opts.yes) {
7098
+ console.log();
7099
+ const answer = await prompt("Confirm push all? (y/N) ");
7100
+ if (answer.toLowerCase() !== "y") {
7101
+ console.log(chalk4.yellow("Push cancelled."));
7102
+ process.exit(0);
7103
+ }
7104
+ console.log(chalk4.green(`
7105
+ \u2714 Pushed ${totalPushed} apps`));
7106
+ } else {
7107
+ console.log(chalk4.green(`
7108
+ \u2714 Pushed ${totalPushed} apps`));
7023
7109
  }
7024
7110
  } else {
7025
- for (const secret of remoteResponse.secrets) {
7026
- remoteEnv[secret.key] = secret.encryptedValue;
7111
+ const { vars: localEnv, files } = await readEnvFiles(cwd, envFiles);
7112
+ const keys = Object.keys(localEnv);
7113
+ if (keys.length === 0) {
7114
+ spinner.fail(chalk4.red("No env files found (.env, .env.local)"));
7115
+ process.exit(1);
7027
7116
  }
7028
- }
7029
- const diff = diffEnvs(remoteEnv, localEnv);
7030
- const hasChanges = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
7031
- spinner.stop();
7032
- console.log(chalk4.bold(`
7117
+ spinner.text = `Reading from ${files.join(", ")}...`;
7118
+ spinner.text = "Fetching remote secrets...";
7119
+ const envId = await resolveEnvironmentId(context.project, context.app, context.env);
7120
+ const client = await createApiClient(backendConfig);
7121
+ const remoteResponse = await client.pullSecrets(envId);
7122
+ const remoteEnv = {};
7123
+ let projectKey;
7124
+ if (isEvBackend) {
7125
+ projectKey = await getDecryptedProjectKey(context.project);
7126
+ for (const secret of remoteResponse.secrets) {
7127
+ remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
7128
+ }
7129
+ } else {
7130
+ for (const secret of remoteResponse.secrets) {
7131
+ remoteEnv[secret.key] = secret.encryptedValue;
7132
+ }
7133
+ }
7134
+ const diff = diffEnvs(remoteEnv, localEnv);
7135
+ const hasChanges = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
7136
+ spinner.stop();
7137
+ console.log(
7138
+ chalk4.bold(`
7033
7139
  Pushing to ${context.app ?? "default"}:${context.env}
7034
- `));
7035
- if (!hasChanges) {
7036
- console.log(chalk4.green("No changes to push."));
7037
- return;
7038
- }
7039
- for (const { key, value } of diff.added) {
7040
- console.log(chalk4.green(`+ ${key}=${maskValue(value)}`));
7041
- }
7042
- for (const { key, oldValue, newValue } of diff.changed) {
7043
- console.log(chalk4.yellow(`~ ${key}`));
7044
- console.log(chalk4.red(` - ${maskValue(oldValue)}`));
7045
- console.log(chalk4.green(` + ${maskValue(newValue)}`));
7046
- }
7047
- if (options?.prune) {
7048
- for (const { key, value } of diff.removed) {
7049
- console.log(
7050
- chalk4.red(`- ${key}=${maskValue(value)}`) + chalk4.dim(" (will be removed)")
7051
- );
7140
+ `)
7141
+ );
7142
+ if (!hasChanges) {
7143
+ console.log(chalk4.green("No changes to push."));
7144
+ return;
7052
7145
  }
7053
- }
7054
- console.log(
7055
- `
7146
+ for (const { key, value } of diff.added) {
7147
+ console.log(chalk4.green(`+ ${key}=${maskValue(value)}`));
7148
+ }
7149
+ for (const { key, oldValue, newValue } of diff.changed) {
7150
+ console.log(chalk4.yellow(`~ ${key}`));
7151
+ console.log(chalk4.red(` - ${maskValue(oldValue)}`));
7152
+ console.log(chalk4.green(` + ${maskValue(newValue)}`));
7153
+ }
7154
+ if (options?.prune) {
7155
+ for (const { key, value } of diff.removed) {
7156
+ console.log(
7157
+ chalk4.red(`- ${key}=${maskValue(value)}`) + chalk4.dim(" (will be removed)")
7158
+ );
7159
+ }
7160
+ }
7161
+ console.log(
7162
+ `
7056
7163
  ${chalk4.green(`+${diff.added.length}`)} ${chalk4.red(`-${diff.removed.length}`)} ${chalk4.yellow(`~${diff.changed.length}`)}
7057
7164
  `
7058
- );
7059
- if (!options?.yes) {
7060
- const answer = await prompt("Confirm push? (y/N) ");
7061
- if (answer.toLowerCase() !== "y") {
7062
- console.log(chalk4.yellow("Push cancelled."));
7063
- process.exit(0);
7165
+ );
7166
+ if (!options?.yes) {
7167
+ const answer = await prompt("Confirm push? (y/N) ");
7168
+ if (answer.toLowerCase() !== "y") {
7169
+ console.log(chalk4.yellow("Push cancelled."));
7170
+ process.exit(0);
7171
+ }
7064
7172
  }
7173
+ const pushSpinner = ora3("Encrypting and pushing secrets...").start();
7174
+ let secrets;
7175
+ if (isEvBackend) {
7176
+ if (!projectKey) projectKey = await getDecryptedProjectKey(context.project);
7177
+ const resolvedKey = projectKey;
7178
+ secrets = keys.map((key) => ({
7179
+ key,
7180
+ encryptedValue: encryptSecret(localEnv[key], resolvedKey)
7181
+ }));
7182
+ } else {
7183
+ secrets = keys.map((key) => ({ key, encryptedValue: localEnv[key] }));
7184
+ }
7185
+ await client.pushSecrets(envId, {
7186
+ secrets,
7187
+ message: options?.message,
7188
+ prune: options?.prune
7189
+ });
7190
+ const result = { keysAdded: diff.added, keysChanged: diff.changed, keysRemoved: diff.removed };
7191
+ const parts = [];
7192
+ if (result.keysAdded.length) parts.push(`${result.keysAdded.length} added`);
7193
+ if (result.keysChanged.length) parts.push(`${result.keysChanged.length} changed`);
7194
+ if (result.keysRemoved.length) parts.push(`${result.keysRemoved.length} removed`);
7195
+ pushSpinner.succeed(
7196
+ chalk4.green(
7197
+ `Pushed to ${context.app ?? "default"}:${context.env} \u2014 ${parts.join(", ") || "no changes"}`
7198
+ )
7199
+ );
7065
7200
  }
7066
- const pushSpinner = ora3("Encrypting and pushing secrets...").start();
7067
- let secrets;
7068
- if (isEvBackend) {
7069
- if (!projectKey) projectKey = await getDecryptedProjectKey(context.project);
7070
- const resolvedKey = projectKey;
7071
- secrets = keys.map((key) => ({
7072
- key,
7073
- encryptedValue: encryptSecret(localEnv[key], resolvedKey)
7074
- }));
7075
- } else {
7076
- secrets = keys.map((key) => ({ key, encryptedValue: localEnv[key] }));
7077
- }
7078
- const result = await client.pushSecrets(envId, {
7079
- secrets,
7080
- message: options?.message,
7081
- prune: options?.prune
7082
- });
7083
- const parts = [];
7084
- if (result.keysAdded?.length) parts.push(`${result.keysAdded.length} added`);
7085
- if (result.keysChanged?.length) parts.push(`${result.keysChanged.length} changed`);
7086
- if (result.keysRemoved?.length) parts.push(`${result.keysRemoved.length} removed`);
7087
- pushSpinner.succeed(
7088
- chalk4.green(
7089
- `Pushed to ${context.app ?? "default"}:${context.env} \u2014 ${parts.join(", ") || "no changes"}`
7090
- )
7091
- );
7092
7201
  } catch (err) {
7093
7202
  ora3().fail(chalk4.red(`Push failed: ${err.message}`));
7094
7203
  process.exit(1);
@@ -7102,9 +7211,45 @@ init_config();
7102
7211
  init_auth();
7103
7212
  import { Command as Command4 } from "commander";
7104
7213
  import { writeFile as writeFile4, copyFile } from "fs/promises";
7105
- import { join as join5 } from "path";
7214
+ import { existsSync as existsSync5 } from "fs";
7215
+ import { join as join6 } from "path";
7106
7216
  import chalk5 from "chalk";
7107
7217
  import ora4 from "ora";
7218
+ async function pullSingleApp(projectId, appName, envName, dir, envFileNames, isEvBackend, backendConfig) {
7219
+ const label = `${appName ?? "default"}:${envName}`;
7220
+ const envId = await resolveEnvironmentId(projectId, appName, envName);
7221
+ const client = await createApiClient(backendConfig);
7222
+ const response = await client.pullSecrets(envId);
7223
+ if (response.secrets.length === 0) {
7224
+ console.log(chalk5.dim(` ${label} \u2014 no secrets`));
7225
+ return 0;
7226
+ }
7227
+ const remoteEnv = {};
7228
+ if (isEvBackend) {
7229
+ const projectKey = await getDecryptedProjectKey(projectId);
7230
+ for (const secret of response.secrets) {
7231
+ remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
7232
+ }
7233
+ } else {
7234
+ for (const secret of response.secrets) {
7235
+ remoteEnv[secret.key] = secret.encryptedValue;
7236
+ }
7237
+ }
7238
+ const envPath = join6(dir, ".env");
7239
+ const backupPath = join6(dir, ".env.backup");
7240
+ const { vars: localEnv, files: localFiles } = await readEnvFiles(dir, envFileNames);
7241
+ if (localFiles.length > 0) {
7242
+ const diff = diffEnvs(remoteEnv, localEnv);
7243
+ if (diff.added.length > 0 || diff.changed.length > 0) {
7244
+ if (existsSync5(envPath)) {
7245
+ await copyFile(envPath, backupPath);
7246
+ }
7247
+ }
7248
+ }
7249
+ await writeFile4(envPath, serializeEnv(remoteEnv), "utf-8");
7250
+ console.log(chalk5.green(` \u2714 ${label} \u2014 ${response.secrets.length} secrets \u2192 ${dir}`));
7251
+ return response.secrets.length;
7252
+ }
7108
7253
  var pullCommand = new Command4("pull").description("Pull remote secrets into local .env").argument("[target]", "app:env or env to pull from").option("-y, --yes", "Skip confirmation prompt", false).action(async (target, options) => {
7109
7254
  const spinner = ora4("Pulling secrets...").start();
7110
7255
  try {
@@ -7113,66 +7258,96 @@ var pullCommand = new Command4("pull").description("Pull remote secrets into loc
7113
7258
  spinner.fail(chalk5.red("No ev.yaml found. Run `ev init` first."));
7114
7259
  process.exit(1);
7115
7260
  }
7116
- const { context, backendConfig, envFiles } = resolved;
7261
+ const { context, backendConfig, envFiles, repoRoot } = resolved;
7117
7262
  const isEvBackend = !backendConfig || backendConfig.type === "ev";
7118
- const envId = await resolveEnvironmentId(context.project, context.app, context.env);
7119
- const client = await createApiClient(backendConfig);
7120
- const response = await client.pullSecrets(envId);
7121
- if (response.secrets.length === 0) {
7122
- spinner.warn(
7123
- chalk5.yellow(`No secrets found in ${context.app ?? "default"}:${context.env}`)
7124
- );
7125
- return;
7126
- }
7127
- spinner.text = "Decrypting secrets...";
7128
- const remoteEnv = {};
7129
- if (isEvBackend) {
7130
- const projectKey = await getDecryptedProjectKey(context.project);
7131
- for (const secret of response.secrets) {
7132
- remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
7263
+ const config = await loadEvConfig(process.cwd());
7264
+ const cwd = process.cwd();
7265
+ const isAtRoot = cwd === repoRoot;
7266
+ if (isAtRoot && config?.apps && Object.keys(config.apps).length > 0) {
7267
+ spinner.stop();
7268
+ console.log(chalk5.bold("\nPulling all apps...\n"));
7269
+ let totalSecrets = 0;
7270
+ for (const [appName, appConfig] of Object.entries(config.apps)) {
7271
+ const appDir = join6(repoRoot, appConfig.path);
7272
+ const count = await pullSingleApp(
7273
+ context.project,
7274
+ appName,
7275
+ context.env,
7276
+ appDir,
7277
+ envFiles,
7278
+ isEvBackend,
7279
+ backendConfig
7280
+ );
7281
+ totalSecrets += count;
7133
7282
  }
7283
+ console.log(chalk5.green(`
7284
+ \u2714 Pulled ${totalSecrets} total secrets across ${Object.keys(config.apps).length} apps`));
7134
7285
  } else {
7135
- for (const secret of response.secrets) {
7136
- remoteEnv[secret.key] = secret.encryptedValue;
7286
+ const envId = await resolveEnvironmentId(context.project, context.app, context.env);
7287
+ const client = await createApiClient(backendConfig);
7288
+ const response = await client.pullSecrets(envId);
7289
+ if (response.secrets.length === 0) {
7290
+ spinner.warn(
7291
+ chalk5.yellow(
7292
+ `No secrets found in ${context.app ?? "default"}:${context.env}`
7293
+ )
7294
+ );
7295
+ return;
7137
7296
  }
7138
- }
7139
- const envPath = join5(process.cwd(), ".env");
7140
- const backupPath = join5(process.cwd(), ".env.backup");
7141
- const { vars: localEnv, files: localFiles } = await readEnvFiles(process.cwd(), envFiles);
7142
- if (localFiles.length > 0) {
7143
- const diff = diffEnvs(remoteEnv, localEnv);
7144
- const localOnlyKeys = diff.added;
7145
- const localChangedKeys = diff.changed;
7146
- if (localOnlyKeys.length > 0 || localChangedKeys.length > 0) {
7147
- spinner.stop();
7148
- console.log(chalk5.yellow(`Warning: Local .env has changes not on remote:`));
7149
- for (const { key } of localOnlyKeys) {
7150
- console.log(chalk5.green(`+ ${key}`));
7297
+ spinner.text = "Decrypting secrets...";
7298
+ const remoteEnv = {};
7299
+ if (isEvBackend) {
7300
+ const projectKey = await getDecryptedProjectKey(context.project);
7301
+ for (const secret of response.secrets) {
7302
+ remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
7151
7303
  }
7152
- for (const { key } of localChangedKeys) {
7153
- console.log(chalk5.yellow(`~ ${key}`));
7304
+ } else {
7305
+ for (const secret of response.secrets) {
7306
+ remoteEnv[secret.key] = secret.encryptedValue;
7154
7307
  }
7155
- console.log(
7156
- chalk5.dim(`
7157
- These will be overwritten. A backup will be saved to .env.backup.`)
7158
- );
7159
- if (!options?.yes) {
7160
- const answer = await prompt("Continue? (y/N) ");
7161
- if (answer.toLowerCase() !== "y") {
7162
- console.log(chalk5.yellow("Pull cancelled."));
7163
- process.exit(0);
7308
+ }
7309
+ const envPath = join6(cwd, ".env");
7310
+ const backupPath = join6(cwd, ".env.backup");
7311
+ const { vars: localEnv, files: localFiles } = await readEnvFiles(cwd, envFiles);
7312
+ if (localFiles.length > 0) {
7313
+ const diff = diffEnvs(remoteEnv, localEnv);
7314
+ const localOnlyKeys = diff.added;
7315
+ const localChangedKeys = diff.changed;
7316
+ if (localOnlyKeys.length > 0 || localChangedKeys.length > 0) {
7317
+ spinner.stop();
7318
+ console.log(chalk5.yellow(`Warning: Local .env has changes not on remote:`));
7319
+ for (const { key } of localOnlyKeys) {
7320
+ console.log(chalk5.green(`+ ${key}`));
7321
+ }
7322
+ for (const { key } of localChangedKeys) {
7323
+ console.log(chalk5.yellow(`~ ${key}`));
7324
+ }
7325
+ console.log(
7326
+ chalk5.dim(
7327
+ `
7328
+ These will be overwritten. A backup will be saved to .env.backup.`
7329
+ )
7330
+ );
7331
+ if (!options?.yes) {
7332
+ const answer = await prompt("Continue? (y/N) ");
7333
+ if (answer.toLowerCase() !== "y") {
7334
+ console.log(chalk5.yellow("Pull cancelled."));
7335
+ process.exit(0);
7336
+ }
7337
+ }
7338
+ if (existsSync5(envPath)) {
7339
+ await copyFile(envPath, backupPath);
7340
+ console.log(chalk5.dim(`Backup saved to .env.backup`));
7164
7341
  }
7165
7342
  }
7166
- await copyFile(envPath, backupPath);
7167
- console.log(chalk5.dim(`Backup saved to .env.backup`));
7168
7343
  }
7344
+ await writeFile4(envPath, serializeEnv(remoteEnv), "utf-8");
7345
+ spinner.succeed(
7346
+ chalk5.green(
7347
+ `Pulled ${response.secrets.length} secrets from ${context.app ?? "default"}:${context.env}`
7348
+ )
7349
+ );
7169
7350
  }
7170
- await writeFile4(envPath, serializeEnv(remoteEnv), "utf-8");
7171
- spinner.succeed(
7172
- chalk5.green(
7173
- `Pulled ${response.secrets.length} secrets from ${context.app ?? "default"}:${context.env}`
7174
- )
7175
- );
7176
7351
  } catch (err) {
7177
7352
  ora4().fail(chalk5.red(`Pull failed: ${err.message}`));
7178
7353
  process.exit(1);
@@ -7806,7 +7981,7 @@ var getCommand = new Command12("get").description("Get a single secret value").a
7806
7981
  init_config();
7807
7982
  import { Command as Command13 } from "commander";
7808
7983
  import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
7809
- import { join as join6 } from "path";
7984
+ import { join as join7 } from "path";
7810
7985
  import chalk15 from "chalk";
7811
7986
  import ora9 from "ora";
7812
7987
  import YAML3 from "yaml";
@@ -7819,7 +7994,7 @@ backendCommand.command("show").description("Show current backend configuration")
7819
7994
  console.error(chalk15.red("No ev.yaml found."));
7820
7995
  process.exit(1);
7821
7996
  }
7822
- const content = await readFile5(join6(root, "ev.yaml"), "utf-8");
7997
+ const content = await readFile5(join7(root, "ev.yaml"), "utf-8");
7823
7998
  const config = YAML3.parse(content);
7824
7999
  console.log(chalk15.bold("\nBackend Configuration\n"));
7825
8000
  const defaultBackend = config.backend ?? { type: "ev" };
@@ -7878,7 +8053,7 @@ backendCommand.command("set <type>").description("Set the default storage backen
7878
8053
  spinner.fail(chalk15.red("No ev.yaml found."));
7879
8054
  process.exit(1);
7880
8055
  }
7881
- const configPath = join6(root, "ev.yaml");
8056
+ const configPath = join7(root, "ev.yaml");
7882
8057
  const content = await readFile5(configPath, "utf-8");
7883
8058
  const config = YAML3.parse(content);
7884
8059
  let backendConfig;
@@ -8385,8 +8560,8 @@ import { Command as Command17 } from "commander";
8385
8560
  import chalk19 from "chalk";
8386
8561
  import ora11 from "ora";
8387
8562
  import { readdir, readFile as readFile6, stat } from "fs/promises";
8388
- import { existsSync as existsSync5 } from "fs";
8389
- import { join as join7, extname } from "path";
8563
+ import { existsSync as existsSync6 } from "fs";
8564
+ import { join as join8, extname } from "path";
8390
8565
  var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
8391
8566
  ".ts",
8392
8567
  ".tsx",
@@ -8452,7 +8627,7 @@ async function scanDir(dir) {
8452
8627
  }
8453
8628
  for (const entry of entries) {
8454
8629
  if (SKIP_DIRS.has(entry)) continue;
8455
- const fullPath = join7(currentDir, entry);
8630
+ const fullPath = join8(currentDir, entry);
8456
8631
  const stats = await stat(fullPath);
8457
8632
  if (stats.isDirectory()) {
8458
8633
  await walk(fullPath);
@@ -8477,8 +8652,8 @@ async function scanDir(dir) {
8477
8652
  async function getLocalEnvKeys(dir, configEnvFiles) {
8478
8653
  const keys = /* @__PURE__ */ new Set();
8479
8654
  for (const file of configEnvFiles) {
8480
- const filePath = join7(dir, file);
8481
- if (existsSync5(filePath)) {
8655
+ const filePath = join8(dir, file);
8656
+ if (existsSync6(filePath)) {
8482
8657
  const content = await readFile6(filePath, "utf-8");
8483
8658
  const parsed = parseEnvFile(content);
8484
8659
  for (const key of Object.keys(parsed)) {
@@ -8567,7 +8742,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
8567
8742
  let totalMissing = 0;
8568
8743
  let totalVars = 0;
8569
8744
  for (const [appName, appConfig] of Object.entries(config.apps)) {
8570
- const appDir = join7(repoRoot, appConfig.path);
8745
+ const appDir = join8(repoRoot, appConfig.path);
8571
8746
  try {
8572
8747
  await stat(appDir);
8573
8748
  } catch {
@@ -8621,7 +8796,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
8621
8796
  }
8622
8797
  console.log();
8623
8798
  } else {
8624
- const scanRoot = context.app && config.apps?.[context.app] ? join7(repoRoot, config.apps[context.app].path) : cwd;
8799
+ const scanRoot = context.app && config.apps?.[context.app] ? join8(repoRoot, config.apps[context.app].path) : cwd;
8625
8800
  const refs = await scanDir(scanRoot);
8626
8801
  if (refs.size === 0) {
8627
8802
  spinner.succeed(
@@ -8670,7 +8845,7 @@ var updateCommand = new Command18("update").description("Update ev to the latest
8670
8845
  const spinner = ora12("Checking for updates...").start();
8671
8846
  try {
8672
8847
  const latest = execSync2("npm view @rowlabs/ev version", { encoding: "utf-8" }).trim();
8673
- const current = "0.3.2";
8848
+ const current = "0.4.0";
8674
8849
  if (current === latest) {
8675
8850
  spinner.succeed(chalk20.green(`Already on the latest version (${current})`));
8676
8851
  return;
@@ -8686,7 +8861,7 @@ var updateCommand = new Command18("update").description("Update ev to the latest
8686
8861
 
8687
8862
  // src/index.ts
8688
8863
  var program = new Command19();
8689
- program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.3.2");
8864
+ program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.4.0");
8690
8865
  program.addCommand(loginCommand);
8691
8866
  program.addCommand(initCommand);
8692
8867
  program.addCommand(pushCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowlabs/ev",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Git for env vars — sync environment variables across teams securely",
5
5
  "type": "module",
6
6
  "bin": {