@rowlabs/ev 0.4.5 → 0.5.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 +378 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6735,7 +6735,7 @@ var init_auth = __esm({
|
|
|
6735
6735
|
|
|
6736
6736
|
// src/index.ts
|
|
6737
6737
|
init_dist();
|
|
6738
|
-
import { Command as
|
|
6738
|
+
import { Command as Command22 } from "commander";
|
|
6739
6739
|
|
|
6740
6740
|
// src/commands/login.ts
|
|
6741
6741
|
init_dist();
|
|
@@ -8642,12 +8642,16 @@ var completionsCommand = new Command16("completions").description("Generate shel
|
|
|
8642
8642
|
});
|
|
8643
8643
|
|
|
8644
8644
|
// src/commands/scan.ts
|
|
8645
|
-
init_dist();
|
|
8646
8645
|
init_api_client();
|
|
8647
8646
|
init_config();
|
|
8648
8647
|
import { Command as Command17 } from "commander";
|
|
8649
8648
|
import chalk19 from "chalk";
|
|
8650
8649
|
import ora11 from "ora";
|
|
8650
|
+
import { stat as stat2 } from "fs/promises";
|
|
8651
|
+
import { join as join9 } from "path";
|
|
8652
|
+
|
|
8653
|
+
// src/lib/scanner.ts
|
|
8654
|
+
init_dist();
|
|
8651
8655
|
import { readdir, readFile as readFile6, stat } from "fs/promises";
|
|
8652
8656
|
import { existsSync as existsSync6 } from "fs";
|
|
8653
8657
|
import { join as join8, extname } from "path";
|
|
@@ -8752,6 +8756,8 @@ async function getLocalEnvKeys(dir, configEnvFiles) {
|
|
|
8752
8756
|
}
|
|
8753
8757
|
return keys;
|
|
8754
8758
|
}
|
|
8759
|
+
|
|
8760
|
+
// src/commands/scan.ts
|
|
8755
8761
|
function printTable(label, refs, envData, repoRoot, showFiles, localKeys) {
|
|
8756
8762
|
const varNames = [...refs.keys()].sort();
|
|
8757
8763
|
if (varNames.length === 0) return 0;
|
|
@@ -8831,9 +8837,9 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
|
|
|
8831
8837
|
let totalMissing = 0;
|
|
8832
8838
|
let totalVars = 0;
|
|
8833
8839
|
for (const [appName, appConfig] of Object.entries(config.apps)) {
|
|
8834
|
-
const appDir =
|
|
8840
|
+
const appDir = join9(repoRoot, appConfig.path);
|
|
8835
8841
|
try {
|
|
8836
|
-
await
|
|
8842
|
+
await stat2(appDir);
|
|
8837
8843
|
} catch {
|
|
8838
8844
|
continue;
|
|
8839
8845
|
}
|
|
@@ -8885,7 +8891,7 @@ var scanCommand = new Command17("scan").description("Scan codebase for env var r
|
|
|
8885
8891
|
}
|
|
8886
8892
|
console.log();
|
|
8887
8893
|
} else {
|
|
8888
|
-
const scanRoot = context.app && config.apps?.[context.app] ?
|
|
8894
|
+
const scanRoot = context.app && config.apps?.[context.app] ? join9(repoRoot, config.apps[context.app].path) : cwd;
|
|
8889
8895
|
const refs = await scanDir(scanRoot);
|
|
8890
8896
|
if (refs.size === 0) {
|
|
8891
8897
|
spinner.succeed(
|
|
@@ -8934,7 +8940,7 @@ var updateCommand = new Command18("update").description("Update ev to the latest
|
|
|
8934
8940
|
const spinner = ora12("Checking for updates...").start();
|
|
8935
8941
|
try {
|
|
8936
8942
|
const latest = execSync2("npm view @rowlabs/ev version", { encoding: "utf-8" }).trim();
|
|
8937
|
-
const current = "0.
|
|
8943
|
+
const current = "0.5.0";
|
|
8938
8944
|
if (current === latest) {
|
|
8939
8945
|
spinner.succeed(chalk20.green(`Already on the latest version (${current})`));
|
|
8940
8946
|
return;
|
|
@@ -9029,9 +9035,371 @@ function prompt2(question) {
|
|
|
9029
9035
|
});
|
|
9030
9036
|
}
|
|
9031
9037
|
|
|
9038
|
+
// src/commands/audit.ts
|
|
9039
|
+
init_api_client();
|
|
9040
|
+
init_config();
|
|
9041
|
+
import { Command as Command20 } from "commander";
|
|
9042
|
+
import chalk22 from "chalk";
|
|
9043
|
+
import ora14 from "ora";
|
|
9044
|
+
import { stat as stat3 } from "fs/promises";
|
|
9045
|
+
import { join as join10 } from "path";
|
|
9046
|
+
var isTTY = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
9047
|
+
function stripAnsi(str) {
|
|
9048
|
+
return str.replace(/\x1B\[[0-9;]*m/g, "");
|
|
9049
|
+
}
|
|
9050
|
+
function log(msg) {
|
|
9051
|
+
console.log(isTTY ? msg : stripAnsi(msg));
|
|
9052
|
+
}
|
|
9053
|
+
async function auditApp(appName, envName, scanPath, projectId) {
|
|
9054
|
+
const client = await createApiClient();
|
|
9055
|
+
const project = await client.getProject(projectId);
|
|
9056
|
+
const serverApp = project.apps.find((a2) => a2.name.toLowerCase() === appName.toLowerCase());
|
|
9057
|
+
if (!serverApp) {
|
|
9058
|
+
log(isTTY ? chalk22.red(` App "${appName}" not found on server`) : `ERROR: App "${appName}" not found on server`);
|
|
9059
|
+
process.exit(1);
|
|
9060
|
+
}
|
|
9061
|
+
const envs = await client.listEnvironments(serverApp.id);
|
|
9062
|
+
const serverEnv = envs.find((e) => e.name.toLowerCase() === envName.toLowerCase());
|
|
9063
|
+
if (!serverEnv) {
|
|
9064
|
+
log(isTTY ? chalk22.red(` Environment "${envName}" not found for app "${appName}"`) : `ERROR: Environment "${envName}" not found for app "${appName}"`);
|
|
9065
|
+
process.exit(1);
|
|
9066
|
+
}
|
|
9067
|
+
const response = await client.pullSecrets(serverEnv.id);
|
|
9068
|
+
const remoteKeys = new Set(response.secrets.map((s2) => s2.key));
|
|
9069
|
+
const refs = await scanDir(scanPath);
|
|
9070
|
+
const referencedVars = [...refs.keys()];
|
|
9071
|
+
const missingVars = referencedVars.filter((v2) => !remoteKeys.has(v2));
|
|
9072
|
+
return {
|
|
9073
|
+
label: `${appName}:${envName}`,
|
|
9074
|
+
total: referencedVars.length,
|
|
9075
|
+
missing: missingVars
|
|
9076
|
+
};
|
|
9077
|
+
}
|
|
9078
|
+
function printResult(result) {
|
|
9079
|
+
if (result.missing.length === 0) {
|
|
9080
|
+
const msg = ` ${result.label} \u2014 ${result.total} variable${result.total === 1 ? "" : "s"}, all present \u2714`;
|
|
9081
|
+
log(isTTY ? chalk22.green(msg) : msg);
|
|
9082
|
+
} else {
|
|
9083
|
+
const header = ` ${result.label} \u2014 ${result.missing.length} missing`;
|
|
9084
|
+
log(isTTY ? chalk22.yellow(header) : header);
|
|
9085
|
+
log("");
|
|
9086
|
+
for (const v2 of result.missing) {
|
|
9087
|
+
log(isTTY ? ` ${chalk22.red(v2)}` : ` ${v2}`);
|
|
9088
|
+
}
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
var auditCommand = new Command20("audit").description("Check that all env vars referenced in code exist in a remote environment").argument("[target]", "Target environment name (defaults to defaultEnv from ev.yaml)").action(async (target) => {
|
|
9092
|
+
const spinner = isTTY ? ora14("Resolving context...").start() : null;
|
|
9093
|
+
try {
|
|
9094
|
+
const resolved = await resolveCurrentContext(target);
|
|
9095
|
+
if (!resolved) {
|
|
9096
|
+
if (spinner) spinner.fail(chalk22.red("No ev.yaml found. Run `ev init` first."));
|
|
9097
|
+
else log("ERROR: No ev.yaml found. Run `ev init` first.");
|
|
9098
|
+
process.exit(1);
|
|
9099
|
+
}
|
|
9100
|
+
const { context, repoRoot } = resolved;
|
|
9101
|
+
const envName = target ?? context.env;
|
|
9102
|
+
const config = await loadEvConfig(process.cwd());
|
|
9103
|
+
if (!config) {
|
|
9104
|
+
if (spinner) spinner.fail(chalk22.red("Could not load ev.yaml"));
|
|
9105
|
+
else log("ERROR: Could not load ev.yaml");
|
|
9106
|
+
process.exit(1);
|
|
9107
|
+
}
|
|
9108
|
+
const cwd = process.cwd();
|
|
9109
|
+
const isAtRoot = cwd === repoRoot;
|
|
9110
|
+
const hasApps = config.apps && Object.keys(config.apps).length > 0;
|
|
9111
|
+
const results = [];
|
|
9112
|
+
if (isAtRoot && hasApps) {
|
|
9113
|
+
if (spinner) spinner.text = "Auditing all apps...";
|
|
9114
|
+
for (const [appName, appConfig] of Object.entries(config.apps)) {
|
|
9115
|
+
const appDir = join10(repoRoot, appConfig.path);
|
|
9116
|
+
try {
|
|
9117
|
+
await stat3(appDir);
|
|
9118
|
+
} catch {
|
|
9119
|
+
continue;
|
|
9120
|
+
}
|
|
9121
|
+
if (spinner) spinner.text = `Auditing ${appName}:${envName}...`;
|
|
9122
|
+
const result = await auditApp(appName, envName, appDir, context.project);
|
|
9123
|
+
results.push(result);
|
|
9124
|
+
}
|
|
9125
|
+
} else {
|
|
9126
|
+
const appName = context.app ?? "default";
|
|
9127
|
+
const scanPath = context.app && config.apps?.[context.app] ? join10(repoRoot, config.apps[context.app].path) : cwd;
|
|
9128
|
+
if (spinner) spinner.text = `Auditing ${appName}:${envName}...`;
|
|
9129
|
+
const result = await auditApp(appName, envName, scanPath, context.project);
|
|
9130
|
+
results.push(result);
|
|
9131
|
+
}
|
|
9132
|
+
if (spinner) spinner.stop();
|
|
9133
|
+
const totalVars = results.reduce((sum, r2) => sum + r2.total, 0);
|
|
9134
|
+
if (totalVars === 0) {
|
|
9135
|
+
log(isTTY ? chalk22.green(" No environment variable references found in code.") : "No environment variable references found in code.");
|
|
9136
|
+
process.exit(0);
|
|
9137
|
+
}
|
|
9138
|
+
log("");
|
|
9139
|
+
for (const result of results) {
|
|
9140
|
+
printResult(result);
|
|
9141
|
+
log("");
|
|
9142
|
+
}
|
|
9143
|
+
const anyMissing = results.some((r2) => r2.missing.length > 0);
|
|
9144
|
+
if (anyMissing) {
|
|
9145
|
+
log(isTTY ? chalk22.red(" \u2717 Audit failed") : "FAIL");
|
|
9146
|
+
process.exit(1);
|
|
9147
|
+
} else {
|
|
9148
|
+
process.exit(0);
|
|
9149
|
+
}
|
|
9150
|
+
} catch (err) {
|
|
9151
|
+
if (spinner) spinner.fail(chalk22.red(`Audit failed: ${err.message}`));
|
|
9152
|
+
else log(`ERROR: Audit failed: ${err.message}`);
|
|
9153
|
+
process.exit(1);
|
|
9154
|
+
}
|
|
9155
|
+
});
|
|
9156
|
+
|
|
9157
|
+
// src/commands/clean.ts
|
|
9158
|
+
init_dist();
|
|
9159
|
+
init_api_client();
|
|
9160
|
+
init_config();
|
|
9161
|
+
init_auth();
|
|
9162
|
+
import { Command as Command21 } from "commander";
|
|
9163
|
+
import chalk23 from "chalk";
|
|
9164
|
+
import ora15 from "ora";
|
|
9165
|
+
import { join as join11 } from "path";
|
|
9166
|
+
import { readFile as readFile7, writeFile as writeFile6, stat as stat4 } from "fs/promises";
|
|
9167
|
+
import { existsSync as existsSync7 } from "fs";
|
|
9168
|
+
var isTTY2 = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
9169
|
+
function stripAnsi2(str) {
|
|
9170
|
+
return str.replace(/\x1B\[[0-9;]*m/g, "");
|
|
9171
|
+
}
|
|
9172
|
+
function log2(msg) {
|
|
9173
|
+
console.log(isTTY2 ? msg : stripAnsi2(msg));
|
|
9174
|
+
}
|
|
9175
|
+
async function findDeadSecrets(appName, envName, scanPath, projectId, envFiles) {
|
|
9176
|
+
const client = await createApiClient();
|
|
9177
|
+
const project = await client.getProject(projectId);
|
|
9178
|
+
const serverApp = project.apps.find((a2) => a2.name.toLowerCase() === appName.toLowerCase());
|
|
9179
|
+
if (!serverApp) {
|
|
9180
|
+
log2(
|
|
9181
|
+
isTTY2 ? chalk23.red(` App "${appName}" not found on server`) : `ERROR: App "${appName}" not found on server`
|
|
9182
|
+
);
|
|
9183
|
+
process.exit(1);
|
|
9184
|
+
}
|
|
9185
|
+
const envs = await client.listEnvironments(serverApp.id);
|
|
9186
|
+
const serverEnv = envs.find((e) => e.name.toLowerCase() === envName.toLowerCase());
|
|
9187
|
+
if (!serverEnv) {
|
|
9188
|
+
log2(
|
|
9189
|
+
isTTY2 ? chalk23.red(` Environment "${envName}" not found for app "${appName}"`) : `ERROR: Environment "${envName}" not found for app "${appName}"`
|
|
9190
|
+
);
|
|
9191
|
+
process.exit(1);
|
|
9192
|
+
}
|
|
9193
|
+
const refs = await scanDir(scanPath);
|
|
9194
|
+
const referencedKeys = new Set(refs.keys());
|
|
9195
|
+
const localKeyFiles = /* @__PURE__ */ new Map();
|
|
9196
|
+
for (const envFile of envFiles) {
|
|
9197
|
+
const filePath = join11(scanPath, envFile);
|
|
9198
|
+
if (existsSync7(filePath)) {
|
|
9199
|
+
const content = await readFile7(filePath, "utf-8");
|
|
9200
|
+
const parsed = parseEnvFile(content);
|
|
9201
|
+
for (const key of Object.keys(parsed)) {
|
|
9202
|
+
if (!localKeyFiles.has(key)) localKeyFiles.set(key, []);
|
|
9203
|
+
localKeyFiles.get(key).push(filePath);
|
|
9204
|
+
}
|
|
9205
|
+
}
|
|
9206
|
+
}
|
|
9207
|
+
const response = await client.pullSecrets(serverEnv.id);
|
|
9208
|
+
const remoteKeys = new Set(response.secrets.map((s2) => s2.key));
|
|
9209
|
+
const allKeys = /* @__PURE__ */ new Set([...localKeyFiles.keys(), ...remoteKeys]);
|
|
9210
|
+
const dead = [];
|
|
9211
|
+
for (const key of allKeys) {
|
|
9212
|
+
if (!referencedKeys.has(key)) {
|
|
9213
|
+
dead.push({
|
|
9214
|
+
key,
|
|
9215
|
+
inLocal: localKeyFiles.has(key),
|
|
9216
|
+
inRemote: remoteKeys.has(key)
|
|
9217
|
+
});
|
|
9218
|
+
}
|
|
9219
|
+
}
|
|
9220
|
+
const affectedFiles = /* @__PURE__ */ new Set();
|
|
9221
|
+
for (const d2 of dead) {
|
|
9222
|
+
if (d2.inLocal) {
|
|
9223
|
+
for (const f2 of localKeyFiles.get(d2.key) ?? []) {
|
|
9224
|
+
affectedFiles.add(f2);
|
|
9225
|
+
}
|
|
9226
|
+
}
|
|
9227
|
+
}
|
|
9228
|
+
return {
|
|
9229
|
+
label: `${appName}:${envName}`,
|
|
9230
|
+
dead,
|
|
9231
|
+
localFiles: [...affectedFiles],
|
|
9232
|
+
envName: serverEnv.id,
|
|
9233
|
+
projectId,
|
|
9234
|
+
appName,
|
|
9235
|
+
codeRefCount: referencedKeys.size
|
|
9236
|
+
};
|
|
9237
|
+
}
|
|
9238
|
+
function printDeadTable(result) {
|
|
9239
|
+
const { label, dead } = result;
|
|
9240
|
+
const count = dead.length;
|
|
9241
|
+
if (count === 0) {
|
|
9242
|
+
const msg = ` ${label} \u2014 no unused secrets found`;
|
|
9243
|
+
log2(isTTY2 ? chalk23.green(msg) : msg);
|
|
9244
|
+
return;
|
|
9245
|
+
}
|
|
9246
|
+
const header = ` ${label} \u2014 ${count} unused secret${count === 1 ? "" : "s"} found`;
|
|
9247
|
+
log2(isTTY2 ? chalk23.yellow(header) : header);
|
|
9248
|
+
log2("");
|
|
9249
|
+
const maxKeyLen = Math.max(8, ...dead.map((d2) => d2.key.length));
|
|
9250
|
+
const keyCol = maxKeyLen + 2;
|
|
9251
|
+
const headerLine = " " + "Variable".padEnd(keyCol) + "local remote";
|
|
9252
|
+
log2(isTTY2 ? chalk23.dim(headerLine) : headerLine);
|
|
9253
|
+
const divider = " " + "\u2500".repeat(keyCol + 16);
|
|
9254
|
+
log2(isTTY2 ? chalk23.dim(divider) : divider);
|
|
9255
|
+
for (const d2 of dead) {
|
|
9256
|
+
const localMark = d2.inLocal ? isTTY2 ? chalk23.green("\u2713") : "\u2713" : isTTY2 ? chalk23.dim("\u2013") : "\u2013";
|
|
9257
|
+
const remoteMark = d2.inRemote ? isTTY2 ? chalk23.green("\u2713") : "\u2713" : isTTY2 ? chalk23.dim("\u2013") : "\u2013";
|
|
9258
|
+
const paddedKey = d2.key.padEnd(keyCol);
|
|
9259
|
+
log2(` ${isTTY2 ? chalk23.red(d2.key) + " ".repeat(keyCol - d2.key.length) : paddedKey}${localMark} ${remoteMark}`);
|
|
9260
|
+
}
|
|
9261
|
+
log2("");
|
|
9262
|
+
}
|
|
9263
|
+
async function cleanApp(result, deadSet, envFiles, scanPath, backendConfig) {
|
|
9264
|
+
const isEvBackend = !backendConfig || backendConfig.type === "ev";
|
|
9265
|
+
let localCleaned = 0;
|
|
9266
|
+
for (const envFile of envFiles) {
|
|
9267
|
+
const filePath = join11(scanPath, envFile);
|
|
9268
|
+
if (!existsSync7(filePath)) continue;
|
|
9269
|
+
const content = await readFile7(filePath, "utf-8");
|
|
9270
|
+
const parsed = parseEnvFile(content);
|
|
9271
|
+
let changed = false;
|
|
9272
|
+
for (const key of deadSet) {
|
|
9273
|
+
if (key in parsed) {
|
|
9274
|
+
delete parsed[key];
|
|
9275
|
+
changed = true;
|
|
9276
|
+
}
|
|
9277
|
+
}
|
|
9278
|
+
if (changed) {
|
|
9279
|
+
await writeFile6(filePath, serializeEnv(parsed), "utf-8");
|
|
9280
|
+
const display = envFile;
|
|
9281
|
+
log2(isTTY2 ? ` ${chalk23.green("\u2713")} Cleaned ${display}` : ` \u2713 Cleaned ${display}`);
|
|
9282
|
+
localCleaned++;
|
|
9283
|
+
}
|
|
9284
|
+
}
|
|
9285
|
+
const deadRemoteKeys = result.dead.filter((d2) => d2.inRemote).map((d2) => d2.key);
|
|
9286
|
+
if (deadRemoteKeys.length > 0) {
|
|
9287
|
+
const client = await createApiClient();
|
|
9288
|
+
const pullResponse = await client.pullSecrets(result.envName);
|
|
9289
|
+
const kept = pullResponse.secrets.filter((s2) => !deadSet.has(s2.key));
|
|
9290
|
+
let secrets;
|
|
9291
|
+
if (isEvBackend) {
|
|
9292
|
+
const projectKey = await getDecryptedProjectKey(result.projectId);
|
|
9293
|
+
secrets = kept.map((s2) => {
|
|
9294
|
+
const decrypted = decryptSecret(s2.encryptedValue, projectKey);
|
|
9295
|
+
const reEncrypted = encryptSecret(decrypted, projectKey);
|
|
9296
|
+
return { key: s2.key, encryptedValue: reEncrypted };
|
|
9297
|
+
});
|
|
9298
|
+
} else {
|
|
9299
|
+
secrets = kept.map((s2) => ({ key: s2.key, encryptedValue: s2.encryptedValue }));
|
|
9300
|
+
}
|
|
9301
|
+
await client.pushSecrets(result.envName, {
|
|
9302
|
+
secrets,
|
|
9303
|
+
prune: true,
|
|
9304
|
+
message: "ev clean: removed unused secrets"
|
|
9305
|
+
});
|
|
9306
|
+
log2(
|
|
9307
|
+
isTTY2 ? ` ${chalk23.green("\u2713")} Removed ${deadRemoteKeys.length} from ${result.label} remote` : ` \u2713 Removed ${deadRemoteKeys.length} from ${result.label} remote`
|
|
9308
|
+
);
|
|
9309
|
+
}
|
|
9310
|
+
}
|
|
9311
|
+
var cleanCommand = new Command21("clean").description("Find and remove unused secrets from local env files and remote environments").argument("[target]", "Target environment name (defaults to defaultEnv from ev.yaml)").action(async (target) => {
|
|
9312
|
+
const spinner = isTTY2 ? ora15("Resolving context...").start() : null;
|
|
9313
|
+
try {
|
|
9314
|
+
const resolved = await resolveCurrentContext(target);
|
|
9315
|
+
if (!resolved) {
|
|
9316
|
+
if (spinner) spinner.fail(chalk23.red("No ev.yaml found. Run `ev init` first."));
|
|
9317
|
+
else log2("ERROR: No ev.yaml found. Run `ev init` first.");
|
|
9318
|
+
process.exit(1);
|
|
9319
|
+
}
|
|
9320
|
+
const { context, repoRoot, backendConfig, envFiles } = resolved;
|
|
9321
|
+
const envName = target ?? context.env;
|
|
9322
|
+
const config = await loadEvConfig(process.cwd());
|
|
9323
|
+
if (!config) {
|
|
9324
|
+
if (spinner) spinner.fail(chalk23.red("Could not load ev.yaml"));
|
|
9325
|
+
else log2("ERROR: Could not load ev.yaml");
|
|
9326
|
+
process.exit(1);
|
|
9327
|
+
}
|
|
9328
|
+
const cwd = process.cwd();
|
|
9329
|
+
const isAtRoot = cwd === repoRoot;
|
|
9330
|
+
const hasApps = config.apps && Object.keys(config.apps).length > 0;
|
|
9331
|
+
const results = [];
|
|
9332
|
+
const scanPaths = /* @__PURE__ */ new Map();
|
|
9333
|
+
if (isAtRoot && hasApps) {
|
|
9334
|
+
if (spinner) spinner.text = "Scanning all apps...";
|
|
9335
|
+
for (const [appName, appConfig] of Object.entries(config.apps)) {
|
|
9336
|
+
const appDir = join11(repoRoot, appConfig.path);
|
|
9337
|
+
try {
|
|
9338
|
+
await stat4(appDir);
|
|
9339
|
+
} catch {
|
|
9340
|
+
continue;
|
|
9341
|
+
}
|
|
9342
|
+
if (spinner) spinner.text = `Scanning ${appName}:${envName}...`;
|
|
9343
|
+
const result = await findDeadSecrets(
|
|
9344
|
+
appName,
|
|
9345
|
+
envName,
|
|
9346
|
+
appDir,
|
|
9347
|
+
context.project,
|
|
9348
|
+
envFiles
|
|
9349
|
+
);
|
|
9350
|
+
results.push(result);
|
|
9351
|
+
scanPaths.set(appName, appDir);
|
|
9352
|
+
}
|
|
9353
|
+
} else {
|
|
9354
|
+
const appName = context.app ?? "default";
|
|
9355
|
+
const scanPath = context.app && config.apps?.[context.app] ? join11(repoRoot, config.apps[context.app].path) : cwd;
|
|
9356
|
+
if (spinner) spinner.text = `Scanning ${appName}:${envName}...`;
|
|
9357
|
+
const result = await findDeadSecrets(
|
|
9358
|
+
appName,
|
|
9359
|
+
envName,
|
|
9360
|
+
scanPath,
|
|
9361
|
+
context.project,
|
|
9362
|
+
envFiles
|
|
9363
|
+
);
|
|
9364
|
+
results.push(result);
|
|
9365
|
+
scanPaths.set(appName, scanPath);
|
|
9366
|
+
}
|
|
9367
|
+
if (spinner) spinner.stop();
|
|
9368
|
+
const totalCodeRefs = results.reduce((sum, r2) => sum + r2.codeRefCount, 0);
|
|
9369
|
+
if (totalCodeRefs === 0) {
|
|
9370
|
+
log2(
|
|
9371
|
+
"No env var references found in code \u2014 can't determine what's unused."
|
|
9372
|
+
);
|
|
9373
|
+
process.exit(0);
|
|
9374
|
+
}
|
|
9375
|
+
log2("");
|
|
9376
|
+
for (const result of results) {
|
|
9377
|
+
printDeadTable(result);
|
|
9378
|
+
if (result.dead.length === 0) continue;
|
|
9379
|
+
const deadSet = new Set(result.dead.map((d2) => d2.key));
|
|
9380
|
+
const appScanPath = scanPaths.get(result.appName) ?? repoRoot;
|
|
9381
|
+
const answer = await prompt(
|
|
9382
|
+
isTTY2 ? ` Remove these ${result.dead.length} secret${result.dead.length === 1 ? "" : "s"}? (y/N) ` : `Remove these ${result.dead.length} secrets? (y/N) `
|
|
9383
|
+
);
|
|
9384
|
+
if (answer.toLowerCase() !== "y") {
|
|
9385
|
+
log2(isTTY2 ? chalk23.dim(" Skipped.") : " Skipped.");
|
|
9386
|
+
log2("");
|
|
9387
|
+
continue;
|
|
9388
|
+
}
|
|
9389
|
+
log2("");
|
|
9390
|
+
await cleanApp(result, deadSet, envFiles, appScanPath, backendConfig);
|
|
9391
|
+
log2("");
|
|
9392
|
+
}
|
|
9393
|
+
} catch (err) {
|
|
9394
|
+
if (spinner) spinner.fail(chalk23.red(`Clean failed: ${err.message}`));
|
|
9395
|
+
else log2(`ERROR: Clean failed: ${err.message}`);
|
|
9396
|
+
process.exit(1);
|
|
9397
|
+
}
|
|
9398
|
+
});
|
|
9399
|
+
|
|
9032
9400
|
// src/index.ts
|
|
9033
|
-
var program = new
|
|
9034
|
-
program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.
|
|
9401
|
+
var program = new Command22();
|
|
9402
|
+
program.name("ev").description("Git for env vars \u2014 sync environment variables across teams securely").version("0.5.0");
|
|
9035
9403
|
program.addCommand(loginCommand);
|
|
9036
9404
|
program.addCommand(initCommand);
|
|
9037
9405
|
program.addCommand(pushCommand);
|
|
@@ -9051,6 +9419,8 @@ program.addCommand(completionsCommand);
|
|
|
9051
9419
|
program.addCommand(scanCommand);
|
|
9052
9420
|
program.addCommand(updateCommand);
|
|
9053
9421
|
program.addCommand(deleteCommand);
|
|
9422
|
+
program.addCommand(auditCommand);
|
|
9423
|
+
program.addCommand(cleanCommand);
|
|
9054
9424
|
async function main() {
|
|
9055
9425
|
await initCrypto();
|
|
9056
9426
|
program.parse();
|