@rowlabs/ev 0.3.1 → 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.
- package/dist/index.js +330 -146
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6306,8 +6306,12 @@ 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,
|
|
6312
6316
|
envFiles: parsed.env_files ?? [".env", ".env.local"],
|
|
6313
6317
|
apps: parsed.apps,
|
|
@@ -6693,16 +6697,18 @@ async function getDecryptedProjectKey(projectId) {
|
|
|
6693
6697
|
async function resolveEnvironmentId(projectId, appName, envName) {
|
|
6694
6698
|
const client = await createApiClient();
|
|
6695
6699
|
const project = await client.getProject(projectId);
|
|
6696
|
-
|
|
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
|
+
}
|
|
6697
6704
|
if (!app) {
|
|
6698
6705
|
console.error(chalk3.red(`App "${appName ?? "default"}" not found`));
|
|
6699
6706
|
process.exit(1);
|
|
6700
6707
|
}
|
|
6701
6708
|
const envs = await client.listEnvironments(app.id);
|
|
6702
|
-
|
|
6709
|
+
let env = envs.find((e) => e.name === envName);
|
|
6703
6710
|
if (!env) {
|
|
6704
|
-
|
|
6705
|
-
process.exit(1);
|
|
6711
|
+
env = await client.createEnvironment(app.id, envName);
|
|
6706
6712
|
}
|
|
6707
6713
|
return env.id;
|
|
6708
6714
|
}
|
|
@@ -6877,6 +6883,7 @@ var initCommand = new Command2("init").description("Initialize ev in the current
|
|
|
6877
6883
|
await saveProjectKey(project.id, sealedKey);
|
|
6878
6884
|
const configData = {
|
|
6879
6885
|
project: project.id,
|
|
6886
|
+
name,
|
|
6880
6887
|
default_env: "dev",
|
|
6881
6888
|
env_files: [".env", ".env.local"]
|
|
6882
6889
|
};
|
|
@@ -6948,6 +6955,7 @@ init_api_client();
|
|
|
6948
6955
|
init_config();
|
|
6949
6956
|
init_auth();
|
|
6950
6957
|
import { Command as Command3 } from "commander";
|
|
6958
|
+
import { join as join5 } from "path";
|
|
6951
6959
|
import chalk4 from "chalk";
|
|
6952
6960
|
import ora3 from "ora";
|
|
6953
6961
|
|
|
@@ -6988,6 +6996,70 @@ async function readEnvFiles(dir, fileNames) {
|
|
|
6988
6996
|
}
|
|
6989
6997
|
|
|
6990
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
|
+
}
|
|
6991
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) => {
|
|
6992
7064
|
const spinner = ora3("Pushing secrets...").start();
|
|
6993
7065
|
try {
|
|
@@ -6996,94 +7068,136 @@ var pushCommand = new Command3("push").description("Push local .env files to rem
|
|
|
6996
7068
|
spinner.fail(chalk4.red("No ev.yaml found. Run `ev init` first."));
|
|
6997
7069
|
process.exit(1);
|
|
6998
7070
|
}
|
|
6999
|
-
const { context, backendConfig, envFiles } = resolved;
|
|
7071
|
+
const { context, backendConfig, envFiles, repoRoot } = resolved;
|
|
7000
7072
|
const isEvBackend = !backendConfig || backendConfig.type === "ev";
|
|
7001
|
-
const
|
|
7002
|
-
const
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
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`));
|
|
7018
7109
|
}
|
|
7019
7110
|
} else {
|
|
7020
|
-
|
|
7021
|
-
|
|
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);
|
|
7022
7116
|
}
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
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(`
|
|
7028
7139
|
Pushing to ${context.app ?? "default"}:${context.env}
|
|
7029
|
-
`)
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
|
|
7034
|
-
for (const { key, value } of diff.added) {
|
|
7035
|
-
console.log(chalk4.green(`+ ${key}=${maskValue(value)}`));
|
|
7036
|
-
}
|
|
7037
|
-
for (const { key, oldValue, newValue } of diff.changed) {
|
|
7038
|
-
console.log(chalk4.yellow(`~ ${key}`));
|
|
7039
|
-
console.log(chalk4.red(` - ${maskValue(oldValue)}`));
|
|
7040
|
-
console.log(chalk4.green(` + ${maskValue(newValue)}`));
|
|
7041
|
-
}
|
|
7042
|
-
if (options?.prune) {
|
|
7043
|
-
for (const { key, value } of diff.removed) {
|
|
7044
|
-
console.log(
|
|
7045
|
-
chalk4.red(`- ${key}=${maskValue(value)}`) + chalk4.dim(" (will be removed)")
|
|
7046
|
-
);
|
|
7140
|
+
`)
|
|
7141
|
+
);
|
|
7142
|
+
if (!hasChanges) {
|
|
7143
|
+
console.log(chalk4.green("No changes to push."));
|
|
7144
|
+
return;
|
|
7047
7145
|
}
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
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
|
+
`
|
|
7051
7163
|
${chalk4.green(`+${diff.added.length}`)} ${chalk4.red(`-${diff.removed.length}`)} ${chalk4.yellow(`~${diff.changed.length}`)}
|
|
7052
7164
|
`
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7056
|
-
|
|
7057
|
-
|
|
7058
|
-
|
|
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
|
+
}
|
|
7059
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
|
+
);
|
|
7060
7200
|
}
|
|
7061
|
-
const pushSpinner = ora3("Encrypting and pushing secrets...").start();
|
|
7062
|
-
let secrets;
|
|
7063
|
-
if (isEvBackend) {
|
|
7064
|
-
if (!projectKey) projectKey = await getDecryptedProjectKey(context.project);
|
|
7065
|
-
const resolvedKey = projectKey;
|
|
7066
|
-
secrets = keys.map((key) => ({
|
|
7067
|
-
key,
|
|
7068
|
-
encryptedValue: encryptSecret(localEnv[key], resolvedKey)
|
|
7069
|
-
}));
|
|
7070
|
-
} else {
|
|
7071
|
-
secrets = keys.map((key) => ({ key, encryptedValue: localEnv[key] }));
|
|
7072
|
-
}
|
|
7073
|
-
const result = await client.pushSecrets(envId, {
|
|
7074
|
-
secrets,
|
|
7075
|
-
message: options?.message,
|
|
7076
|
-
prune: options?.prune
|
|
7077
|
-
});
|
|
7078
|
-
const parts = [];
|
|
7079
|
-
if (result.keysAdded?.length) parts.push(`${result.keysAdded.length} added`);
|
|
7080
|
-
if (result.keysChanged?.length) parts.push(`${result.keysChanged.length} changed`);
|
|
7081
|
-
if (result.keysRemoved?.length) parts.push(`${result.keysRemoved.length} removed`);
|
|
7082
|
-
pushSpinner.succeed(
|
|
7083
|
-
chalk4.green(
|
|
7084
|
-
`Pushed to ${context.app ?? "default"}:${context.env} \u2014 ${parts.join(", ") || "no changes"}`
|
|
7085
|
-
)
|
|
7086
|
-
);
|
|
7087
7201
|
} catch (err) {
|
|
7088
7202
|
ora3().fail(chalk4.red(`Push failed: ${err.message}`));
|
|
7089
7203
|
process.exit(1);
|
|
@@ -7097,9 +7211,45 @@ init_config();
|
|
|
7097
7211
|
init_auth();
|
|
7098
7212
|
import { Command as Command4 } from "commander";
|
|
7099
7213
|
import { writeFile as writeFile4, copyFile } from "fs/promises";
|
|
7100
|
-
import {
|
|
7214
|
+
import { existsSync as existsSync5 } from "fs";
|
|
7215
|
+
import { join as join6 } from "path";
|
|
7101
7216
|
import chalk5 from "chalk";
|
|
7102
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
|
+
}
|
|
7103
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) => {
|
|
7104
7254
|
const spinner = ora4("Pulling secrets...").start();
|
|
7105
7255
|
try {
|
|
@@ -7108,66 +7258,96 @@ var pullCommand = new Command4("pull").description("Pull remote secrets into loc
|
|
|
7108
7258
|
spinner.fail(chalk5.red("No ev.yaml found. Run `ev init` first."));
|
|
7109
7259
|
process.exit(1);
|
|
7110
7260
|
}
|
|
7111
|
-
const { context, backendConfig, envFiles } = resolved;
|
|
7261
|
+
const { context, backendConfig, envFiles, repoRoot } = resolved;
|
|
7112
7262
|
const isEvBackend = !backendConfig || backendConfig.type === "ev";
|
|
7113
|
-
const
|
|
7114
|
-
const
|
|
7115
|
-
const
|
|
7116
|
-
if (
|
|
7117
|
-
spinner.
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
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;
|
|
7128
7282
|
}
|
|
7283
|
+
console.log(chalk5.green(`
|
|
7284
|
+
\u2714 Pulled ${totalSecrets} total secrets across ${Object.keys(config.apps).length} apps`));
|
|
7129
7285
|
} else {
|
|
7130
|
-
|
|
7131
|
-
|
|
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;
|
|
7132
7296
|
}
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
const localOnlyKeys = diff.added;
|
|
7140
|
-
const localChangedKeys = diff.changed;
|
|
7141
|
-
if (localOnlyKeys.length > 0 || localChangedKeys.length > 0) {
|
|
7142
|
-
spinner.stop();
|
|
7143
|
-
console.log(chalk5.yellow(`Warning: Local .env has changes not on remote:`));
|
|
7144
|
-
for (const { key } of localOnlyKeys) {
|
|
7145
|
-
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);
|
|
7146
7303
|
}
|
|
7147
|
-
|
|
7148
|
-
|
|
7304
|
+
} else {
|
|
7305
|
+
for (const secret of response.secrets) {
|
|
7306
|
+
remoteEnv[secret.key] = secret.encryptedValue;
|
|
7149
7307
|
}
|
|
7150
|
-
|
|
7151
|
-
|
|
7152
|
-
|
|
7153
|
-
|
|
7154
|
-
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
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`));
|
|
7159
7341
|
}
|
|
7160
7342
|
}
|
|
7161
|
-
await copyFile(envPath, backupPath);
|
|
7162
|
-
console.log(chalk5.dim(`Backup saved to .env.backup`));
|
|
7163
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
|
+
);
|
|
7164
7350
|
}
|
|
7165
|
-
await writeFile4(envPath, serializeEnv(remoteEnv), "utf-8");
|
|
7166
|
-
spinner.succeed(
|
|
7167
|
-
chalk5.green(
|
|
7168
|
-
`Pulled ${response.secrets.length} secrets from ${context.app ?? "default"}:${context.env}`
|
|
7169
|
-
)
|
|
7170
|
-
);
|
|
7171
7351
|
} catch (err) {
|
|
7172
7352
|
ora4().fail(chalk5.red(`Pull failed: ${err.message}`));
|
|
7173
7353
|
process.exit(1);
|
|
@@ -7325,6 +7505,7 @@ var statusCommand = new Command7("status").description("Show current context").a
|
|
|
7325
7505
|
const auth = await loadAuth();
|
|
7326
7506
|
const keypair = await loadKeypair();
|
|
7327
7507
|
const resolved = await resolveCurrentContext();
|
|
7508
|
+
const config = await loadEvConfig(process.cwd());
|
|
7328
7509
|
console.log(chalk9.bold("\nev status\n"));
|
|
7329
7510
|
if (auth) console.log(` ${chalk9.green("\u25CF")} Logged in (${auth.apiUrl})`);
|
|
7330
7511
|
else console.log(` ${chalk9.red("\u25CF")} Not logged in`);
|
|
@@ -7332,9 +7513,12 @@ var statusCommand = new Command7("status").description("Show current context").a
|
|
|
7332
7513
|
else console.log(` ${chalk9.red("\u25CF")} No keypair`);
|
|
7333
7514
|
if (resolved) {
|
|
7334
7515
|
const { context } = resolved;
|
|
7335
|
-
console.log(` ${chalk9.green("\u25CF")} Project: ${context.project}`);
|
|
7516
|
+
console.log(` ${chalk9.green("\u25CF")} Project: ${config?.name ?? context.project}`);
|
|
7336
7517
|
console.log(` ${chalk9.green("\u25CF")} App: ${context.app ?? "default"}`);
|
|
7337
7518
|
console.log(` ${chalk9.green("\u25CF")} Env: ${context.env}`);
|
|
7519
|
+
if (config?.envFiles) {
|
|
7520
|
+
console.log(` ${chalk9.green("\u25CF")} Env files: ${config.envFiles.join(", ")}`);
|
|
7521
|
+
}
|
|
7338
7522
|
} else {
|
|
7339
7523
|
console.log(` ${chalk9.yellow("\u25CF")} No ev.yaml found in this directory`);
|
|
7340
7524
|
}
|
|
@@ -7797,7 +7981,7 @@ var getCommand = new Command12("get").description("Get a single secret value").a
|
|
|
7797
7981
|
init_config();
|
|
7798
7982
|
import { Command as Command13 } from "commander";
|
|
7799
7983
|
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
7800
|
-
import { join as
|
|
7984
|
+
import { join as join7 } from "path";
|
|
7801
7985
|
import chalk15 from "chalk";
|
|
7802
7986
|
import ora9 from "ora";
|
|
7803
7987
|
import YAML3 from "yaml";
|
|
@@ -7810,7 +7994,7 @@ backendCommand.command("show").description("Show current backend configuration")
|
|
|
7810
7994
|
console.error(chalk15.red("No ev.yaml found."));
|
|
7811
7995
|
process.exit(1);
|
|
7812
7996
|
}
|
|
7813
|
-
const content = await readFile5(
|
|
7997
|
+
const content = await readFile5(join7(root, "ev.yaml"), "utf-8");
|
|
7814
7998
|
const config = YAML3.parse(content);
|
|
7815
7999
|
console.log(chalk15.bold("\nBackend Configuration\n"));
|
|
7816
8000
|
const defaultBackend = config.backend ?? { type: "ev" };
|
|
@@ -7869,7 +8053,7 @@ backendCommand.command("set <type>").description("Set the default storage backen
|
|
|
7869
8053
|
spinner.fail(chalk15.red("No ev.yaml found."));
|
|
7870
8054
|
process.exit(1);
|
|
7871
8055
|
}
|
|
7872
|
-
const configPath =
|
|
8056
|
+
const configPath = join7(root, "ev.yaml");
|
|
7873
8057
|
const content = await readFile5(configPath, "utf-8");
|
|
7874
8058
|
const config = YAML3.parse(content);
|
|
7875
8059
|
let backendConfig;
|
|
@@ -8376,8 +8560,8 @@ import { Command as Command17 } from "commander";
|
|
|
8376
8560
|
import chalk19 from "chalk";
|
|
8377
8561
|
import ora11 from "ora";
|
|
8378
8562
|
import { readdir, readFile as readFile6, stat } from "fs/promises";
|
|
8379
|
-
import { existsSync as
|
|
8380
|
-
import { join as
|
|
8563
|
+
import { existsSync as existsSync6 } from "fs";
|
|
8564
|
+
import { join as join8, extname } from "path";
|
|
8381
8565
|
var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8382
8566
|
".ts",
|
|
8383
8567
|
".tsx",
|
|
@@ -8443,7 +8627,7 @@ async function scanDir(dir) {
|
|
|
8443
8627
|
}
|
|
8444
8628
|
for (const entry of entries) {
|
|
8445
8629
|
if (SKIP_DIRS.has(entry)) continue;
|
|
8446
|
-
const fullPath =
|
|
8630
|
+
const fullPath = join8(currentDir, entry);
|
|
8447
8631
|
const stats = await stat(fullPath);
|
|
8448
8632
|
if (stats.isDirectory()) {
|
|
8449
8633
|
await walk(fullPath);
|
|
@@ -8468,8 +8652,8 @@ async function scanDir(dir) {
|
|
|
8468
8652
|
async function getLocalEnvKeys(dir, configEnvFiles) {
|
|
8469
8653
|
const keys = /* @__PURE__ */ new Set();
|
|
8470
8654
|
for (const file of configEnvFiles) {
|
|
8471
|
-
const filePath =
|
|
8472
|
-
if (
|
|
8655
|
+
const filePath = join8(dir, file);
|
|
8656
|
+
if (existsSync6(filePath)) {
|
|
8473
8657
|
const content = await readFile6(filePath, "utf-8");
|
|
8474
8658
|
const parsed = parseEnvFile(content);
|
|
8475
8659
|
for (const key of Object.keys(parsed)) {
|
|
@@ -8558,7 +8742,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
|
|
|
8558
8742
|
let totalMissing = 0;
|
|
8559
8743
|
let totalVars = 0;
|
|
8560
8744
|
for (const [appName, appConfig] of Object.entries(config.apps)) {
|
|
8561
|
-
const appDir =
|
|
8745
|
+
const appDir = join8(repoRoot, appConfig.path);
|
|
8562
8746
|
try {
|
|
8563
8747
|
await stat(appDir);
|
|
8564
8748
|
} catch {
|
|
@@ -8612,7 +8796,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
|
|
|
8612
8796
|
}
|
|
8613
8797
|
console.log();
|
|
8614
8798
|
} else {
|
|
8615
|
-
const scanRoot = context.app && config.apps?.[context.app] ?
|
|
8799
|
+
const scanRoot = context.app && config.apps?.[context.app] ? join8(repoRoot, config.apps[context.app].path) : cwd;
|
|
8616
8800
|
const refs = await scanDir(scanRoot);
|
|
8617
8801
|
if (refs.size === 0) {
|
|
8618
8802
|
spinner.succeed(
|
|
@@ -8661,7 +8845,7 @@ var updateCommand = new Command18("update").description("Update ev to the latest
|
|
|
8661
8845
|
const spinner = ora12("Checking for updates...").start();
|
|
8662
8846
|
try {
|
|
8663
8847
|
const latest = execSync2("npm view @rowlabs/ev version", { encoding: "utf-8" }).trim();
|
|
8664
|
-
const current = "0.
|
|
8848
|
+
const current = "0.4.0";
|
|
8665
8849
|
if (current === latest) {
|
|
8666
8850
|
spinner.succeed(chalk20.green(`Already on the latest version (${current})`));
|
|
8667
8851
|
return;
|
|
@@ -8677,7 +8861,7 @@ var updateCommand = new Command18("update").description("Update ev to the latest
|
|
|
8677
8861
|
|
|
8678
8862
|
// src/index.ts
|
|
8679
8863
|
var program = new Command19();
|
|
8680
|
-
program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.
|
|
8864
|
+
program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.4.0");
|
|
8681
8865
|
program.addCommand(loginCommand);
|
|
8682
8866
|
program.addCommand(initCommand);
|
|
8683
8867
|
program.addCommand(pushCommand);
|