@rowlabs/ev 0.3.2 → 0.4.1
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 +331 -144
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6577,6 +6577,11 @@ var init_api_client = __esm({
|
|
|
6577
6577
|
githubRepo
|
|
6578
6578
|
});
|
|
6579
6579
|
}
|
|
6580
|
+
async updateProjectName(projectId, name) {
|
|
6581
|
+
return this.request("PATCH", `/projects/${projectId}`, {
|
|
6582
|
+
name
|
|
6583
|
+
});
|
|
6584
|
+
}
|
|
6580
6585
|
async listProjects() {
|
|
6581
6586
|
return this.request("GET", "/projects");
|
|
6582
6587
|
}
|
|
@@ -6697,16 +6702,18 @@ async function getDecryptedProjectKey(projectId) {
|
|
|
6697
6702
|
async function resolveEnvironmentId(projectId, appName, envName) {
|
|
6698
6703
|
const client = await createApiClient();
|
|
6699
6704
|
const project = await client.getProject(projectId);
|
|
6700
|
-
|
|
6705
|
+
let app = appName ? project.apps.find((a2) => a2.name === appName) : project.apps.find((a2) => a2.name === "default") ?? project.apps[0];
|
|
6706
|
+
if (!app && appName) {
|
|
6707
|
+
app = await client.createApp(projectId, appName);
|
|
6708
|
+
}
|
|
6701
6709
|
if (!app) {
|
|
6702
6710
|
console.error(chalk3.red(`App "${appName ?? "default"}" not found`));
|
|
6703
6711
|
process.exit(1);
|
|
6704
6712
|
}
|
|
6705
6713
|
const envs = await client.listEnvironments(app.id);
|
|
6706
|
-
|
|
6714
|
+
let env = envs.find((e) => e.name === envName);
|
|
6707
6715
|
if (!env) {
|
|
6708
|
-
|
|
6709
|
-
process.exit(1);
|
|
6716
|
+
env = await client.createEnvironment(app.id, envName);
|
|
6710
6717
|
}
|
|
6711
6718
|
return env.id;
|
|
6712
6719
|
}
|
|
@@ -6953,6 +6960,7 @@ init_api_client();
|
|
|
6953
6960
|
init_config();
|
|
6954
6961
|
init_auth();
|
|
6955
6962
|
import { Command as Command3 } from "commander";
|
|
6963
|
+
import { join as join5 } from "path";
|
|
6956
6964
|
import chalk4 from "chalk";
|
|
6957
6965
|
import ora3 from "ora";
|
|
6958
6966
|
|
|
@@ -6993,6 +7001,70 @@ async function readEnvFiles(dir, fileNames) {
|
|
|
6993
7001
|
}
|
|
6994
7002
|
|
|
6995
7003
|
// src/commands/push.ts
|
|
7004
|
+
async function pushSingleApp(projectId, appName, envName, dir, envFileNames, options, isEvBackend, backendConfig) {
|
|
7005
|
+
const { vars: localEnv, files } = await readEnvFiles(dir, envFileNames);
|
|
7006
|
+
const keys = Object.keys(localEnv);
|
|
7007
|
+
if (keys.length === 0) return false;
|
|
7008
|
+
const label = `${appName ?? "default"}:${envName}`;
|
|
7009
|
+
const envId = await resolveEnvironmentId(projectId, appName, envName);
|
|
7010
|
+
const client = await createApiClient(backendConfig);
|
|
7011
|
+
const remoteResponse = await client.pullSecrets(envId);
|
|
7012
|
+
const remoteEnv = {};
|
|
7013
|
+
let projectKey;
|
|
7014
|
+
if (isEvBackend) {
|
|
7015
|
+
projectKey = await getDecryptedProjectKey(projectId);
|
|
7016
|
+
for (const secret of remoteResponse.secrets) {
|
|
7017
|
+
remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
|
|
7018
|
+
}
|
|
7019
|
+
} else {
|
|
7020
|
+
for (const secret of remoteResponse.secrets) {
|
|
7021
|
+
remoteEnv[secret.key] = secret.encryptedValue;
|
|
7022
|
+
}
|
|
7023
|
+
}
|
|
7024
|
+
const diff = diffEnvs(remoteEnv, localEnv);
|
|
7025
|
+
const hasChanges = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
|
|
7026
|
+
if (!hasChanges) {
|
|
7027
|
+
console.log(chalk4.dim(` ${label} \u2014 no changes (${files.join(", ")})`));
|
|
7028
|
+
return true;
|
|
7029
|
+
}
|
|
7030
|
+
console.log(chalk4.bold(`
|
|
7031
|
+
${label}`) + chalk4.dim(` (${files.join(", ")})`));
|
|
7032
|
+
for (const { key, value } of diff.added) {
|
|
7033
|
+
console.log(chalk4.green(` + ${key}=${maskValue(value)}`));
|
|
7034
|
+
}
|
|
7035
|
+
for (const { key, oldValue, newValue } of diff.changed) {
|
|
7036
|
+
console.log(chalk4.yellow(` ~ ${key}`));
|
|
7037
|
+
console.log(chalk4.red(` - ${maskValue(oldValue)}`));
|
|
7038
|
+
console.log(chalk4.green(` + ${maskValue(newValue)}`));
|
|
7039
|
+
}
|
|
7040
|
+
if (options.prune) {
|
|
7041
|
+
for (const { key, value } of diff.removed) {
|
|
7042
|
+
console.log(
|
|
7043
|
+
chalk4.red(` - ${key}=${maskValue(value)}`) + chalk4.dim(" (will be removed)")
|
|
7044
|
+
);
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
console.log(
|
|
7048
|
+
` ${chalk4.green(`+${diff.added.length}`)} ${chalk4.red(`-${diff.removed.length}`)} ${chalk4.yellow(`~${diff.changed.length}`)}`
|
|
7049
|
+
);
|
|
7050
|
+
let secrets;
|
|
7051
|
+
if (isEvBackend) {
|
|
7052
|
+
if (!projectKey) projectKey = await getDecryptedProjectKey(projectId);
|
|
7053
|
+
const resolvedKey = projectKey;
|
|
7054
|
+
secrets = keys.map((key) => ({
|
|
7055
|
+
key,
|
|
7056
|
+
encryptedValue: encryptSecret(localEnv[key], resolvedKey)
|
|
7057
|
+
}));
|
|
7058
|
+
} else {
|
|
7059
|
+
secrets = keys.map((key) => ({ key, encryptedValue: localEnv[key] }));
|
|
7060
|
+
}
|
|
7061
|
+
await client.pushSecrets(envId, {
|
|
7062
|
+
secrets,
|
|
7063
|
+
message: options.message,
|
|
7064
|
+
prune: options.prune
|
|
7065
|
+
});
|
|
7066
|
+
return true;
|
|
7067
|
+
}
|
|
6996
7068
|
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
7069
|
const spinner = ora3("Pushing secrets...").start();
|
|
6998
7070
|
try {
|
|
@@ -7001,94 +7073,143 @@ var pushCommand = new Command3("push").description("Push local .env files to rem
|
|
|
7001
7073
|
spinner.fail(chalk4.red("No ev.yaml found. Run `ev init` first."));
|
|
7002
7074
|
process.exit(1);
|
|
7003
7075
|
}
|
|
7004
|
-
const { context, backendConfig, envFiles } = resolved;
|
|
7076
|
+
const { context, backendConfig, envFiles, repoRoot } = resolved;
|
|
7005
7077
|
const isEvBackend = !backendConfig || backendConfig.type === "ev";
|
|
7006
|
-
const
|
|
7007
|
-
const
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7078
|
+
const config = await loadEvConfig(process.cwd());
|
|
7079
|
+
const cwd = process.cwd();
|
|
7080
|
+
const isAtRoot = cwd === repoRoot;
|
|
7081
|
+
if (config?.name) {
|
|
7082
|
+
try {
|
|
7083
|
+
const client = await createApiClient(backendConfig);
|
|
7084
|
+
await client.updateProjectName(context.project, config.name);
|
|
7085
|
+
} catch {
|
|
7086
|
+
}
|
|
7011
7087
|
}
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7088
|
+
if (isAtRoot && config?.apps && Object.keys(config.apps).length > 0) {
|
|
7089
|
+
spinner.text = "Pushing all apps...";
|
|
7090
|
+
spinner.stop();
|
|
7091
|
+
let totalPushed = 0;
|
|
7092
|
+
const opts = options ?? { prune: false, yes: false };
|
|
7093
|
+
for (const [appName, appConfig] of Object.entries(config.apps)) {
|
|
7094
|
+
const appDir = join5(repoRoot, appConfig.path);
|
|
7095
|
+
const pushed = await pushSingleApp(
|
|
7096
|
+
context.project,
|
|
7097
|
+
appName,
|
|
7098
|
+
context.env,
|
|
7099
|
+
appDir,
|
|
7100
|
+
envFiles,
|
|
7101
|
+
opts,
|
|
7102
|
+
isEvBackend,
|
|
7103
|
+
backendConfig
|
|
7104
|
+
);
|
|
7105
|
+
if (pushed) totalPushed++;
|
|
7106
|
+
}
|
|
7107
|
+
if (totalPushed === 0) {
|
|
7108
|
+
console.log(chalk4.yellow("\nNo env files found in any app directory."));
|
|
7109
|
+
} else if (!opts.yes) {
|
|
7110
|
+
console.log();
|
|
7111
|
+
const answer = await prompt("Confirm push all? (y/N) ");
|
|
7112
|
+
if (answer.toLowerCase() !== "y") {
|
|
7113
|
+
console.log(chalk4.yellow("Push cancelled."));
|
|
7114
|
+
process.exit(0);
|
|
7115
|
+
}
|
|
7116
|
+
console.log(chalk4.green(`
|
|
7117
|
+
\u2714 Pushed ${totalPushed} apps`));
|
|
7118
|
+
} else {
|
|
7119
|
+
console.log(chalk4.green(`
|
|
7120
|
+
\u2714 Pushed ${totalPushed} apps`));
|
|
7023
7121
|
}
|
|
7024
7122
|
} else {
|
|
7025
|
-
|
|
7026
|
-
|
|
7123
|
+
const { vars: localEnv, files } = await readEnvFiles(cwd, envFiles);
|
|
7124
|
+
const keys = Object.keys(localEnv);
|
|
7125
|
+
if (keys.length === 0) {
|
|
7126
|
+
spinner.fail(chalk4.red("No env files found (.env, .env.local)"));
|
|
7127
|
+
process.exit(1);
|
|
7027
7128
|
}
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7129
|
+
spinner.text = `Reading from ${files.join(", ")}...`;
|
|
7130
|
+
spinner.text = "Fetching remote secrets...";
|
|
7131
|
+
const envId = await resolveEnvironmentId(context.project, context.app, context.env);
|
|
7132
|
+
const client = await createApiClient(backendConfig);
|
|
7133
|
+
const remoteResponse = await client.pullSecrets(envId);
|
|
7134
|
+
const remoteEnv = {};
|
|
7135
|
+
let projectKey;
|
|
7136
|
+
if (isEvBackend) {
|
|
7137
|
+
projectKey = await getDecryptedProjectKey(context.project);
|
|
7138
|
+
for (const secret of remoteResponse.secrets) {
|
|
7139
|
+
remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
|
|
7140
|
+
}
|
|
7141
|
+
} else {
|
|
7142
|
+
for (const secret of remoteResponse.secrets) {
|
|
7143
|
+
remoteEnv[secret.key] = secret.encryptedValue;
|
|
7144
|
+
}
|
|
7145
|
+
}
|
|
7146
|
+
const diff = diffEnvs(remoteEnv, localEnv);
|
|
7147
|
+
const hasChanges = diff.added.length > 0 || diff.removed.length > 0 || diff.changed.length > 0;
|
|
7148
|
+
spinner.stop();
|
|
7149
|
+
console.log(
|
|
7150
|
+
chalk4.bold(`
|
|
7033
7151
|
Pushing to ${context.app ?? "default"}:${context.env}
|
|
7034
|
-
`)
|
|
7035
|
-
|
|
7036
|
-
|
|
7037
|
-
|
|
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
|
-
);
|
|
7152
|
+
`)
|
|
7153
|
+
);
|
|
7154
|
+
if (!hasChanges) {
|
|
7155
|
+
console.log(chalk4.green("No changes to push."));
|
|
7156
|
+
return;
|
|
7052
7157
|
}
|
|
7053
|
-
|
|
7054
|
-
|
|
7055
|
-
|
|
7158
|
+
for (const { key, value } of diff.added) {
|
|
7159
|
+
console.log(chalk4.green(`+ ${key}=${maskValue(value)}`));
|
|
7160
|
+
}
|
|
7161
|
+
for (const { key, oldValue, newValue } of diff.changed) {
|
|
7162
|
+
console.log(chalk4.yellow(`~ ${key}`));
|
|
7163
|
+
console.log(chalk4.red(` - ${maskValue(oldValue)}`));
|
|
7164
|
+
console.log(chalk4.green(` + ${maskValue(newValue)}`));
|
|
7165
|
+
}
|
|
7166
|
+
if (options?.prune) {
|
|
7167
|
+
for (const { key, value } of diff.removed) {
|
|
7168
|
+
console.log(
|
|
7169
|
+
chalk4.red(`- ${key}=${maskValue(value)}`) + chalk4.dim(" (will be removed)")
|
|
7170
|
+
);
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
console.log(
|
|
7174
|
+
`
|
|
7056
7175
|
${chalk4.green(`+${diff.added.length}`)} ${chalk4.red(`-${diff.removed.length}`)} ${chalk4.yellow(`~${diff.changed.length}`)}
|
|
7057
7176
|
`
|
|
7058
|
-
|
|
7059
|
-
|
|
7060
|
-
|
|
7061
|
-
|
|
7062
|
-
|
|
7063
|
-
|
|
7177
|
+
);
|
|
7178
|
+
if (!options?.yes) {
|
|
7179
|
+
const answer = await prompt("Confirm push? (y/N) ");
|
|
7180
|
+
if (answer.toLowerCase() !== "y") {
|
|
7181
|
+
console.log(chalk4.yellow("Push cancelled."));
|
|
7182
|
+
process.exit(0);
|
|
7183
|
+
}
|
|
7064
7184
|
}
|
|
7185
|
+
const pushSpinner = ora3("Encrypting and pushing secrets...").start();
|
|
7186
|
+
let secrets;
|
|
7187
|
+
if (isEvBackend) {
|
|
7188
|
+
if (!projectKey) projectKey = await getDecryptedProjectKey(context.project);
|
|
7189
|
+
const resolvedKey = projectKey;
|
|
7190
|
+
secrets = keys.map((key) => ({
|
|
7191
|
+
key,
|
|
7192
|
+
encryptedValue: encryptSecret(localEnv[key], resolvedKey)
|
|
7193
|
+
}));
|
|
7194
|
+
} else {
|
|
7195
|
+
secrets = keys.map((key) => ({ key, encryptedValue: localEnv[key] }));
|
|
7196
|
+
}
|
|
7197
|
+
await client.pushSecrets(envId, {
|
|
7198
|
+
secrets,
|
|
7199
|
+
message: options?.message,
|
|
7200
|
+
prune: options?.prune
|
|
7201
|
+
});
|
|
7202
|
+
const result = { keysAdded: diff.added, keysChanged: diff.changed, keysRemoved: diff.removed };
|
|
7203
|
+
const parts = [];
|
|
7204
|
+
if (result.keysAdded.length) parts.push(`${result.keysAdded.length} added`);
|
|
7205
|
+
if (result.keysChanged.length) parts.push(`${result.keysChanged.length} changed`);
|
|
7206
|
+
if (result.keysRemoved.length) parts.push(`${result.keysRemoved.length} removed`);
|
|
7207
|
+
pushSpinner.succeed(
|
|
7208
|
+
chalk4.green(
|
|
7209
|
+
`Pushed to ${context.app ?? "default"}:${context.env} \u2014 ${parts.join(", ") || "no changes"}`
|
|
7210
|
+
)
|
|
7211
|
+
);
|
|
7065
7212
|
}
|
|
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
7213
|
} catch (err) {
|
|
7093
7214
|
ora3().fail(chalk4.red(`Push failed: ${err.message}`));
|
|
7094
7215
|
process.exit(1);
|
|
@@ -7102,9 +7223,45 @@ init_config();
|
|
|
7102
7223
|
init_auth();
|
|
7103
7224
|
import { Command as Command4 } from "commander";
|
|
7104
7225
|
import { writeFile as writeFile4, copyFile } from "fs/promises";
|
|
7105
|
-
import {
|
|
7226
|
+
import { existsSync as existsSync5 } from "fs";
|
|
7227
|
+
import { join as join6 } from "path";
|
|
7106
7228
|
import chalk5 from "chalk";
|
|
7107
7229
|
import ora4 from "ora";
|
|
7230
|
+
async function pullSingleApp(projectId, appName, envName, dir, envFileNames, isEvBackend, backendConfig) {
|
|
7231
|
+
const label = `${appName ?? "default"}:${envName}`;
|
|
7232
|
+
const envId = await resolveEnvironmentId(projectId, appName, envName);
|
|
7233
|
+
const client = await createApiClient(backendConfig);
|
|
7234
|
+
const response = await client.pullSecrets(envId);
|
|
7235
|
+
if (response.secrets.length === 0) {
|
|
7236
|
+
console.log(chalk5.dim(` ${label} \u2014 no secrets`));
|
|
7237
|
+
return 0;
|
|
7238
|
+
}
|
|
7239
|
+
const remoteEnv = {};
|
|
7240
|
+
if (isEvBackend) {
|
|
7241
|
+
const projectKey = await getDecryptedProjectKey(projectId);
|
|
7242
|
+
for (const secret of response.secrets) {
|
|
7243
|
+
remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
|
|
7244
|
+
}
|
|
7245
|
+
} else {
|
|
7246
|
+
for (const secret of response.secrets) {
|
|
7247
|
+
remoteEnv[secret.key] = secret.encryptedValue;
|
|
7248
|
+
}
|
|
7249
|
+
}
|
|
7250
|
+
const envPath = join6(dir, ".env");
|
|
7251
|
+
const backupPath = join6(dir, ".env.backup");
|
|
7252
|
+
const { vars: localEnv, files: localFiles } = await readEnvFiles(dir, envFileNames);
|
|
7253
|
+
if (localFiles.length > 0) {
|
|
7254
|
+
const diff = diffEnvs(remoteEnv, localEnv);
|
|
7255
|
+
if (diff.added.length > 0 || diff.changed.length > 0) {
|
|
7256
|
+
if (existsSync5(envPath)) {
|
|
7257
|
+
await copyFile(envPath, backupPath);
|
|
7258
|
+
}
|
|
7259
|
+
}
|
|
7260
|
+
}
|
|
7261
|
+
await writeFile4(envPath, serializeEnv(remoteEnv), "utf-8");
|
|
7262
|
+
console.log(chalk5.green(` \u2714 ${label} \u2014 ${response.secrets.length} secrets \u2192 ${dir}`));
|
|
7263
|
+
return response.secrets.length;
|
|
7264
|
+
}
|
|
7108
7265
|
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
7266
|
const spinner = ora4("Pulling secrets...").start();
|
|
7110
7267
|
try {
|
|
@@ -7113,66 +7270,96 @@ var pullCommand = new Command4("pull").description("Pull remote secrets into loc
|
|
|
7113
7270
|
spinner.fail(chalk5.red("No ev.yaml found. Run `ev init` first."));
|
|
7114
7271
|
process.exit(1);
|
|
7115
7272
|
}
|
|
7116
|
-
const { context, backendConfig, envFiles } = resolved;
|
|
7273
|
+
const { context, backendConfig, envFiles, repoRoot } = resolved;
|
|
7117
7274
|
const isEvBackend = !backendConfig || backendConfig.type === "ev";
|
|
7118
|
-
const
|
|
7119
|
-
const
|
|
7120
|
-
const
|
|
7121
|
-
if (
|
|
7122
|
-
spinner.
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7275
|
+
const config = await loadEvConfig(process.cwd());
|
|
7276
|
+
const cwd = process.cwd();
|
|
7277
|
+
const isAtRoot = cwd === repoRoot;
|
|
7278
|
+
if (isAtRoot && config?.apps && Object.keys(config.apps).length > 0) {
|
|
7279
|
+
spinner.stop();
|
|
7280
|
+
console.log(chalk5.bold("\nPulling all apps...\n"));
|
|
7281
|
+
let totalSecrets = 0;
|
|
7282
|
+
for (const [appName, appConfig] of Object.entries(config.apps)) {
|
|
7283
|
+
const appDir = join6(repoRoot, appConfig.path);
|
|
7284
|
+
const count = await pullSingleApp(
|
|
7285
|
+
context.project,
|
|
7286
|
+
appName,
|
|
7287
|
+
context.env,
|
|
7288
|
+
appDir,
|
|
7289
|
+
envFiles,
|
|
7290
|
+
isEvBackend,
|
|
7291
|
+
backendConfig
|
|
7292
|
+
);
|
|
7293
|
+
totalSecrets += count;
|
|
7133
7294
|
}
|
|
7295
|
+
console.log(chalk5.green(`
|
|
7296
|
+
\u2714 Pulled ${totalSecrets} total secrets across ${Object.keys(config.apps).length} apps`));
|
|
7134
7297
|
} else {
|
|
7135
|
-
|
|
7136
|
-
|
|
7298
|
+
const envId = await resolveEnvironmentId(context.project, context.app, context.env);
|
|
7299
|
+
const client = await createApiClient(backendConfig);
|
|
7300
|
+
const response = await client.pullSecrets(envId);
|
|
7301
|
+
if (response.secrets.length === 0) {
|
|
7302
|
+
spinner.warn(
|
|
7303
|
+
chalk5.yellow(
|
|
7304
|
+
`No secrets found in ${context.app ?? "default"}:${context.env}`
|
|
7305
|
+
)
|
|
7306
|
+
);
|
|
7307
|
+
return;
|
|
7137
7308
|
}
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
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}`));
|
|
7309
|
+
spinner.text = "Decrypting secrets...";
|
|
7310
|
+
const remoteEnv = {};
|
|
7311
|
+
if (isEvBackend) {
|
|
7312
|
+
const projectKey = await getDecryptedProjectKey(context.project);
|
|
7313
|
+
for (const secret of response.secrets) {
|
|
7314
|
+
remoteEnv[secret.key] = decryptSecret(secret.encryptedValue, projectKey);
|
|
7151
7315
|
}
|
|
7152
|
-
|
|
7153
|
-
|
|
7316
|
+
} else {
|
|
7317
|
+
for (const secret of response.secrets) {
|
|
7318
|
+
remoteEnv[secret.key] = secret.encryptedValue;
|
|
7154
7319
|
}
|
|
7155
|
-
|
|
7156
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
7161
|
-
|
|
7162
|
-
|
|
7163
|
-
|
|
7320
|
+
}
|
|
7321
|
+
const envPath = join6(cwd, ".env");
|
|
7322
|
+
const backupPath = join6(cwd, ".env.backup");
|
|
7323
|
+
const { vars: localEnv, files: localFiles } = await readEnvFiles(cwd, envFiles);
|
|
7324
|
+
if (localFiles.length > 0) {
|
|
7325
|
+
const diff = diffEnvs(remoteEnv, localEnv);
|
|
7326
|
+
const localOnlyKeys = diff.added;
|
|
7327
|
+
const localChangedKeys = diff.changed;
|
|
7328
|
+
if (localOnlyKeys.length > 0 || localChangedKeys.length > 0) {
|
|
7329
|
+
spinner.stop();
|
|
7330
|
+
console.log(chalk5.yellow(`Warning: Local .env has changes not on remote:`));
|
|
7331
|
+
for (const { key } of localOnlyKeys) {
|
|
7332
|
+
console.log(chalk5.green(`+ ${key}`));
|
|
7333
|
+
}
|
|
7334
|
+
for (const { key } of localChangedKeys) {
|
|
7335
|
+
console.log(chalk5.yellow(`~ ${key}`));
|
|
7336
|
+
}
|
|
7337
|
+
console.log(
|
|
7338
|
+
chalk5.dim(
|
|
7339
|
+
`
|
|
7340
|
+
These will be overwritten. A backup will be saved to .env.backup.`
|
|
7341
|
+
)
|
|
7342
|
+
);
|
|
7343
|
+
if (!options?.yes) {
|
|
7344
|
+
const answer = await prompt("Continue? (y/N) ");
|
|
7345
|
+
if (answer.toLowerCase() !== "y") {
|
|
7346
|
+
console.log(chalk5.yellow("Pull cancelled."));
|
|
7347
|
+
process.exit(0);
|
|
7348
|
+
}
|
|
7349
|
+
}
|
|
7350
|
+
if (existsSync5(envPath)) {
|
|
7351
|
+
await copyFile(envPath, backupPath);
|
|
7352
|
+
console.log(chalk5.dim(`Backup saved to .env.backup`));
|
|
7164
7353
|
}
|
|
7165
7354
|
}
|
|
7166
|
-
await copyFile(envPath, backupPath);
|
|
7167
|
-
console.log(chalk5.dim(`Backup saved to .env.backup`));
|
|
7168
7355
|
}
|
|
7356
|
+
await writeFile4(envPath, serializeEnv(remoteEnv), "utf-8");
|
|
7357
|
+
spinner.succeed(
|
|
7358
|
+
chalk5.green(
|
|
7359
|
+
`Pulled ${response.secrets.length} secrets from ${context.app ?? "default"}:${context.env}`
|
|
7360
|
+
)
|
|
7361
|
+
);
|
|
7169
7362
|
}
|
|
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
7363
|
} catch (err) {
|
|
7177
7364
|
ora4().fail(chalk5.red(`Pull failed: ${err.message}`));
|
|
7178
7365
|
process.exit(1);
|
|
@@ -7806,7 +7993,7 @@ var getCommand = new Command12("get").description("Get a single secret value").a
|
|
|
7806
7993
|
init_config();
|
|
7807
7994
|
import { Command as Command13 } from "commander";
|
|
7808
7995
|
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
7809
|
-
import { join as
|
|
7996
|
+
import { join as join7 } from "path";
|
|
7810
7997
|
import chalk15 from "chalk";
|
|
7811
7998
|
import ora9 from "ora";
|
|
7812
7999
|
import YAML3 from "yaml";
|
|
@@ -7819,7 +8006,7 @@ backendCommand.command("show").description("Show current backend configuration")
|
|
|
7819
8006
|
console.error(chalk15.red("No ev.yaml found."));
|
|
7820
8007
|
process.exit(1);
|
|
7821
8008
|
}
|
|
7822
|
-
const content = await readFile5(
|
|
8009
|
+
const content = await readFile5(join7(root, "ev.yaml"), "utf-8");
|
|
7823
8010
|
const config = YAML3.parse(content);
|
|
7824
8011
|
console.log(chalk15.bold("\nBackend Configuration\n"));
|
|
7825
8012
|
const defaultBackend = config.backend ?? { type: "ev" };
|
|
@@ -7878,7 +8065,7 @@ backendCommand.command("set <type>").description("Set the default storage backen
|
|
|
7878
8065
|
spinner.fail(chalk15.red("No ev.yaml found."));
|
|
7879
8066
|
process.exit(1);
|
|
7880
8067
|
}
|
|
7881
|
-
const configPath =
|
|
8068
|
+
const configPath = join7(root, "ev.yaml");
|
|
7882
8069
|
const content = await readFile5(configPath, "utf-8");
|
|
7883
8070
|
const config = YAML3.parse(content);
|
|
7884
8071
|
let backendConfig;
|
|
@@ -8385,8 +8572,8 @@ import { Command as Command17 } from "commander";
|
|
|
8385
8572
|
import chalk19 from "chalk";
|
|
8386
8573
|
import ora11 from "ora";
|
|
8387
8574
|
import { readdir, readFile as readFile6, stat } from "fs/promises";
|
|
8388
|
-
import { existsSync as
|
|
8389
|
-
import { join as
|
|
8575
|
+
import { existsSync as existsSync6 } from "fs";
|
|
8576
|
+
import { join as join8, extname } from "path";
|
|
8390
8577
|
var SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
8391
8578
|
".ts",
|
|
8392
8579
|
".tsx",
|
|
@@ -8452,7 +8639,7 @@ async function scanDir(dir) {
|
|
|
8452
8639
|
}
|
|
8453
8640
|
for (const entry of entries) {
|
|
8454
8641
|
if (SKIP_DIRS.has(entry)) continue;
|
|
8455
|
-
const fullPath =
|
|
8642
|
+
const fullPath = join8(currentDir, entry);
|
|
8456
8643
|
const stats = await stat(fullPath);
|
|
8457
8644
|
if (stats.isDirectory()) {
|
|
8458
8645
|
await walk(fullPath);
|
|
@@ -8477,8 +8664,8 @@ async function scanDir(dir) {
|
|
|
8477
8664
|
async function getLocalEnvKeys(dir, configEnvFiles) {
|
|
8478
8665
|
const keys = /* @__PURE__ */ new Set();
|
|
8479
8666
|
for (const file of configEnvFiles) {
|
|
8480
|
-
const filePath =
|
|
8481
|
-
if (
|
|
8667
|
+
const filePath = join8(dir, file);
|
|
8668
|
+
if (existsSync6(filePath)) {
|
|
8482
8669
|
const content = await readFile6(filePath, "utf-8");
|
|
8483
8670
|
const parsed = parseEnvFile(content);
|
|
8484
8671
|
for (const key of Object.keys(parsed)) {
|
|
@@ -8567,7 +8754,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
|
|
|
8567
8754
|
let totalMissing = 0;
|
|
8568
8755
|
let totalVars = 0;
|
|
8569
8756
|
for (const [appName, appConfig] of Object.entries(config.apps)) {
|
|
8570
|
-
const appDir =
|
|
8757
|
+
const appDir = join8(repoRoot, appConfig.path);
|
|
8571
8758
|
try {
|
|
8572
8759
|
await stat(appDir);
|
|
8573
8760
|
} catch {
|
|
@@ -8621,7 +8808,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
|
|
|
8621
8808
|
}
|
|
8622
8809
|
console.log();
|
|
8623
8810
|
} else {
|
|
8624
|
-
const scanRoot = context.app && config.apps?.[context.app] ?
|
|
8811
|
+
const scanRoot = context.app && config.apps?.[context.app] ? join8(repoRoot, config.apps[context.app].path) : cwd;
|
|
8625
8812
|
const refs = await scanDir(scanRoot);
|
|
8626
8813
|
if (refs.size === 0) {
|
|
8627
8814
|
spinner.succeed(
|
|
@@ -8670,7 +8857,7 @@ var updateCommand = new Command18("update").description("Update ev to the latest
|
|
|
8670
8857
|
const spinner = ora12("Checking for updates...").start();
|
|
8671
8858
|
try {
|
|
8672
8859
|
const latest = execSync2("npm view @rowlabs/ev version", { encoding: "utf-8" }).trim();
|
|
8673
|
-
const current = "0.
|
|
8860
|
+
const current = "0.4.1";
|
|
8674
8861
|
if (current === latest) {
|
|
8675
8862
|
spinner.succeed(chalk20.green(`Already on the latest version (${current})`));
|
|
8676
8863
|
return;
|
|
@@ -8686,7 +8873,7 @@ var updateCommand = new Command18("update").description("Update ev to the latest
|
|
|
8686
8873
|
|
|
8687
8874
|
// src/index.ts
|
|
8688
8875
|
var program = new Command19();
|
|
8689
|
-
program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.
|
|
8876
|
+
program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.4.1");
|
|
8690
8877
|
program.addCommand(loginCommand);
|
|
8691
8878
|
program.addCommand(initCommand);
|
|
8692
8879
|
program.addCommand(pushCommand);
|