@rowlabs/ev 0.3.0 → 0.3.2

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 +100 -55
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6306,9 +6306,14 @@ function parseGitEnvConfig(content) {
6306
6306
  if (!parsed.project || typeof parsed.project !== "string") {
6307
6307
  throw new Error("Invalid ev.yaml: missing or invalid 'project' field");
6308
6308
  }
6309
+ if (!parsed.name || typeof parsed.name !== "string") {
6310
+ throw new Error("Invalid ev.yaml: missing or invalid 'name' field");
6311
+ }
6309
6312
  return {
6310
6313
  project: parsed.project,
6314
+ name: parsed.name,
6311
6315
  defaultEnv: parsed.default_env,
6316
+ envFiles: parsed.env_files ?? [".env", ".env.local"],
6312
6317
  apps: parsed.apps,
6313
6318
  backend: parsed.backend,
6314
6319
  environments: parsed.environments
@@ -6459,7 +6464,7 @@ async function resolveCurrentContext(explicitTarget) {
6459
6464
  if (!config) return null;
6460
6465
  const context = resolveContext(config, cwd, root, explicitTarget);
6461
6466
  const backendConfig = resolveBackendConfig(config, context.env);
6462
- return { context, repoRoot: root, backendConfig };
6467
+ return { context, repoRoot: root, backendConfig, envFiles: config.envFiles };
6463
6468
  }
6464
6469
  var CONFIG_DIR, AUTH_FILE;
6465
6470
  var init_config = __esm({
@@ -6717,7 +6722,7 @@ var init_auth = __esm({
6717
6722
 
6718
6723
  // src/index.ts
6719
6724
  init_dist();
6720
- import { Command as Command18 } from "commander";
6725
+ import { Command as Command19 } from "commander";
6721
6726
 
6722
6727
  // src/commands/login.ts
6723
6728
  init_dist();
@@ -6874,7 +6879,12 @@ var initCommand = new Command2("init").description("Initialize ev in the current
6874
6879
  const client = await createApiClient();
6875
6880
  const project = await client.createProject(name, sealedKey);
6876
6881
  await saveProjectKey(project.id, sealedKey);
6877
- const configData = { project: project.id, default_env: "dev" };
6882
+ const configData = {
6883
+ project: project.id,
6884
+ name,
6885
+ default_env: "dev",
6886
+ env_files: [".env", ".env.local"]
6887
+ };
6878
6888
  const yamlContent = "# ev project configuration\n# Docs: https://github.com/blaze-rowland/git-env\n\n" + YAML2.stringify(configData);
6879
6889
  await writeFile3(configPath, yamlContent, "utf-8");
6880
6890
  const appsDir = join3(cwd, "apps");
@@ -6943,9 +6953,6 @@ init_api_client();
6943
6953
  init_config();
6944
6954
  init_auth();
6945
6955
  import { Command as Command3 } from "commander";
6946
- import { readFile as readFile4 } from "fs/promises";
6947
- import { existsSync as existsSync4 } from "fs";
6948
- import { join as join4 } from "path";
6949
6956
  import chalk4 from "chalk";
6950
6957
  import ora3 from "ora";
6951
6958
 
@@ -6965,8 +6972,28 @@ function maskValue(value) {
6965
6972
  return value.slice(0, 2) + "****" + value.slice(-2);
6966
6973
  }
6967
6974
 
6975
+ // src/lib/env-files.ts
6976
+ init_dist();
6977
+ import { readFile as readFile4 } from "fs/promises";
6978
+ import { existsSync as existsSync4 } from "fs";
6979
+ import { join as join4 } from "path";
6980
+ async function readEnvFiles(dir, fileNames) {
6981
+ const vars = {};
6982
+ const files = [];
6983
+ for (const name of fileNames) {
6984
+ const filePath = join4(dir, name);
6985
+ if (existsSync4(filePath)) {
6986
+ const content = await readFile4(filePath, "utf-8");
6987
+ const parsed = parseEnvFile(content);
6988
+ Object.assign(vars, parsed);
6989
+ files.push(name);
6990
+ }
6991
+ }
6992
+ return { vars, files };
6993
+ }
6994
+
6968
6995
  // src/commands/push.ts
6969
- var pushCommand = new Command3("push").description("Push local .env 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) => {
6996
+ 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) => {
6970
6997
  const spinner = ora3("Pushing secrets...").start();
6971
6998
  try {
6972
6999
  const resolved = await resolveCurrentContext(target);
@@ -6974,20 +7001,15 @@ var pushCommand = new Command3("push").description("Push local .env to remote").
6974
7001
  spinner.fail(chalk4.red("No ev.yaml found. Run `ev init` first."));
6975
7002
  process.exit(1);
6976
7003
  }
6977
- const { context, backendConfig } = resolved;
7004
+ const { context, backendConfig, envFiles } = resolved;
6978
7005
  const isEvBackend = !backendConfig || backendConfig.type === "ev";
6979
- const envPath = join4(process.cwd(), ".env");
6980
- if (!existsSync4(envPath)) {
6981
- spinner.fail(chalk4.red("No .env file found in current directory"));
6982
- process.exit(1);
6983
- }
6984
- const envContent = await readFile4(envPath, "utf-8");
6985
- const localEnv = parseEnvFile(envContent);
7006
+ const { vars: localEnv, files } = await readEnvFiles(process.cwd(), envFiles);
6986
7007
  const keys = Object.keys(localEnv);
6987
7008
  if (keys.length === 0) {
6988
- spinner.fail(chalk4.red("No variables found in .env"));
7009
+ spinner.fail(chalk4.red("No env files found (.env, .env.local)"));
6989
7010
  process.exit(1);
6990
7011
  }
7012
+ spinner.text = `Reading from ${files.join(", ")}...`;
6991
7013
  spinner.text = "Fetching remote secrets...";
6992
7014
  const envId = await resolveEnvironmentId(context.project, context.app, context.env);
6993
7015
  const client = await createApiClient(backendConfig);
@@ -7079,8 +7101,7 @@ init_api_client();
7079
7101
  init_config();
7080
7102
  init_auth();
7081
7103
  import { Command as Command4 } from "commander";
7082
- import { readFile as readFile5, writeFile as writeFile4, copyFile } from "fs/promises";
7083
- import { existsSync as existsSync5 } from "fs";
7104
+ import { writeFile as writeFile4, copyFile } from "fs/promises";
7084
7105
  import { join as join5 } from "path";
7085
7106
  import chalk5 from "chalk";
7086
7107
  import ora4 from "ora";
@@ -7092,7 +7113,7 @@ var pullCommand = new Command4("pull").description("Pull remote secrets into loc
7092
7113
  spinner.fail(chalk5.red("No ev.yaml found. Run `ev init` first."));
7093
7114
  process.exit(1);
7094
7115
  }
7095
- const { context, backendConfig } = resolved;
7116
+ const { context, backendConfig, envFiles } = resolved;
7096
7117
  const isEvBackend = !backendConfig || backendConfig.type === "ev";
7097
7118
  const envId = await resolveEnvironmentId(context.project, context.app, context.env);
7098
7119
  const client = await createApiClient(backendConfig);
@@ -7117,9 +7138,8 @@ var pullCommand = new Command4("pull").description("Pull remote secrets into loc
7117
7138
  }
7118
7139
  const envPath = join5(process.cwd(), ".env");
7119
7140
  const backupPath = join5(process.cwd(), ".env.backup");
7120
- if (existsSync5(envPath)) {
7121
- const localContent = await readFile5(envPath, "utf-8");
7122
- const localEnv = parseEnvFile(localContent);
7141
+ const { vars: localEnv, files: localFiles } = await readEnvFiles(process.cwd(), envFiles);
7142
+ if (localFiles.length > 0) {
7123
7143
  const diff = diffEnvs(remoteEnv, localEnv);
7124
7144
  const localOnlyKeys = diff.added;
7125
7145
  const localChangedKeys = diff.changed;
@@ -7165,9 +7185,6 @@ init_api_client();
7165
7185
  init_config();
7166
7186
  init_auth();
7167
7187
  import { Command as Command5 } from "commander";
7168
- import { readFile as readFile6 } from "fs/promises";
7169
- import { existsSync as existsSync6 } from "fs";
7170
- import { join as join6 } from "path";
7171
7188
  import chalk6 from "chalk";
7172
7189
  import ora5 from "ora";
7173
7190
  var diffCommand = new Command5("diff").description("Show differences between local and remote, or between two environments").argument("[env1]", "First environment (or local vs this env)").argument("[env2]", "Second environment (for env-to-env diff)").action(async (env1, env2) => {
@@ -7178,7 +7195,7 @@ var diffCommand = new Command5("diff").description("Show differences between loc
7178
7195
  spinner.fail(chalk6.red("No ev.yaml found. Run `ev init` first."));
7179
7196
  process.exit(1);
7180
7197
  }
7181
- const { context } = resolved;
7198
+ const { context, envFiles } = resolved;
7182
7199
  const projectKey = await getDecryptedProjectKey(context.project);
7183
7200
  const client = await createApiClient();
7184
7201
  let base;
@@ -7199,13 +7216,12 @@ var diffCommand = new Command5("diff").description("Show differences between loc
7199
7216
  baseLabel = env1;
7200
7217
  targetLabel = env2;
7201
7218
  } else {
7202
- const envPath = join6(process.cwd(), ".env");
7203
- if (!existsSync6(envPath)) {
7204
- spinner.fail(chalk6.red("No .env file found"));
7219
+ const { vars: localVars, files } = await readEnvFiles(process.cwd(), envFiles);
7220
+ if (files.length === 0) {
7221
+ spinner.fail(chalk6.red("No env files found"));
7205
7222
  process.exit(1);
7206
7223
  }
7207
- const localContent = await readFile6(envPath, "utf-8");
7208
- base = parseEnvFile(localContent);
7224
+ base = localVars;
7209
7225
  const targetEnv = env1 ?? context.env;
7210
7226
  const envId = await resolveEnvironmentId(context.project, context.app, targetEnv);
7211
7227
  const remoteRes = await client.pullSecrets(envId);
@@ -7314,6 +7330,7 @@ var statusCommand = new Command7("status").description("Show current context").a
7314
7330
  const auth = await loadAuth();
7315
7331
  const keypair = await loadKeypair();
7316
7332
  const resolved = await resolveCurrentContext();
7333
+ const config = await loadEvConfig(process.cwd());
7317
7334
  console.log(chalk9.bold("\nev status\n"));
7318
7335
  if (auth) console.log(` ${chalk9.green("\u25CF")} Logged in (${auth.apiUrl})`);
7319
7336
  else console.log(` ${chalk9.red("\u25CF")} Not logged in`);
@@ -7321,9 +7338,12 @@ var statusCommand = new Command7("status").description("Show current context").a
7321
7338
  else console.log(` ${chalk9.red("\u25CF")} No keypair`);
7322
7339
  if (resolved) {
7323
7340
  const { context } = resolved;
7324
- console.log(` ${chalk9.green("\u25CF")} Project: ${context.project}`);
7341
+ console.log(` ${chalk9.green("\u25CF")} Project: ${config?.name ?? context.project}`);
7325
7342
  console.log(` ${chalk9.green("\u25CF")} App: ${context.app ?? "default"}`);
7326
7343
  console.log(` ${chalk9.green("\u25CF")} Env: ${context.env}`);
7344
+ if (config?.envFiles) {
7345
+ console.log(` ${chalk9.green("\u25CF")} Env files: ${config.envFiles.join(", ")}`);
7346
+ }
7327
7347
  } else {
7328
7348
  console.log(` ${chalk9.yellow("\u25CF")} No ev.yaml found in this directory`);
7329
7349
  }
@@ -7785,8 +7805,8 @@ var getCommand = new Command12("get").description("Get a single secret value").a
7785
7805
  // src/commands/backend.ts
7786
7806
  init_config();
7787
7807
  import { Command as Command13 } from "commander";
7788
- import { readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
7789
- import { join as join7 } from "path";
7808
+ import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
7809
+ import { join as join6 } from "path";
7790
7810
  import chalk15 from "chalk";
7791
7811
  import ora9 from "ora";
7792
7812
  import YAML3 from "yaml";
@@ -7799,7 +7819,7 @@ backendCommand.command("show").description("Show current backend configuration")
7799
7819
  console.error(chalk15.red("No ev.yaml found."));
7800
7820
  process.exit(1);
7801
7821
  }
7802
- const content = await readFile7(join7(root, "ev.yaml"), "utf-8");
7822
+ const content = await readFile5(join6(root, "ev.yaml"), "utf-8");
7803
7823
  const config = YAML3.parse(content);
7804
7824
  console.log(chalk15.bold("\nBackend Configuration\n"));
7805
7825
  const defaultBackend = config.backend ?? { type: "ev" };
@@ -7858,8 +7878,8 @@ backendCommand.command("set <type>").description("Set the default storage backen
7858
7878
  spinner.fail(chalk15.red("No ev.yaml found."));
7859
7879
  process.exit(1);
7860
7880
  }
7861
- const configPath = join7(root, "ev.yaml");
7862
- const content = await readFile7(configPath, "utf-8");
7881
+ const configPath = join6(root, "ev.yaml");
7882
+ const content = await readFile5(configPath, "utf-8");
7863
7883
  const config = YAML3.parse(content);
7864
7884
  let backendConfig;
7865
7885
  if (type === "ev") {
@@ -8205,6 +8225,7 @@ _ev() {
8205
8225
  'import:Import from external provider'
8206
8226
  'status:Show current context'
8207
8227
  'scan:Scan codebase for env var references'
8228
+ 'update:Update ev to latest version'
8208
8229
  'doctor:Check setup for issues'
8209
8230
  'completions:Generate shell completions'
8210
8231
  )
@@ -8259,7 +8280,7 @@ var BASH_COMPLETION = `_ev() {
8259
8280
  cur="\${COMP_WORDS[COMP_CWORD]}"
8260
8281
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
8261
8282
 
8262
- commands="login init push pull diff log get rollback env promote access backend import status scan doctor completions"
8283
+ commands="login init push pull diff log get rollback env promote access backend import status scan doctor completions update"
8263
8284
 
8264
8285
  case "\${prev}" in
8265
8286
  ev)
@@ -8310,6 +8331,7 @@ complete -c ev -n '__fish_use_subcommand' -a backend -d 'Configure storage backe
8310
8331
  complete -c ev -n '__fish_use_subcommand' -a import -d 'Import from external provider'
8311
8332
  complete -c ev -n '__fish_use_subcommand' -a status -d 'Show current context'
8312
8333
  complete -c ev -n '__fish_use_subcommand' -a scan -d 'Scan for env var references'
8334
+ complete -c ev -n '__fish_use_subcommand' -a update -d 'Update ev to latest version'
8313
8335
  complete -c ev -n '__fish_use_subcommand' -a doctor -d 'Check setup for issues'
8314
8336
  complete -c ev -n '__fish_use_subcommand' -a completions -d 'Generate shell completions'
8315
8337
 
@@ -8362,9 +8384,9 @@ init_config();
8362
8384
  import { Command as Command17 } from "commander";
8363
8385
  import chalk19 from "chalk";
8364
8386
  import ora11 from "ora";
8365
- import { readdir, readFile as readFile8, stat } from "fs/promises";
8366
- import { existsSync as existsSync7 } from "fs";
8367
- import { join as join8, extname } from "path";
8387
+ import { readdir, readFile as readFile6, stat } from "fs/promises";
8388
+ import { existsSync as existsSync5 } from "fs";
8389
+ import { join as join7, extname } from "path";
8368
8390
  var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
8369
8391
  ".ts",
8370
8392
  ".tsx",
@@ -8430,12 +8452,12 @@ async function scanDir(dir) {
8430
8452
  }
8431
8453
  for (const entry of entries) {
8432
8454
  if (SKIP_DIRS.has(entry)) continue;
8433
- const fullPath = join8(currentDir, entry);
8455
+ const fullPath = join7(currentDir, entry);
8434
8456
  const stats = await stat(fullPath);
8435
8457
  if (stats.isDirectory()) {
8436
8458
  await walk(fullPath);
8437
8459
  } else if (SCAN_EXTENSIONS.has(extname(entry))) {
8438
- const content = await readFile8(fullPath, "utf-8");
8460
+ const content = await readFile6(fullPath, "utf-8");
8439
8461
  for (const pattern of ENV_PATTERNS) {
8440
8462
  pattern.lastIndex = 0;
8441
8463
  let match;
@@ -8452,13 +8474,12 @@ async function scanDir(dir) {
8452
8474
  await walk(dir);
8453
8475
  return results;
8454
8476
  }
8455
- async function getLocalEnvKeys(dir) {
8477
+ async function getLocalEnvKeys(dir, configEnvFiles) {
8456
8478
  const keys = /* @__PURE__ */ new Set();
8457
- const envFiles = [".env", ".env.local"];
8458
- for (const file of envFiles) {
8459
- const filePath = join8(dir, file);
8460
- if (existsSync7(filePath)) {
8461
- const content = await readFile8(filePath, "utf-8");
8479
+ for (const file of configEnvFiles) {
8480
+ const filePath = join7(dir, file);
8481
+ if (existsSync5(filePath)) {
8482
+ const content = await readFile6(filePath, "utf-8");
8462
8483
  const parsed = parseEnvFile(content);
8463
8484
  for (const key of Object.keys(parsed)) {
8464
8485
  keys.add(key);
@@ -8546,7 +8567,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
8546
8567
  let totalMissing = 0;
8547
8568
  let totalVars = 0;
8548
8569
  for (const [appName, appConfig] of Object.entries(config.apps)) {
8549
- const appDir = join8(repoRoot, appConfig.path);
8570
+ const appDir = join7(repoRoot, appConfig.path);
8550
8571
  try {
8551
8572
  await stat(appDir);
8552
8573
  } catch {
@@ -8569,7 +8590,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
8569
8590
  } else {
8570
8591
  envData = allEnvData;
8571
8592
  }
8572
- const localKeys = await getLocalEnvKeys(appDir);
8593
+ const localKeys = await getLocalEnvKeys(appDir, config.envFiles);
8573
8594
  spinner.stop();
8574
8595
  const missing = printTable(
8575
8596
  appName,
@@ -8600,7 +8621,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
8600
8621
  }
8601
8622
  console.log();
8602
8623
  } else {
8603
- const scanRoot = context.app && config.apps?.[context.app] ? join8(repoRoot, config.apps[context.app].path) : cwd;
8624
+ const scanRoot = context.app && config.apps?.[context.app] ? join7(repoRoot, config.apps[context.app].path) : cwd;
8604
8625
  const refs = await scanDir(scanRoot);
8605
8626
  if (refs.size === 0) {
8606
8627
  spinner.succeed(
@@ -8609,7 +8630,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
8609
8630
  return;
8610
8631
  }
8611
8632
  spinner.text = `Found ${refs.size} variables. Checking environments...`;
8612
- const localKeys = await getLocalEnvKeys(scanRoot);
8633
+ const localKeys = await getLocalEnvKeys(scanRoot, config.envFiles);
8613
8634
  spinner.stop();
8614
8635
  const label = context.app ?? "project";
8615
8636
  const missingCount = printTable(
@@ -8640,9 +8661,32 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
8640
8661
  }
8641
8662
  });
8642
8663
 
8664
+ // src/commands/update.ts
8665
+ import { Command as Command18 } from "commander";
8666
+ import { execSync as execSync2 } from "child_process";
8667
+ import chalk20 from "chalk";
8668
+ import ora12 from "ora";
8669
+ var updateCommand = new Command18("update").description("Update ev to the latest version").action(async () => {
8670
+ const spinner = ora12("Checking for updates...").start();
8671
+ try {
8672
+ const latest = execSync2("npm view @rowlabs/ev version", { encoding: "utf-8" }).trim();
8673
+ const current = "0.3.2";
8674
+ if (current === latest) {
8675
+ spinner.succeed(chalk20.green(`Already on the latest version (${current})`));
8676
+ return;
8677
+ }
8678
+ spinner.text = `Updating ${current} \u2192 ${latest}...`;
8679
+ execSync2("npm i -g @rowlabs/ev@latest", { stdio: "pipe" });
8680
+ spinner.succeed(chalk20.green(`Updated to ${latest}`));
8681
+ } catch (err) {
8682
+ spinner.fail(chalk20.red(`Update failed: ${err.message}`));
8683
+ process.exit(1);
8684
+ }
8685
+ });
8686
+
8643
8687
  // src/index.ts
8644
- var program = new Command18();
8645
- program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.3.0");
8688
+ var program = new Command19();
8689
+ program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.3.2");
8646
8690
  program.addCommand(loginCommand);
8647
8691
  program.addCommand(initCommand);
8648
8692
  program.addCommand(pushCommand);
@@ -8660,6 +8704,7 @@ program.addCommand(importCommand);
8660
8704
  program.addCommand(doctorCommand);
8661
8705
  program.addCommand(completionsCommand);
8662
8706
  program.addCommand(scanCommand);
8707
+ program.addCommand(updateCommand);
8663
8708
  async function main() {
8664
8709
  await initCrypto();
8665
8710
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rowlabs/ev",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Git for env vars — sync environment variables across teams securely",
5
5
  "type": "module",
6
6
  "bin": {