@keywaysh/cli 0.1.13 → 0.1.14
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/README.md +19 -0
- package/dist/cli.js +298 -254
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
-
import
|
|
11
|
+
import pc12 from "picocolors";
|
|
12
12
|
|
|
13
13
|
// src/cmds/init.ts
|
|
14
|
-
import
|
|
15
|
-
import
|
|
14
|
+
import pc7 from "picocolors";
|
|
15
|
+
import prompts6 from "prompts";
|
|
16
16
|
|
|
17
17
|
// src/utils/git.ts
|
|
18
18
|
import { execSync } from "child_process";
|
|
@@ -140,7 +140,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
140
140
|
// package.json
|
|
141
141
|
var package_default = {
|
|
142
142
|
name: "@keywaysh/cli",
|
|
143
|
-
version: "0.1.
|
|
143
|
+
version: "0.1.14",
|
|
144
144
|
description: "One link to all your secrets",
|
|
145
145
|
type: "module",
|
|
146
146
|
bin: {
|
|
@@ -900,10 +900,10 @@ async function addBadgeToReadme(silent = false) {
|
|
|
900
900
|
}
|
|
901
901
|
|
|
902
902
|
// src/cmds/push.ts
|
|
903
|
-
import
|
|
904
|
-
import
|
|
905
|
-
import
|
|
906
|
-
import
|
|
903
|
+
import pc6 from "picocolors";
|
|
904
|
+
import fs5 from "fs";
|
|
905
|
+
import path5 from "path";
|
|
906
|
+
import prompts5 from "prompts";
|
|
907
907
|
|
|
908
908
|
// src/cmds/login.ts
|
|
909
909
|
import pc4 from "picocolors";
|
|
@@ -1112,9 +1112,34 @@ async function logoutCommand() {
|
|
|
1112
1112
|
console.log(pc4.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
1113
1113
|
}
|
|
1114
1114
|
|
|
1115
|
+
// src/utils/env.ts
|
|
1116
|
+
import fs4 from "fs";
|
|
1117
|
+
import path4 from "path";
|
|
1118
|
+
import pc5 from "picocolors";
|
|
1119
|
+
import prompts4 from "prompts";
|
|
1120
|
+
async function promptCreateEnvFile() {
|
|
1121
|
+
const { createEnv } = await prompts4({
|
|
1122
|
+
type: "confirm",
|
|
1123
|
+
name: "createEnv",
|
|
1124
|
+
message: "No .env file found. Create one?",
|
|
1125
|
+
initial: true
|
|
1126
|
+
}, {
|
|
1127
|
+
onCancel: () => {
|
|
1128
|
+
throw new Error("Cancelled by user.");
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
if (!createEnv) {
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
const envFilePath = path4.join(process.cwd(), ".env");
|
|
1135
|
+
fs4.writeFileSync(envFilePath, "# Add your environment variables here\n# Example: API_KEY=your-api-key\n");
|
|
1136
|
+
console.log(pc5.green("\u2713 Created .env file"));
|
|
1137
|
+
return true;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1115
1140
|
// src/cmds/push.ts
|
|
1116
1141
|
function deriveEnvFromFile(file) {
|
|
1117
|
-
const base =
|
|
1142
|
+
const base = path5.basename(file);
|
|
1118
1143
|
const match = base.match(/\.env(?:\.(.+))?$/);
|
|
1119
1144
|
if (match) {
|
|
1120
1145
|
return match[1] || "development";
|
|
@@ -1123,15 +1148,15 @@ function deriveEnvFromFile(file) {
|
|
|
1123
1148
|
}
|
|
1124
1149
|
function discoverEnvCandidates(cwd) {
|
|
1125
1150
|
try {
|
|
1126
|
-
const entries =
|
|
1151
|
+
const entries = fs5.readdirSync(cwd);
|
|
1127
1152
|
const hasEnvLocal = entries.includes(".env.local");
|
|
1128
1153
|
if (hasEnvLocal) {
|
|
1129
|
-
console.log(
|
|
1154
|
+
console.log(pc6.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
|
|
1130
1155
|
}
|
|
1131
1156
|
const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
|
|
1132
|
-
const fullPath =
|
|
1157
|
+
const fullPath = path5.join(cwd, name);
|
|
1133
1158
|
try {
|
|
1134
|
-
const stat =
|
|
1159
|
+
const stat = fs5.statSync(fullPath);
|
|
1135
1160
|
if (!stat.isFile()) return null;
|
|
1136
1161
|
return { file: name, env: deriveEnvFromFile(name) };
|
|
1137
1162
|
} catch {
|
|
@@ -1152,13 +1177,21 @@ function discoverEnvCandidates(cwd) {
|
|
|
1152
1177
|
}
|
|
1153
1178
|
async function pushCommand(options) {
|
|
1154
1179
|
try {
|
|
1155
|
-
console.log(
|
|
1180
|
+
console.log(pc6.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
1156
1181
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1157
1182
|
let environment = options.env;
|
|
1158
1183
|
let envFile = options.file;
|
|
1159
1184
|
const candidates = discoverEnvCandidates(process.cwd());
|
|
1160
1185
|
if (candidates.length === 0 && !envFile) {
|
|
1161
|
-
|
|
1186
|
+
if (!isInteractive2) {
|
|
1187
|
+
throw new Error("No .env file found. Create a .env file first, or use --file <path> to specify one.");
|
|
1188
|
+
}
|
|
1189
|
+
const created = await promptCreateEnvFile();
|
|
1190
|
+
if (!created) {
|
|
1191
|
+
throw new Error("No .env file found.");
|
|
1192
|
+
}
|
|
1193
|
+
console.log(pc6.gray(" Add your variables and run keyway push again\n"));
|
|
1194
|
+
return;
|
|
1162
1195
|
}
|
|
1163
1196
|
if (environment && !envFile) {
|
|
1164
1197
|
const match = candidates.find((c) => c.env === environment);
|
|
@@ -1167,7 +1200,7 @@ async function pushCommand(options) {
|
|
|
1167
1200
|
}
|
|
1168
1201
|
}
|
|
1169
1202
|
if (!environment && !envFile && isInteractive2 && candidates.length > 0) {
|
|
1170
|
-
const { choice } = await
|
|
1203
|
+
const { choice } = await prompts5(
|
|
1171
1204
|
{
|
|
1172
1205
|
type: "select",
|
|
1173
1206
|
name: "choice",
|
|
@@ -1190,15 +1223,15 @@ async function pushCommand(options) {
|
|
|
1190
1223
|
envFile = choice.file;
|
|
1191
1224
|
environment = choice.env;
|
|
1192
1225
|
} else if (choice === "custom") {
|
|
1193
|
-
const { fileInput } = await
|
|
1226
|
+
const { fileInput } = await prompts5(
|
|
1194
1227
|
{
|
|
1195
1228
|
type: "text",
|
|
1196
1229
|
name: "fileInput",
|
|
1197
1230
|
message: "Path to env file:",
|
|
1198
1231
|
validate: (value) => {
|
|
1199
1232
|
if (!value) return "Path is required";
|
|
1200
|
-
const resolved =
|
|
1201
|
-
if (!
|
|
1233
|
+
const resolved = path5.resolve(process.cwd(), value);
|
|
1234
|
+
if (!fs5.existsSync(resolved)) return `File not found: ${value}`;
|
|
1202
1235
|
return true;
|
|
1203
1236
|
}
|
|
1204
1237
|
},
|
|
@@ -1218,11 +1251,11 @@ async function pushCommand(options) {
|
|
|
1218
1251
|
if (!envFile) {
|
|
1219
1252
|
envFile = ".env";
|
|
1220
1253
|
}
|
|
1221
|
-
const envFilePath =
|
|
1222
|
-
if (!
|
|
1254
|
+
const envFilePath = path5.resolve(process.cwd(), envFile);
|
|
1255
|
+
if (!fs5.existsSync(envFilePath)) {
|
|
1223
1256
|
throw new Error(`File not found: ${envFile}`);
|
|
1224
1257
|
}
|
|
1225
|
-
const content =
|
|
1258
|
+
const content = fs5.readFileSync(envFilePath, "utf-8");
|
|
1226
1259
|
if (content.trim().length === 0) {
|
|
1227
1260
|
throw new Error(`File is empty: ${envFile}`);
|
|
1228
1261
|
}
|
|
@@ -1230,17 +1263,17 @@ async function pushCommand(options) {
|
|
|
1230
1263
|
const trimmed = line.trim();
|
|
1231
1264
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1232
1265
|
});
|
|
1233
|
-
console.log(`File: ${
|
|
1234
|
-
console.log(`Environment: ${
|
|
1235
|
-
console.log(`Variables: ${
|
|
1266
|
+
console.log(`File: ${pc6.cyan(envFile)}`);
|
|
1267
|
+
console.log(`Environment: ${pc6.cyan(environment)}`);
|
|
1268
|
+
console.log(`Variables: ${pc6.cyan(lines.length.toString())}`);
|
|
1236
1269
|
const repoFullName = getCurrentRepoFullName();
|
|
1237
|
-
console.log(`Repository: ${
|
|
1270
|
+
console.log(`Repository: ${pc6.cyan(repoFullName)}`);
|
|
1238
1271
|
if (!options.yes) {
|
|
1239
1272
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1240
1273
|
if (!isInteractive3) {
|
|
1241
1274
|
throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
1242
1275
|
}
|
|
1243
|
-
const { confirm } = await
|
|
1276
|
+
const { confirm } = await prompts5(
|
|
1244
1277
|
{
|
|
1245
1278
|
type: "confirm",
|
|
1246
1279
|
name: "confirm",
|
|
@@ -1254,7 +1287,7 @@ async function pushCommand(options) {
|
|
|
1254
1287
|
}
|
|
1255
1288
|
);
|
|
1256
1289
|
if (!confirm) {
|
|
1257
|
-
console.log(
|
|
1290
|
+
console.log(pc6.yellow("Push aborted."));
|
|
1258
1291
|
return;
|
|
1259
1292
|
}
|
|
1260
1293
|
}
|
|
@@ -1266,13 +1299,13 @@ async function pushCommand(options) {
|
|
|
1266
1299
|
});
|
|
1267
1300
|
console.log("\nUploading secrets...");
|
|
1268
1301
|
const response = await pushSecrets(repoFullName, environment, content, accessToken);
|
|
1269
|
-
console.log(
|
|
1302
|
+
console.log(pc6.green("\n\u2713 " + response.message));
|
|
1270
1303
|
if (response.stats) {
|
|
1271
1304
|
const { created, updated, deleted } = response.stats;
|
|
1272
1305
|
const parts = [];
|
|
1273
|
-
if (created > 0) parts.push(
|
|
1274
|
-
if (updated > 0) parts.push(
|
|
1275
|
-
if (deleted > 0) parts.push(
|
|
1306
|
+
if (created > 0) parts.push(pc6.green(`+${created} created`));
|
|
1307
|
+
if (updated > 0) parts.push(pc6.yellow(`~${updated} updated`));
|
|
1308
|
+
if (deleted > 0) parts.push(pc6.red(`-${deleted} deleted`));
|
|
1276
1309
|
if (parts.length > 0) {
|
|
1277
1310
|
console.log(`Stats: ${parts.join(", ")}`);
|
|
1278
1311
|
}
|
|
@@ -1281,7 +1314,7 @@ async function pushCommand(options) {
|
|
|
1281
1314
|
Your secrets are now encrypted and stored securely.`);
|
|
1282
1315
|
const dashboardLink = `https://www.keyway.sh/dashboard/vaults/${repoFullName}`;
|
|
1283
1316
|
console.log(`
|
|
1284
|
-
${
|
|
1317
|
+
${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
|
|
1285
1318
|
await shutdownAnalytics();
|
|
1286
1319
|
} catch (error) {
|
|
1287
1320
|
let message;
|
|
@@ -1294,7 +1327,7 @@ ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
|
|
|
1294
1327
|
const availableEnvs = envNotFoundMatch[2];
|
|
1295
1328
|
message = `Environment '${requestedEnv}' does not exist in this vault.`;
|
|
1296
1329
|
hint = `Available environments: ${availableEnvs}
|
|
1297
|
-
Use ${
|
|
1330
|
+
Use ${pc6.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1298
1331
|
}
|
|
1299
1332
|
if (error.statusCode === 403 && (error.upgradeUrl || message.toLowerCase().includes("read-only"))) {
|
|
1300
1333
|
const upgradeMessage = message.toLowerCase().includes("read-only") ? "This vault is read-only on your current plan." : message;
|
|
@@ -1317,10 +1350,10 @@ Use ${pc5.cyan(`keyway push --env <environment>`)} to specify one, or create '${
|
|
|
1317
1350
|
error: message
|
|
1318
1351
|
});
|
|
1319
1352
|
await shutdownAnalytics();
|
|
1320
|
-
console.error(
|
|
1353
|
+
console.error(pc6.red(`
|
|
1321
1354
|
\u2717 ${message}`));
|
|
1322
1355
|
if (hint) {
|
|
1323
|
-
console.error(
|
|
1356
|
+
console.error(pc6.gray(`
|
|
1324
1357
|
${hint}`));
|
|
1325
1358
|
}
|
|
1326
1359
|
process.exit(1);
|
|
@@ -1355,7 +1388,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1355
1388
|
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1356
1389
|
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1357
1390
|
console.log("");
|
|
1358
|
-
const { shouldProceed } = await
|
|
1391
|
+
const { shouldProceed } = await prompts6({
|
|
1359
1392
|
type: "confirm",
|
|
1360
1393
|
name: "shouldProceed",
|
|
1361
1394
|
message: "Open browser to sign in?",
|
|
@@ -1365,8 +1398,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1365
1398
|
throw new Error('Setup required. Run "keyway init" when ready.');
|
|
1366
1399
|
}
|
|
1367
1400
|
await openUrl(deviceStart.verificationUriComplete);
|
|
1368
|
-
console.log(
|
|
1369
|
-
console.log(
|
|
1401
|
+
console.log(pc7.blue("\u23F3 Waiting for authorization..."));
|
|
1402
|
+
console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1370
1403
|
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1371
1404
|
const startTime = Date.now();
|
|
1372
1405
|
let accessToken = null;
|
|
@@ -1381,7 +1414,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1381
1414
|
githubLogin: result.githubLogin,
|
|
1382
1415
|
expiresAt: result.expiresAt
|
|
1383
1416
|
});
|
|
1384
|
-
console.log(
|
|
1417
|
+
console.log(pc7.green("\u2713 Signed in!"));
|
|
1385
1418
|
if (result.githubLogin) {
|
|
1386
1419
|
identifyUser(result.githubLogin, {
|
|
1387
1420
|
github_username: result.githubLogin,
|
|
@@ -1391,7 +1424,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1391
1424
|
break;
|
|
1392
1425
|
}
|
|
1393
1426
|
consecutiveErrors = 0;
|
|
1394
|
-
process.stdout.write(
|
|
1427
|
+
process.stdout.write(pc7.gray("."));
|
|
1395
1428
|
} catch (error) {
|
|
1396
1429
|
consecutiveErrors++;
|
|
1397
1430
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1402,35 +1435,35 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1402
1435
|
}
|
|
1403
1436
|
if (!accessToken) {
|
|
1404
1437
|
console.log("");
|
|
1405
|
-
console.log(
|
|
1438
|
+
console.log(pc7.yellow("\u26A0 Timed out waiting for sign in."));
|
|
1406
1439
|
throw new Error("Sign in timed out. Please try again.");
|
|
1407
1440
|
}
|
|
1408
1441
|
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1409
1442
|
if (installStatus.installed) {
|
|
1410
|
-
console.log(
|
|
1443
|
+
console.log(pc7.green("\u2713 GitHub App installed"));
|
|
1411
1444
|
console.log("");
|
|
1412
1445
|
return accessToken;
|
|
1413
1446
|
}
|
|
1414
1447
|
console.log("");
|
|
1415
|
-
console.log(
|
|
1416
|
-
console.log(
|
|
1448
|
+
console.log(pc7.yellow("\u26A0 GitHub App not installed on this repository"));
|
|
1449
|
+
console.log(pc7.gray(" The Keyway GitHub App is required for secure access."));
|
|
1417
1450
|
console.log("");
|
|
1418
|
-
const { shouldInstall } = await
|
|
1451
|
+
const { shouldInstall } = await prompts6({
|
|
1419
1452
|
type: "confirm",
|
|
1420
1453
|
name: "shouldInstall",
|
|
1421
1454
|
message: "Open browser to install GitHub App?",
|
|
1422
1455
|
initial: true
|
|
1423
1456
|
});
|
|
1424
1457
|
if (!shouldInstall) {
|
|
1425
|
-
console.log(
|
|
1458
|
+
console.log(pc7.gray(`
|
|
1426
1459
|
Install later: ${installUrl}`));
|
|
1427
1460
|
throw new Error("GitHub App installation required.");
|
|
1428
1461
|
}
|
|
1429
1462
|
await openUrl(installUrl);
|
|
1430
|
-
console.log(
|
|
1431
|
-
console.log(
|
|
1432
|
-
console.log(
|
|
1433
|
-
console.log(
|
|
1463
|
+
console.log(pc7.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1464
|
+
console.log(pc7.gray(' Add this repository and click "Install"'));
|
|
1465
|
+
console.log(pc7.gray(" Then return here - the CLI will detect it automatically"));
|
|
1466
|
+
console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1434
1467
|
const installStartTime = Date.now();
|
|
1435
1468
|
consecutiveErrors = 0;
|
|
1436
1469
|
while (Date.now() - installStartTime < POLL_TIMEOUT_MS) {
|
|
@@ -1438,12 +1471,12 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1438
1471
|
try {
|
|
1439
1472
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1440
1473
|
if (pollStatus.installed) {
|
|
1441
|
-
console.log(
|
|
1474
|
+
console.log(pc7.green("\u2713 GitHub App installed!"));
|
|
1442
1475
|
console.log("");
|
|
1443
1476
|
return accessToken;
|
|
1444
1477
|
}
|
|
1445
1478
|
consecutiveErrors = 0;
|
|
1446
|
-
process.stdout.write(
|
|
1479
|
+
process.stdout.write(pc7.gray("."));
|
|
1447
1480
|
} catch (error) {
|
|
1448
1481
|
consecutiveErrors++;
|
|
1449
1482
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1453,8 +1486,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1453
1486
|
}
|
|
1454
1487
|
}
|
|
1455
1488
|
console.log("");
|
|
1456
|
-
console.log(
|
|
1457
|
-
console.log(
|
|
1489
|
+
console.log(pc7.yellow("\u26A0 Timed out waiting for installation."));
|
|
1490
|
+
console.log(pc7.gray(` Install the GitHub App: ${installUrl}`));
|
|
1458
1491
|
throw new Error("GitHub App installation timed out.");
|
|
1459
1492
|
}
|
|
1460
1493
|
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
@@ -1464,7 +1497,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1464
1497
|
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1465
1498
|
} catch (error) {
|
|
1466
1499
|
if (error instanceof APIError && error.statusCode === 401) {
|
|
1467
|
-
console.log(
|
|
1500
|
+
console.log(pc7.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1468
1501
|
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1469
1502
|
clearAuth2();
|
|
1470
1503
|
return null;
|
|
@@ -1475,29 +1508,29 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1475
1508
|
return accessToken;
|
|
1476
1509
|
}
|
|
1477
1510
|
console.log("");
|
|
1478
|
-
console.log(
|
|
1511
|
+
console.log(pc7.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1479
1512
|
console.log("");
|
|
1480
|
-
console.log(
|
|
1481
|
-
console.log(
|
|
1513
|
+
console.log(pc7.gray(" The Keyway GitHub App is required to securely manage secrets."));
|
|
1514
|
+
console.log(pc7.gray(" It only requests minimal permissions (repository metadata)."));
|
|
1482
1515
|
console.log("");
|
|
1483
1516
|
if (!isInteractive()) {
|
|
1484
|
-
console.log(
|
|
1517
|
+
console.log(pc7.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1485
1518
|
throw new Error("GitHub App installation required.");
|
|
1486
1519
|
}
|
|
1487
|
-
const { shouldInstall } = await
|
|
1520
|
+
const { shouldInstall } = await prompts6({
|
|
1488
1521
|
type: "confirm",
|
|
1489
1522
|
name: "shouldInstall",
|
|
1490
1523
|
message: "Open browser to install Keyway GitHub App?",
|
|
1491
1524
|
initial: true
|
|
1492
1525
|
});
|
|
1493
1526
|
if (!shouldInstall) {
|
|
1494
|
-
console.log(
|
|
1527
|
+
console.log(pc7.gray(`
|
|
1495
1528
|
You can install later: ${status.installUrl}`));
|
|
1496
1529
|
throw new Error("GitHub App installation required.");
|
|
1497
1530
|
}
|
|
1498
1531
|
await openUrl(status.installUrl);
|
|
1499
|
-
console.log(
|
|
1500
|
-
console.log(
|
|
1532
|
+
console.log(pc7.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1533
|
+
console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1501
1534
|
const startTime = Date.now();
|
|
1502
1535
|
let consecutiveErrors = 0;
|
|
1503
1536
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
@@ -1505,12 +1538,12 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1505
1538
|
try {
|
|
1506
1539
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1507
1540
|
if (pollStatus.installed) {
|
|
1508
|
-
console.log(
|
|
1541
|
+
console.log(pc7.green("\u2713 GitHub App installed!"));
|
|
1509
1542
|
console.log("");
|
|
1510
1543
|
return accessToken;
|
|
1511
1544
|
}
|
|
1512
1545
|
consecutiveErrors = 0;
|
|
1513
|
-
process.stdout.write(
|
|
1546
|
+
process.stdout.write(pc7.gray("."));
|
|
1514
1547
|
} catch (error) {
|
|
1515
1548
|
consecutiveErrors++;
|
|
1516
1549
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1520,35 +1553,35 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1520
1553
|
}
|
|
1521
1554
|
}
|
|
1522
1555
|
console.log("");
|
|
1523
|
-
console.log(
|
|
1524
|
-
console.log(
|
|
1556
|
+
console.log(pc7.yellow("\u26A0 Timed out waiting for installation."));
|
|
1557
|
+
console.log(pc7.gray(` You can install the GitHub App later: ${status.installUrl}`));
|
|
1525
1558
|
throw new Error("GitHub App installation timed out.");
|
|
1526
1559
|
}
|
|
1527
1560
|
async function initCommand(options = {}) {
|
|
1528
1561
|
try {
|
|
1529
1562
|
const repoFullName = getCurrentRepoFullName();
|
|
1530
1563
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1531
|
-
console.log(
|
|
1532
|
-
console.log(` ${
|
|
1564
|
+
console.log(pc7.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1565
|
+
console.log(` ${pc7.gray("Repository:")} ${pc7.white(repoFullName)}`);
|
|
1533
1566
|
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1534
1567
|
allowPrompt: options.loginPrompt !== false
|
|
1535
1568
|
});
|
|
1536
1569
|
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1537
1570
|
const vaultExists = await checkVaultExists(accessToken, repoFullName);
|
|
1538
1571
|
if (vaultExists) {
|
|
1539
|
-
console.log(
|
|
1540
|
-
console.log(` ${
|
|
1541
|
-
console.log(` ${
|
|
1572
|
+
console.log(pc7.green("\n\u2713 Already initialized!\n"));
|
|
1573
|
+
console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets`);
|
|
1574
|
+
console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(dashboardLink)}`);
|
|
1542
1575
|
console.log("");
|
|
1543
1576
|
await shutdownAnalytics();
|
|
1544
1577
|
return;
|
|
1545
1578
|
}
|
|
1546
1579
|
await initVault(repoFullName, accessToken);
|
|
1547
|
-
console.log(
|
|
1580
|
+
console.log(pc7.green("\u2713 Vault created!"));
|
|
1548
1581
|
try {
|
|
1549
1582
|
const badgeAdded = await addBadgeToReadme(true);
|
|
1550
1583
|
if (badgeAdded) {
|
|
1551
|
-
console.log(
|
|
1584
|
+
console.log(pc7.green("\u2713 Badge added to README.md"));
|
|
1552
1585
|
}
|
|
1553
1586
|
} catch {
|
|
1554
1587
|
}
|
|
@@ -1556,9 +1589,9 @@ async function initCommand(options = {}) {
|
|
|
1556
1589
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1557
1590
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1558
1591
|
if (envCandidates.length > 0 && isInteractive2) {
|
|
1559
|
-
console.log(
|
|
1592
|
+
console.log(pc7.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1560
1593
|
`));
|
|
1561
|
-
const { shouldPush } = await
|
|
1594
|
+
const { shouldPush } = await prompts6({
|
|
1562
1595
|
type: "confirm",
|
|
1563
1596
|
name: "shouldPush",
|
|
1564
1597
|
message: "Push secrets now?",
|
|
@@ -1570,25 +1603,36 @@ async function initCommand(options = {}) {
|
|
|
1570
1603
|
return;
|
|
1571
1604
|
}
|
|
1572
1605
|
}
|
|
1573
|
-
console.log(
|
|
1606
|
+
console.log(pc7.dim("\u2500".repeat(50)));
|
|
1574
1607
|
console.log("");
|
|
1575
1608
|
if (envCandidates.length === 0) {
|
|
1576
|
-
|
|
1577
|
-
|
|
1609
|
+
if (isInteractive2) {
|
|
1610
|
+
const created = await promptCreateEnvFile();
|
|
1611
|
+
if (created) {
|
|
1612
|
+
console.log(` Add your variables and run ${pc7.cyan("keyway push")}
|
|
1578
1613
|
`);
|
|
1614
|
+
} else {
|
|
1615
|
+
console.log(` Next: Create ${pc7.cyan(".env")} and run ${pc7.cyan("keyway push")}
|
|
1616
|
+
`);
|
|
1617
|
+
}
|
|
1618
|
+
} else {
|
|
1619
|
+
console.log(`${pc7.yellow("\u26A0")} No .env file found - your vault is empty`);
|
|
1620
|
+
console.log(` Next: Create ${pc7.cyan(".env")} and run ${pc7.cyan("keyway push")}
|
|
1621
|
+
`);
|
|
1622
|
+
}
|
|
1579
1623
|
} else {
|
|
1580
|
-
console.log(` ${
|
|
1624
|
+
console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets
|
|
1581
1625
|
`);
|
|
1582
1626
|
}
|
|
1583
|
-
console.log(` ${
|
|
1627
|
+
console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(dashboardLink)}`);
|
|
1584
1628
|
console.log("");
|
|
1585
1629
|
await shutdownAnalytics();
|
|
1586
1630
|
} catch (error) {
|
|
1587
1631
|
if (error instanceof APIError) {
|
|
1588
1632
|
if (error.statusCode === 409) {
|
|
1589
|
-
console.log(
|
|
1590
|
-
console.log(` ${
|
|
1591
|
-
console.log(` ${
|
|
1633
|
+
console.log(pc7.green("\n\u2713 Already initialized!\n"));
|
|
1634
|
+
console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets`);
|
|
1635
|
+
console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
|
|
1592
1636
|
console.log("");
|
|
1593
1637
|
await shutdownAnalytics();
|
|
1594
1638
|
return;
|
|
@@ -1606,25 +1650,25 @@ async function initCommand(options = {}) {
|
|
|
1606
1650
|
error: message
|
|
1607
1651
|
});
|
|
1608
1652
|
await shutdownAnalytics();
|
|
1609
|
-
console.error(
|
|
1653
|
+
console.error(pc7.red(`
|
|
1610
1654
|
\u2717 ${message}`));
|
|
1611
1655
|
process.exit(1);
|
|
1612
1656
|
}
|
|
1613
1657
|
}
|
|
1614
1658
|
|
|
1615
1659
|
// src/cmds/pull.ts
|
|
1616
|
-
import
|
|
1617
|
-
import
|
|
1618
|
-
import
|
|
1619
|
-
import
|
|
1660
|
+
import pc8 from "picocolors";
|
|
1661
|
+
import fs6 from "fs";
|
|
1662
|
+
import path6 from "path";
|
|
1663
|
+
import prompts7 from "prompts";
|
|
1620
1664
|
async function pullCommand(options) {
|
|
1621
1665
|
try {
|
|
1622
1666
|
const environment = options.env || "development";
|
|
1623
1667
|
const envFile = options.file || ".env";
|
|
1624
|
-
console.log(
|
|
1625
|
-
console.log(`Environment: ${
|
|
1668
|
+
console.log(pc8.blue("\u{1F510} Pulling secrets from Keyway...\n"));
|
|
1669
|
+
console.log(`Environment: ${pc8.cyan(environment)}`);
|
|
1626
1670
|
const repoFullName = getCurrentRepoFullName();
|
|
1627
|
-
console.log(`Repository: ${
|
|
1671
|
+
console.log(`Repository: ${pc8.cyan(repoFullName)}`);
|
|
1628
1672
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1629
1673
|
trackEvent(AnalyticsEvents.CLI_PULL, {
|
|
1630
1674
|
repoFullName,
|
|
@@ -1632,16 +1676,16 @@ async function pullCommand(options) {
|
|
|
1632
1676
|
});
|
|
1633
1677
|
console.log("\nDownloading secrets...");
|
|
1634
1678
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1635
|
-
const envFilePath =
|
|
1636
|
-
if (
|
|
1679
|
+
const envFilePath = path6.resolve(process.cwd(), envFile);
|
|
1680
|
+
if (fs6.existsSync(envFilePath)) {
|
|
1637
1681
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1638
1682
|
if (options.yes) {
|
|
1639
|
-
console.log(
|
|
1683
|
+
console.log(pc8.yellow(`
|
|
1640
1684
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1641
1685
|
} else if (!isInteractive2) {
|
|
1642
1686
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
1643
1687
|
} else {
|
|
1644
|
-
const { confirm } = await
|
|
1688
|
+
const { confirm } = await prompts7(
|
|
1645
1689
|
{
|
|
1646
1690
|
type: "confirm",
|
|
1647
1691
|
name: "confirm",
|
|
@@ -1655,21 +1699,21 @@ async function pullCommand(options) {
|
|
|
1655
1699
|
}
|
|
1656
1700
|
);
|
|
1657
1701
|
if (!confirm) {
|
|
1658
|
-
console.log(
|
|
1702
|
+
console.log(pc8.yellow("Pull aborted."));
|
|
1659
1703
|
return;
|
|
1660
1704
|
}
|
|
1661
1705
|
}
|
|
1662
1706
|
}
|
|
1663
|
-
|
|
1707
|
+
fs6.writeFileSync(envFilePath, response.content, "utf-8");
|
|
1664
1708
|
const lines = response.content.split("\n").filter((line) => {
|
|
1665
1709
|
const trimmed = line.trim();
|
|
1666
1710
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1667
1711
|
});
|
|
1668
|
-
console.log(
|
|
1712
|
+
console.log(pc8.green(`
|
|
1669
1713
|
\u2713 Secrets downloaded successfully`));
|
|
1670
1714
|
console.log(`
|
|
1671
|
-
File: ${
|
|
1672
|
-
console.log(`Variables: ${
|
|
1715
|
+
File: ${pc8.cyan(envFile)}`);
|
|
1716
|
+
console.log(`Variables: ${pc8.cyan(lines.length.toString())}`);
|
|
1673
1717
|
await shutdownAnalytics();
|
|
1674
1718
|
} catch (error) {
|
|
1675
1719
|
const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
|
|
@@ -1678,14 +1722,14 @@ File: ${pc7.cyan(envFile)}`);
|
|
|
1678
1722
|
error: message
|
|
1679
1723
|
});
|
|
1680
1724
|
await shutdownAnalytics();
|
|
1681
|
-
console.error(
|
|
1725
|
+
console.error(pc8.red(`
|
|
1682
1726
|
\u2717 ${message}`));
|
|
1683
1727
|
process.exit(1);
|
|
1684
1728
|
}
|
|
1685
1729
|
}
|
|
1686
1730
|
|
|
1687
1731
|
// src/cmds/doctor.ts
|
|
1688
|
-
import
|
|
1732
|
+
import pc9 from "picocolors";
|
|
1689
1733
|
|
|
1690
1734
|
// src/core/doctor.ts
|
|
1691
1735
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -1939,9 +1983,9 @@ async function runAllChecks(options = {}) {
|
|
|
1939
1983
|
// src/cmds/doctor.ts
|
|
1940
1984
|
function formatSummary(results) {
|
|
1941
1985
|
const parts = [
|
|
1942
|
-
|
|
1943
|
-
results.summary.warn > 0 ?
|
|
1944
|
-
results.summary.fail > 0 ?
|
|
1986
|
+
pc9.green(`${results.summary.pass} passed`),
|
|
1987
|
+
results.summary.warn > 0 ? pc9.yellow(`${results.summary.warn} warnings`) : null,
|
|
1988
|
+
results.summary.fail > 0 ? pc9.red(`${results.summary.fail} failed`) : null
|
|
1945
1989
|
].filter(Boolean);
|
|
1946
1990
|
return parts.join(", ");
|
|
1947
1991
|
}
|
|
@@ -1958,20 +2002,20 @@ async function doctorCommand(options = {}) {
|
|
|
1958
2002
|
process.stdout.write(JSON.stringify(results, null, 0) + "\n");
|
|
1959
2003
|
process.exit(results.exitCode);
|
|
1960
2004
|
}
|
|
1961
|
-
console.log(
|
|
2005
|
+
console.log(pc9.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
|
|
1962
2006
|
results.checks.forEach((check) => {
|
|
1963
|
-
const icon = check.status === "pass" ?
|
|
1964
|
-
const detail = check.detail ?
|
|
2007
|
+
const icon = check.status === "pass" ? pc9.green("\u2713") : check.status === "warn" ? pc9.yellow("!") : pc9.red("\u2717");
|
|
2008
|
+
const detail = check.detail ? pc9.dim(` \u2014 ${check.detail}`) : "";
|
|
1965
2009
|
console.log(` ${icon} ${check.name}${detail}`);
|
|
1966
2010
|
});
|
|
1967
2011
|
console.log(`
|
|
1968
2012
|
Summary: ${formatSummary(results)}`);
|
|
1969
2013
|
if (results.summary.fail > 0) {
|
|
1970
|
-
console.log(
|
|
2014
|
+
console.log(pc9.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
|
|
1971
2015
|
} else if (results.summary.warn > 0) {
|
|
1972
|
-
console.log(
|
|
2016
|
+
console.log(pc9.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
|
|
1973
2017
|
} else {
|
|
1974
|
-
console.log(
|
|
2018
|
+
console.log(pc9.green("\u2728 All checks passed! Your environment is ready for Keyway."));
|
|
1975
2019
|
}
|
|
1976
2020
|
process.exit(results.exitCode);
|
|
1977
2021
|
} catch (error) {
|
|
@@ -1992,7 +2036,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1992
2036
|
};
|
|
1993
2037
|
process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
|
|
1994
2038
|
} else {
|
|
1995
|
-
console.error(
|
|
2039
|
+
console.error(pc9.red(`
|
|
1996
2040
|
\u2717 ${message}`));
|
|
1997
2041
|
}
|
|
1998
2042
|
process.exit(1);
|
|
@@ -2000,8 +2044,8 @@ Summary: ${formatSummary(results)}`);
|
|
|
2000
2044
|
}
|
|
2001
2045
|
|
|
2002
2046
|
// src/cmds/connect.ts
|
|
2003
|
-
import
|
|
2004
|
-
import
|
|
2047
|
+
import pc10 from "picocolors";
|
|
2048
|
+
import prompts8 from "prompts";
|
|
2005
2049
|
var TOKEN_AUTH_PROVIDERS = ["railway"];
|
|
2006
2050
|
function getTokenCreationUrl(provider) {
|
|
2007
2051
|
switch (provider) {
|
|
@@ -2014,37 +2058,37 @@ function getTokenCreationUrl(provider) {
|
|
|
2014
2058
|
async function connectWithTokenFlow(accessToken, provider, displayName) {
|
|
2015
2059
|
const tokenUrl = getTokenCreationUrl(provider);
|
|
2016
2060
|
if (provider === "railway") {
|
|
2017
|
-
console.log(
|
|
2018
|
-
console.log(
|
|
2061
|
+
console.log(pc10.yellow("\nTip: Select the workspace containing your projects."));
|
|
2062
|
+
console.log(pc10.yellow(` Do NOT use "No workspace" - it won't have access to your projects.`));
|
|
2019
2063
|
}
|
|
2020
2064
|
await openUrl(tokenUrl);
|
|
2021
|
-
const { token } = await
|
|
2065
|
+
const { token } = await prompts8({
|
|
2022
2066
|
type: "password",
|
|
2023
2067
|
name: "token",
|
|
2024
2068
|
message: `${displayName} API Token:`
|
|
2025
2069
|
});
|
|
2026
2070
|
if (!token) {
|
|
2027
|
-
console.log(
|
|
2071
|
+
console.log(pc10.gray("Cancelled."));
|
|
2028
2072
|
return false;
|
|
2029
2073
|
}
|
|
2030
|
-
console.log(
|
|
2074
|
+
console.log(pc10.gray("\nValidating token..."));
|
|
2031
2075
|
try {
|
|
2032
2076
|
const result = await connectWithToken(accessToken, provider, token);
|
|
2033
2077
|
if (result.success) {
|
|
2034
|
-
console.log(
|
|
2078
|
+
console.log(pc10.green(`
|
|
2035
2079
|
\u2713 Connected to ${displayName}!`));
|
|
2036
|
-
console.log(
|
|
2080
|
+
console.log(pc10.gray(` Account: ${result.user.username}`));
|
|
2037
2081
|
if (result.user.teamName) {
|
|
2038
|
-
console.log(
|
|
2082
|
+
console.log(pc10.gray(` Team: ${result.user.teamName}`));
|
|
2039
2083
|
}
|
|
2040
2084
|
return true;
|
|
2041
2085
|
} else {
|
|
2042
|
-
console.log(
|
|
2086
|
+
console.log(pc10.red("\n\u2717 Connection failed."));
|
|
2043
2087
|
return false;
|
|
2044
2088
|
}
|
|
2045
2089
|
} catch (error) {
|
|
2046
2090
|
const message = error instanceof Error ? error.message : "Token validation failed";
|
|
2047
|
-
console.log(
|
|
2091
|
+
console.log(pc10.red(`
|
|
2048
2092
|
\u2717 ${message}`));
|
|
2049
2093
|
return false;
|
|
2050
2094
|
}
|
|
@@ -2053,7 +2097,7 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
|
2053
2097
|
const authUrl = getProviderAuthUrl(provider, accessToken);
|
|
2054
2098
|
const startTime = /* @__PURE__ */ new Date();
|
|
2055
2099
|
await openUrl(authUrl);
|
|
2056
|
-
console.log(
|
|
2100
|
+
console.log(pc10.gray("Waiting for authorization..."));
|
|
2057
2101
|
const maxAttempts = 60;
|
|
2058
2102
|
let attempts = 0;
|
|
2059
2103
|
while (attempts < maxAttempts) {
|
|
@@ -2065,15 +2109,15 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
|
2065
2109
|
(c) => c.provider === provider && new Date(c.createdAt) > startTime
|
|
2066
2110
|
);
|
|
2067
2111
|
if (newConn) {
|
|
2068
|
-
console.log(
|
|
2112
|
+
console.log(pc10.green(`
|
|
2069
2113
|
\u2713 Connected to ${displayName}!`));
|
|
2070
2114
|
return true;
|
|
2071
2115
|
}
|
|
2072
2116
|
} catch {
|
|
2073
2117
|
}
|
|
2074
2118
|
}
|
|
2075
|
-
console.log(
|
|
2076
|
-
console.log(
|
|
2119
|
+
console.log(pc10.red("\n\u2717 Authorization timeout."));
|
|
2120
|
+
console.log(pc10.gray("Run `keyway connections` to check if the connection was established."));
|
|
2077
2121
|
return false;
|
|
2078
2122
|
}
|
|
2079
2123
|
async function connectCommand(provider, options = {}) {
|
|
@@ -2083,30 +2127,30 @@ async function connectCommand(provider, options = {}) {
|
|
|
2083
2127
|
const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
|
|
2084
2128
|
if (!providerInfo) {
|
|
2085
2129
|
const available = providers.map((p) => p.name).join(", ");
|
|
2086
|
-
console.error(
|
|
2087
|
-
console.log(
|
|
2130
|
+
console.error(pc10.red(`Unknown provider: ${provider}`));
|
|
2131
|
+
console.log(pc10.gray(`Available providers: ${available || "none"}`));
|
|
2088
2132
|
process.exit(1);
|
|
2089
2133
|
}
|
|
2090
2134
|
if (!providerInfo.configured) {
|
|
2091
|
-
console.error(
|
|
2092
|
-
console.log(
|
|
2135
|
+
console.error(pc10.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
|
|
2136
|
+
console.log(pc10.gray("Contact your administrator to enable this integration."));
|
|
2093
2137
|
process.exit(1);
|
|
2094
2138
|
}
|
|
2095
2139
|
const { connections } = await getConnections(accessToken);
|
|
2096
2140
|
const existingConnection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2097
2141
|
if (existingConnection) {
|
|
2098
|
-
const { reconnect } = await
|
|
2142
|
+
const { reconnect } = await prompts8({
|
|
2099
2143
|
type: "confirm",
|
|
2100
2144
|
name: "reconnect",
|
|
2101
2145
|
message: `You're already connected to ${providerInfo.displayName}. Reconnect?`,
|
|
2102
2146
|
initial: false
|
|
2103
2147
|
});
|
|
2104
2148
|
if (!reconnect) {
|
|
2105
|
-
console.log(
|
|
2149
|
+
console.log(pc10.gray("Keeping existing connection."));
|
|
2106
2150
|
return;
|
|
2107
2151
|
}
|
|
2108
2152
|
}
|
|
2109
|
-
console.log(
|
|
2153
|
+
console.log(pc10.blue(`
|
|
2110
2154
|
Connecting to ${providerInfo.displayName}...
|
|
2111
2155
|
`));
|
|
2112
2156
|
let connected = false;
|
|
@@ -2125,7 +2169,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
2125
2169
|
command: "connect",
|
|
2126
2170
|
error: truncateMessage(message)
|
|
2127
2171
|
});
|
|
2128
|
-
console.error(
|
|
2172
|
+
console.error(pc10.red(`
|
|
2129
2173
|
\u2717 ${message}`));
|
|
2130
2174
|
process.exit(1);
|
|
2131
2175
|
}
|
|
@@ -2135,24 +2179,24 @@ async function connectionsCommand(options = {}) {
|
|
|
2135
2179
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2136
2180
|
const { connections } = await getConnections(accessToken);
|
|
2137
2181
|
if (connections.length === 0) {
|
|
2138
|
-
console.log(
|
|
2139
|
-
console.log(
|
|
2140
|
-
console.log(
|
|
2182
|
+
console.log(pc10.gray("No provider connections found."));
|
|
2183
|
+
console.log(pc10.gray("\nConnect to a provider with: keyway connect <provider>"));
|
|
2184
|
+
console.log(pc10.gray("Available providers: vercel, railway"));
|
|
2141
2185
|
return;
|
|
2142
2186
|
}
|
|
2143
|
-
console.log(
|
|
2187
|
+
console.log(pc10.blue("\n\u{1F4E1} Provider Connections\n"));
|
|
2144
2188
|
for (const conn of connections) {
|
|
2145
2189
|
const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
|
|
2146
|
-
const teamInfo = conn.providerTeamId ?
|
|
2190
|
+
const teamInfo = conn.providerTeamId ? pc10.gray(` (Team: ${conn.providerTeamId})`) : "";
|
|
2147
2191
|
const date = new Date(conn.createdAt).toLocaleDateString();
|
|
2148
|
-
console.log(` ${
|
|
2149
|
-
console.log(
|
|
2150
|
-
console.log(
|
|
2192
|
+
console.log(` ${pc10.green("\u25CF")} ${pc10.bold(providerName)}${teamInfo}`);
|
|
2193
|
+
console.log(pc10.gray(` Connected: ${date}`));
|
|
2194
|
+
console.log(pc10.gray(` ID: ${conn.id}`));
|
|
2151
2195
|
console.log("");
|
|
2152
2196
|
}
|
|
2153
2197
|
} catch (error) {
|
|
2154
2198
|
const message = error instanceof Error ? error.message : "Failed to list connections";
|
|
2155
|
-
console.error(
|
|
2199
|
+
console.error(pc10.red(`
|
|
2156
2200
|
\u2717 ${message}`));
|
|
2157
2201
|
process.exit(1);
|
|
2158
2202
|
}
|
|
@@ -2163,22 +2207,22 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2163
2207
|
const { connections } = await getConnections(accessToken);
|
|
2164
2208
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2165
2209
|
if (!connection) {
|
|
2166
|
-
console.log(
|
|
2210
|
+
console.log(pc10.gray(`No connection found for provider: ${provider}`));
|
|
2167
2211
|
return;
|
|
2168
2212
|
}
|
|
2169
2213
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2170
|
-
const { confirm } = await
|
|
2214
|
+
const { confirm } = await prompts8({
|
|
2171
2215
|
type: "confirm",
|
|
2172
2216
|
name: "confirm",
|
|
2173
2217
|
message: `Disconnect from ${providerName}?`,
|
|
2174
2218
|
initial: false
|
|
2175
2219
|
});
|
|
2176
2220
|
if (!confirm) {
|
|
2177
|
-
console.log(
|
|
2221
|
+
console.log(pc10.gray("Cancelled."));
|
|
2178
2222
|
return;
|
|
2179
2223
|
}
|
|
2180
2224
|
await deleteConnection(accessToken, connection.id);
|
|
2181
|
-
console.log(
|
|
2225
|
+
console.log(pc10.green(`
|
|
2182
2226
|
\u2713 Disconnected from ${providerName}`));
|
|
2183
2227
|
trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
|
|
2184
2228
|
provider: provider.toLowerCase()
|
|
@@ -2189,15 +2233,15 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2189
2233
|
command: "disconnect",
|
|
2190
2234
|
error: truncateMessage(message)
|
|
2191
2235
|
});
|
|
2192
|
-
console.error(
|
|
2236
|
+
console.error(pc10.red(`
|
|
2193
2237
|
\u2717 ${message}`));
|
|
2194
2238
|
process.exit(1);
|
|
2195
2239
|
}
|
|
2196
2240
|
}
|
|
2197
2241
|
|
|
2198
2242
|
// src/cmds/sync.ts
|
|
2199
|
-
import
|
|
2200
|
-
import
|
|
2243
|
+
import pc11 from "picocolors";
|
|
2244
|
+
import prompts9 from "prompts";
|
|
2201
2245
|
function mapToVercelEnvironment(keywayEnv) {
|
|
2202
2246
|
const mapping = {
|
|
2203
2247
|
production: "production",
|
|
@@ -2241,36 +2285,36 @@ function mapToProviderEnvironment(provider, keywayEnv) {
|
|
|
2241
2285
|
function displayDiffSummary(diff, providerName) {
|
|
2242
2286
|
const totalDiff = diff.onlyInKeyway.length + diff.onlyInProvider.length + diff.different.length;
|
|
2243
2287
|
if (totalDiff === 0 && diff.same.length > 0) {
|
|
2244
|
-
console.log(
|
|
2288
|
+
console.log(pc11.green(`
|
|
2245
2289
|
\u2713 Already in sync (${diff.same.length} secrets)`));
|
|
2246
2290
|
return;
|
|
2247
2291
|
}
|
|
2248
|
-
console.log(
|
|
2249
|
-
console.log(
|
|
2292
|
+
console.log(pc11.blue("\n\u{1F4CA} Comparison Summary\n"));
|
|
2293
|
+
console.log(pc11.gray(` Keyway: ${diff.keywayCount} secrets | ${providerName}: ${diff.providerCount} secrets
|
|
2250
2294
|
`));
|
|
2251
2295
|
if (diff.onlyInKeyway.length > 0) {
|
|
2252
|
-
console.log(
|
|
2253
|
-
diff.onlyInKeyway.slice(0, 3).forEach((key) => console.log(
|
|
2296
|
+
console.log(pc11.cyan(` \u2192 ${diff.onlyInKeyway.length} only in Keyway`));
|
|
2297
|
+
diff.onlyInKeyway.slice(0, 3).forEach((key) => console.log(pc11.gray(` ${key}`)));
|
|
2254
2298
|
if (diff.onlyInKeyway.length > 3) {
|
|
2255
|
-
console.log(
|
|
2299
|
+
console.log(pc11.gray(` ... and ${diff.onlyInKeyway.length - 3} more`));
|
|
2256
2300
|
}
|
|
2257
2301
|
}
|
|
2258
2302
|
if (diff.onlyInProvider.length > 0) {
|
|
2259
|
-
console.log(
|
|
2260
|
-
diff.onlyInProvider.slice(0, 3).forEach((key) => console.log(
|
|
2303
|
+
console.log(pc11.magenta(` \u2190 ${diff.onlyInProvider.length} only in ${providerName}`));
|
|
2304
|
+
diff.onlyInProvider.slice(0, 3).forEach((key) => console.log(pc11.gray(` ${key}`)));
|
|
2261
2305
|
if (diff.onlyInProvider.length > 3) {
|
|
2262
|
-
console.log(
|
|
2306
|
+
console.log(pc11.gray(` ... and ${diff.onlyInProvider.length - 3} more`));
|
|
2263
2307
|
}
|
|
2264
2308
|
}
|
|
2265
2309
|
if (diff.different.length > 0) {
|
|
2266
|
-
console.log(
|
|
2267
|
-
diff.different.slice(0, 3).forEach((key) => console.log(
|
|
2310
|
+
console.log(pc11.yellow(` \u2260 ${diff.different.length} with different values`));
|
|
2311
|
+
diff.different.slice(0, 3).forEach((key) => console.log(pc11.gray(` ${key}`)));
|
|
2268
2312
|
if (diff.different.length > 3) {
|
|
2269
|
-
console.log(
|
|
2313
|
+
console.log(pc11.gray(` ... and ${diff.different.length - 3} more`));
|
|
2270
2314
|
}
|
|
2271
2315
|
}
|
|
2272
2316
|
if (diff.same.length > 0) {
|
|
2273
|
-
console.log(
|
|
2317
|
+
console.log(pc11.gray(` = ${diff.same.length} identical`));
|
|
2274
2318
|
}
|
|
2275
2319
|
console.log("");
|
|
2276
2320
|
}
|
|
@@ -2317,25 +2361,25 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2317
2361
|
let title = displayName;
|
|
2318
2362
|
const badges = [];
|
|
2319
2363
|
if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
|
|
2320
|
-
badges.push(
|
|
2364
|
+
badges.push(pc11.green("\u2190 linked"));
|
|
2321
2365
|
} else if (p.name.toLowerCase() === repoName || p.serviceName?.toLowerCase() === repoName) {
|
|
2322
|
-
badges.push(
|
|
2366
|
+
badges.push(pc11.green("\u2190 same name"));
|
|
2323
2367
|
} else if (p.linkedRepo) {
|
|
2324
|
-
badges.push(
|
|
2368
|
+
badges.push(pc11.gray(`\u2192 ${p.linkedRepo}`));
|
|
2325
2369
|
}
|
|
2326
2370
|
if (badges.length > 0) {
|
|
2327
2371
|
title = `${displayName} ${badges.join(" ")}`;
|
|
2328
2372
|
}
|
|
2329
2373
|
return { title, value: p.id };
|
|
2330
2374
|
});
|
|
2331
|
-
const { projectChoice } = await
|
|
2375
|
+
const { projectChoice } = await prompts9({
|
|
2332
2376
|
type: "select",
|
|
2333
2377
|
name: "projectChoice",
|
|
2334
2378
|
message: "Select a project:",
|
|
2335
2379
|
choices
|
|
2336
2380
|
});
|
|
2337
2381
|
if (!projectChoice) {
|
|
2338
|
-
console.log(
|
|
2382
|
+
console.log(pc11.gray("Cancelled."));
|
|
2339
2383
|
process.exit(0);
|
|
2340
2384
|
}
|
|
2341
2385
|
return projects.find((p) => p.id === projectChoice);
|
|
@@ -2343,40 +2387,40 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2343
2387
|
async function syncCommand(provider, options = {}) {
|
|
2344
2388
|
try {
|
|
2345
2389
|
if (options.pull && options.allowDelete) {
|
|
2346
|
-
console.error(
|
|
2347
|
-
console.log(
|
|
2390
|
+
console.error(pc11.red("Error: --allow-delete cannot be used with --pull"));
|
|
2391
|
+
console.log(pc11.gray("The --allow-delete flag is only for push operations."));
|
|
2348
2392
|
process.exit(1);
|
|
2349
2393
|
}
|
|
2350
2394
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2351
2395
|
const repoFullName = detectGitRepo();
|
|
2352
2396
|
if (!repoFullName) {
|
|
2353
|
-
console.error(
|
|
2354
|
-
console.log(
|
|
2397
|
+
console.error(pc11.red("Could not detect Git repository."));
|
|
2398
|
+
console.log(pc11.gray("Run this command from a Git repository directory."));
|
|
2355
2399
|
process.exit(1);
|
|
2356
2400
|
}
|
|
2357
|
-
console.log(
|
|
2401
|
+
console.log(pc11.gray(`Repository: ${repoFullName}`));
|
|
2358
2402
|
const vaultExists = await checkVaultExists(accessToken, repoFullName);
|
|
2359
2403
|
if (!vaultExists) {
|
|
2360
|
-
console.log(
|
|
2404
|
+
console.log(pc11.yellow(`
|
|
2361
2405
|
No vault found for ${repoFullName}.`));
|
|
2362
|
-
const { shouldCreate } = await
|
|
2406
|
+
const { shouldCreate } = await prompts9({
|
|
2363
2407
|
type: "confirm",
|
|
2364
2408
|
name: "shouldCreate",
|
|
2365
2409
|
message: "Create vault now?",
|
|
2366
2410
|
initial: true
|
|
2367
2411
|
});
|
|
2368
2412
|
if (!shouldCreate) {
|
|
2369
|
-
console.log(
|
|
2413
|
+
console.log(pc11.gray("Cancelled. Run `keyway init` to create a vault first."));
|
|
2370
2414
|
process.exit(0);
|
|
2371
2415
|
}
|
|
2372
|
-
console.log(
|
|
2416
|
+
console.log(pc11.gray("\nCreating vault..."));
|
|
2373
2417
|
try {
|
|
2374
2418
|
await initVault(repoFullName, accessToken);
|
|
2375
|
-
console.log(
|
|
2419
|
+
console.log(pc11.green(`\u2713 Vault created for ${repoFullName}
|
|
2376
2420
|
`));
|
|
2377
2421
|
} catch (error) {
|
|
2378
2422
|
const message = error instanceof Error ? error.message : "Failed to create vault";
|
|
2379
|
-
console.error(
|
|
2423
|
+
console.error(pc11.red(`
|
|
2380
2424
|
\u2717 ${message}`));
|
|
2381
2425
|
process.exit(1);
|
|
2382
2426
|
}
|
|
@@ -2385,16 +2429,16 @@ No vault found for ${repoFullName}.`));
|
|
|
2385
2429
|
let connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2386
2430
|
if (!connection) {
|
|
2387
2431
|
const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2388
|
-
console.log(
|
|
2432
|
+
console.log(pc11.yellow(`
|
|
2389
2433
|
Not connected to ${providerDisplayName}.`));
|
|
2390
|
-
const { shouldConnect } = await
|
|
2434
|
+
const { shouldConnect } = await prompts9({
|
|
2391
2435
|
type: "confirm",
|
|
2392
2436
|
name: "shouldConnect",
|
|
2393
2437
|
message: `Connect to ${providerDisplayName} now?`,
|
|
2394
2438
|
initial: true
|
|
2395
2439
|
});
|
|
2396
2440
|
if (!shouldConnect) {
|
|
2397
|
-
console.log(
|
|
2441
|
+
console.log(pc11.gray("Cancelled."));
|
|
2398
2442
|
process.exit(0);
|
|
2399
2443
|
}
|
|
2400
2444
|
await connectCommand(provider, { loginPrompt: false });
|
|
@@ -2402,7 +2446,7 @@ Not connected to ${providerDisplayName}.`));
|
|
|
2402
2446
|
connections = refreshed.connections;
|
|
2403
2447
|
connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2404
2448
|
if (!connection) {
|
|
2405
|
-
console.error(
|
|
2449
|
+
console.error(pc11.red(`
|
|
2406
2450
|
Connection to ${providerDisplayName} failed.`));
|
|
2407
2451
|
process.exit(1);
|
|
2408
2452
|
}
|
|
@@ -2410,7 +2454,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2410
2454
|
}
|
|
2411
2455
|
const { projects } = await getConnectionProjects(accessToken, connection.id);
|
|
2412
2456
|
if (projects.length === 0) {
|
|
2413
|
-
console.error(
|
|
2457
|
+
console.error(pc11.red(`No projects found in your ${provider} account.`));
|
|
2414
2458
|
process.exit(1);
|
|
2415
2459
|
}
|
|
2416
2460
|
let selectedProject;
|
|
@@ -2419,21 +2463,21 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2419
2463
|
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase() || p.serviceName?.toLowerCase() === options.project?.toLowerCase()
|
|
2420
2464
|
);
|
|
2421
2465
|
if (!found) {
|
|
2422
|
-
console.error(
|
|
2423
|
-
console.log(
|
|
2424
|
-
projects.forEach((p) => console.log(
|
|
2466
|
+
console.error(pc11.red(`Project not found: ${options.project}`));
|
|
2467
|
+
console.log(pc11.gray("Available projects:"));
|
|
2468
|
+
projects.forEach((p) => console.log(pc11.gray(` - ${getProjectDisplayName(p)}`)));
|
|
2425
2469
|
process.exit(1);
|
|
2426
2470
|
}
|
|
2427
2471
|
selectedProject = found;
|
|
2428
2472
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2429
2473
|
console.log("");
|
|
2430
|
-
console.log(
|
|
2431
|
-
console.log(
|
|
2432
|
-
console.log(
|
|
2433
|
-
console.log(
|
|
2434
|
-
console.log(
|
|
2474
|
+
console.log(pc11.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
2475
|
+
console.log(pc11.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2476
|
+
console.log(pc11.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
2477
|
+
console.log(pc11.yellow(` Current repo: ${repoFullName}`));
|
|
2478
|
+
console.log(pc11.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
|
|
2435
2479
|
if (selectedProject.linkedRepo) {
|
|
2436
|
-
console.log(
|
|
2480
|
+
console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2437
2481
|
}
|
|
2438
2482
|
console.log("");
|
|
2439
2483
|
}
|
|
@@ -2442,11 +2486,11 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2442
2486
|
if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
|
|
2443
2487
|
selectedProject = autoMatch.project;
|
|
2444
2488
|
const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
|
|
2445
|
-
console.log(
|
|
2489
|
+
console.log(pc11.green(`\u2713 Auto-selected project: ${getProjectDisplayName(selectedProject)} (${matchReason})`));
|
|
2446
2490
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2447
2491
|
const partialDisplayName = getProjectDisplayName(autoMatch.project);
|
|
2448
|
-
console.log(
|
|
2449
|
-
const { useDetected } = await
|
|
2492
|
+
console.log(pc11.yellow(`Detected project: ${partialDisplayName} (partial match)`));
|
|
2493
|
+
const { useDetected } = await prompts9({
|
|
2450
2494
|
type: "confirm",
|
|
2451
2495
|
name: "useDetected",
|
|
2452
2496
|
message: `Use ${partialDisplayName}?`,
|
|
@@ -2461,30 +2505,30 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2461
2505
|
selectedProject = projects[0];
|
|
2462
2506
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2463
2507
|
console.log("");
|
|
2464
|
-
console.log(
|
|
2465
|
-
console.log(
|
|
2466
|
-
console.log(
|
|
2467
|
-
console.log(
|
|
2468
|
-
console.log(
|
|
2508
|
+
console.log(pc11.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
2509
|
+
console.log(pc11.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2510
|
+
console.log(pc11.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
2511
|
+
console.log(pc11.yellow(` Current repo: ${repoFullName}`));
|
|
2512
|
+
console.log(pc11.yellow(` Only project: ${getProjectDisplayName(selectedProject)}`));
|
|
2469
2513
|
if (selectedProject.linkedRepo) {
|
|
2470
|
-
console.log(
|
|
2514
|
+
console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2471
2515
|
}
|
|
2472
2516
|
console.log("");
|
|
2473
|
-
const { continueAnyway } = await
|
|
2517
|
+
const { continueAnyway } = await prompts9({
|
|
2474
2518
|
type: "confirm",
|
|
2475
2519
|
name: "continueAnyway",
|
|
2476
2520
|
message: "Continue anyway?",
|
|
2477
2521
|
initial: false
|
|
2478
2522
|
});
|
|
2479
2523
|
if (!continueAnyway) {
|
|
2480
|
-
console.log(
|
|
2524
|
+
console.log(pc11.gray("Cancelled."));
|
|
2481
2525
|
process.exit(0);
|
|
2482
2526
|
}
|
|
2483
2527
|
}
|
|
2484
2528
|
} else {
|
|
2485
|
-
console.log(
|
|
2529
|
+
console.log(pc11.yellow(`
|
|
2486
2530
|
\u26A0\uFE0F No matching project found for ${repoFullName}`));
|
|
2487
|
-
console.log(
|
|
2531
|
+
console.log(pc11.gray("Select a project manually:\n"));
|
|
2488
2532
|
selectedProject = await promptProjectSelection(projects, repoFullName);
|
|
2489
2533
|
}
|
|
2490
2534
|
}
|
|
@@ -2492,23 +2536,23 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2492
2536
|
const autoMatch = findMatchingProject(projects, repoFullName);
|
|
2493
2537
|
if (autoMatch && autoMatch.project.id !== selectedProject.id) {
|
|
2494
2538
|
console.log("");
|
|
2495
|
-
console.log(
|
|
2496
|
-
console.log(
|
|
2497
|
-
console.log(
|
|
2498
|
-
console.log(
|
|
2499
|
-
console.log(
|
|
2539
|
+
console.log(pc11.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
2540
|
+
console.log(pc11.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
|
|
2541
|
+
console.log(pc11.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
2542
|
+
console.log(pc11.yellow(` Current repo: ${repoFullName}`));
|
|
2543
|
+
console.log(pc11.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
|
|
2500
2544
|
if (selectedProject.linkedRepo) {
|
|
2501
|
-
console.log(
|
|
2545
|
+
console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2502
2546
|
}
|
|
2503
2547
|
console.log("");
|
|
2504
|
-
const { continueAnyway } = await
|
|
2548
|
+
const { continueAnyway } = await prompts9({
|
|
2505
2549
|
type: "confirm",
|
|
2506
2550
|
name: "continueAnyway",
|
|
2507
2551
|
message: "Are you sure you want to sync with this project?",
|
|
2508
2552
|
initial: false
|
|
2509
2553
|
});
|
|
2510
2554
|
if (!continueAnyway) {
|
|
2511
|
-
console.log(
|
|
2555
|
+
console.log(pc11.gray("Cancelled."));
|
|
2512
2556
|
process.exit(0);
|
|
2513
2557
|
}
|
|
2514
2558
|
}
|
|
@@ -2522,7 +2566,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2522
2566
|
if (needsEnvPrompt || needsDirectionPrompt) {
|
|
2523
2567
|
if (needsEnvPrompt) {
|
|
2524
2568
|
const vaultEnvs = await getVaultEnvironments(accessToken, repoFullName);
|
|
2525
|
-
const { selectedEnv } = await
|
|
2569
|
+
const { selectedEnv } = await prompts9({
|
|
2526
2570
|
type: "select",
|
|
2527
2571
|
name: "selectedEnv",
|
|
2528
2572
|
message: "Keyway environment:",
|
|
@@ -2530,7 +2574,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2530
2574
|
initial: Math.max(0, vaultEnvs.indexOf("production"))
|
|
2531
2575
|
});
|
|
2532
2576
|
if (!selectedEnv) {
|
|
2533
|
-
console.log(
|
|
2577
|
+
console.log(pc11.gray("Cancelled."));
|
|
2534
2578
|
process.exit(0);
|
|
2535
2579
|
}
|
|
2536
2580
|
keywayEnv = selectedEnv;
|
|
@@ -2544,9 +2588,9 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2544
2588
|
providerEnv = mappedEnv;
|
|
2545
2589
|
} else if (selectedProject.environments.length === 1) {
|
|
2546
2590
|
providerEnv = selectedProject.environments[0];
|
|
2547
|
-
console.log(
|
|
2591
|
+
console.log(pc11.gray(`Using ${providerName} environment: ${providerEnv}`));
|
|
2548
2592
|
} else {
|
|
2549
|
-
const { selectedProviderEnv } = await
|
|
2593
|
+
const { selectedProviderEnv } = await prompts9({
|
|
2550
2594
|
type: "select",
|
|
2551
2595
|
name: "selectedProviderEnv",
|
|
2552
2596
|
message: `${providerName} environment:`,
|
|
@@ -2556,7 +2600,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2556
2600
|
))
|
|
2557
2601
|
});
|
|
2558
2602
|
if (!selectedProviderEnv) {
|
|
2559
|
-
console.log(
|
|
2603
|
+
console.log(pc11.gray("Cancelled."));
|
|
2560
2604
|
process.exit(0);
|
|
2561
2605
|
}
|
|
2562
2606
|
providerEnv = selectedProviderEnv;
|
|
@@ -2570,7 +2614,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2570
2614
|
if (needsDirectionPrompt) {
|
|
2571
2615
|
const effectiveKeywayEnv = keywayEnv || "production";
|
|
2572
2616
|
const effectiveProviderEnv = providerEnv || mapToProviderEnvironment(provider, effectiveKeywayEnv);
|
|
2573
|
-
console.log(
|
|
2617
|
+
console.log(pc11.gray("\nComparing secrets..."));
|
|
2574
2618
|
diff = await getSyncDiff(accessToken, repoFullName, {
|
|
2575
2619
|
connectionId: connection.id,
|
|
2576
2620
|
projectId: selectedProject.id,
|
|
@@ -2592,7 +2636,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2592
2636
|
} else if (diff.providerCount === 0 && diff.keywayCount > 0) {
|
|
2593
2637
|
defaultDirection = 0;
|
|
2594
2638
|
}
|
|
2595
|
-
const { selectedDirection } = await
|
|
2639
|
+
const { selectedDirection } = await prompts9({
|
|
2596
2640
|
type: "select",
|
|
2597
2641
|
name: "selectedDirection",
|
|
2598
2642
|
message: "Sync direction:",
|
|
@@ -2603,7 +2647,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2603
2647
|
initial: defaultDirection
|
|
2604
2648
|
});
|
|
2605
2649
|
if (!selectedDirection) {
|
|
2606
|
-
console.log(
|
|
2650
|
+
console.log(pc11.gray("Cancelled."));
|
|
2607
2651
|
process.exit(0);
|
|
2608
2652
|
}
|
|
2609
2653
|
direction = selectedDirection;
|
|
@@ -2620,10 +2664,10 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2620
2664
|
keywayEnv
|
|
2621
2665
|
);
|
|
2622
2666
|
if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
|
|
2623
|
-
console.log(
|
|
2667
|
+
console.log(pc11.yellow(`
|
|
2624
2668
|
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2625
|
-
console.log(
|
|
2626
|
-
const { importFirst } = await
|
|
2669
|
+
console.log(pc11.gray(` (Use --environment to sync a different environment)`));
|
|
2670
|
+
const { importFirst } = await prompts9({
|
|
2627
2671
|
type: "confirm",
|
|
2628
2672
|
name: "importFirst",
|
|
2629
2673
|
message: `Import secrets from ${providerName} first?`,
|
|
@@ -2664,7 +2708,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2664
2708
|
command: "sync",
|
|
2665
2709
|
error: truncateMessage(message)
|
|
2666
2710
|
});
|
|
2667
|
-
console.error(
|
|
2711
|
+
console.error(pc11.red(`
|
|
2668
2712
|
\u2717 ${message}`));
|
|
2669
2713
|
process.exit(1);
|
|
2670
2714
|
}
|
|
@@ -2683,49 +2727,49 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2683
2727
|
});
|
|
2684
2728
|
const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
|
|
2685
2729
|
if (totalChanges === 0) {
|
|
2686
|
-
console.log(
|
|
2730
|
+
console.log(pc11.green("\n\u2713 Already in sync. No changes needed."));
|
|
2687
2731
|
return;
|
|
2688
2732
|
}
|
|
2689
|
-
console.log(
|
|
2733
|
+
console.log(pc11.blue("\n\u{1F4CB} Sync Preview\n"));
|
|
2690
2734
|
if (preview.toCreate.length > 0) {
|
|
2691
|
-
console.log(
|
|
2692
|
-
preview.toCreate.slice(0, 5).forEach((key) => console.log(
|
|
2735
|
+
console.log(pc11.green(` + ${preview.toCreate.length} to create`));
|
|
2736
|
+
preview.toCreate.slice(0, 5).forEach((key) => console.log(pc11.gray(` ${key}`)));
|
|
2693
2737
|
if (preview.toCreate.length > 5) {
|
|
2694
|
-
console.log(
|
|
2738
|
+
console.log(pc11.gray(` ... and ${preview.toCreate.length - 5} more`));
|
|
2695
2739
|
}
|
|
2696
2740
|
}
|
|
2697
2741
|
if (preview.toUpdate.length > 0) {
|
|
2698
|
-
console.log(
|
|
2699
|
-
preview.toUpdate.slice(0, 5).forEach((key) => console.log(
|
|
2742
|
+
console.log(pc11.yellow(` ~ ${preview.toUpdate.length} to update`));
|
|
2743
|
+
preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc11.gray(` ${key}`)));
|
|
2700
2744
|
if (preview.toUpdate.length > 5) {
|
|
2701
|
-
console.log(
|
|
2745
|
+
console.log(pc11.gray(` ... and ${preview.toUpdate.length - 5} more`));
|
|
2702
2746
|
}
|
|
2703
2747
|
}
|
|
2704
2748
|
if (preview.toDelete.length > 0) {
|
|
2705
|
-
console.log(
|
|
2706
|
-
preview.toDelete.slice(0, 5).forEach((key) => console.log(
|
|
2749
|
+
console.log(pc11.red(` - ${preview.toDelete.length} to delete`));
|
|
2750
|
+
preview.toDelete.slice(0, 5).forEach((key) => console.log(pc11.gray(` ${key}`)));
|
|
2707
2751
|
if (preview.toDelete.length > 5) {
|
|
2708
|
-
console.log(
|
|
2752
|
+
console.log(pc11.gray(` ... and ${preview.toDelete.length - 5} more`));
|
|
2709
2753
|
}
|
|
2710
2754
|
}
|
|
2711
2755
|
if (preview.toSkip.length > 0) {
|
|
2712
|
-
console.log(
|
|
2756
|
+
console.log(pc11.gray(` \u25CB ${preview.toSkip.length} unchanged`));
|
|
2713
2757
|
}
|
|
2714
2758
|
console.log("");
|
|
2715
2759
|
if (!skipConfirm) {
|
|
2716
2760
|
const target = direction === "push" ? providerName : "Keyway";
|
|
2717
|
-
const { confirm } = await
|
|
2761
|
+
const { confirm } = await prompts9({
|
|
2718
2762
|
type: "confirm",
|
|
2719
2763
|
name: "confirm",
|
|
2720
2764
|
message: `Apply ${totalChanges} changes to ${target}?`,
|
|
2721
2765
|
initial: true
|
|
2722
2766
|
});
|
|
2723
2767
|
if (!confirm) {
|
|
2724
|
-
console.log(
|
|
2768
|
+
console.log(pc11.gray("Cancelled."));
|
|
2725
2769
|
return;
|
|
2726
2770
|
}
|
|
2727
2771
|
}
|
|
2728
|
-
console.log(
|
|
2772
|
+
console.log(pc11.blue("\n\u23F3 Syncing...\n"));
|
|
2729
2773
|
const result = await executeSync(accessToken, repoFullName, {
|
|
2730
2774
|
connectionId,
|
|
2731
2775
|
projectId: project.id,
|
|
@@ -2737,11 +2781,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2737
2781
|
allowDelete
|
|
2738
2782
|
});
|
|
2739
2783
|
if (result.success) {
|
|
2740
|
-
console.log(
|
|
2741
|
-
console.log(
|
|
2742
|
-
console.log(
|
|
2784
|
+
console.log(pc11.green("\u2713 Sync complete"));
|
|
2785
|
+
console.log(pc11.gray(` Created: ${result.stats.created}`));
|
|
2786
|
+
console.log(pc11.gray(` Updated: ${result.stats.updated}`));
|
|
2743
2787
|
if (result.stats.deleted > 0) {
|
|
2744
|
-
console.log(
|
|
2788
|
+
console.log(pc11.gray(` Deleted: ${result.stats.deleted}`));
|
|
2745
2789
|
}
|
|
2746
2790
|
trackEvent(AnalyticsEvents.CLI_SYNC, {
|
|
2747
2791
|
provider,
|
|
@@ -2751,7 +2795,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2751
2795
|
deleted: result.stats.deleted
|
|
2752
2796
|
});
|
|
2753
2797
|
} else {
|
|
2754
|
-
console.error(
|
|
2798
|
+
console.error(pc11.red(`
|
|
2755
2799
|
\u2717 ${result.error}`));
|
|
2756
2800
|
process.exit(1);
|
|
2757
2801
|
}
|
|
@@ -2759,16 +2803,16 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2759
2803
|
|
|
2760
2804
|
// src/cli.ts
|
|
2761
2805
|
process.on("unhandledRejection", (reason) => {
|
|
2762
|
-
console.error(
|
|
2806
|
+
console.error(pc12.red("Unhandled error:"), reason);
|
|
2763
2807
|
process.exit(1);
|
|
2764
2808
|
});
|
|
2765
2809
|
var program = new Command();
|
|
2766
2810
|
var TAGLINE = "Sync secrets with your team and infra";
|
|
2767
2811
|
var showBanner = () => {
|
|
2768
|
-
const text =
|
|
2812
|
+
const text = pc12.bold(pc12.cyan("Keyway CLI"));
|
|
2769
2813
|
console.log(`
|
|
2770
2814
|
${text}
|
|
2771
|
-
${
|
|
2815
|
+
${pc12.gray(TAGLINE)}
|
|
2772
2816
|
`);
|
|
2773
2817
|
};
|
|
2774
2818
|
showBanner();
|
|
@@ -2807,6 +2851,6 @@ program.command("sync <provider>").description("Sync secrets with a provider (e.
|
|
|
2807
2851
|
await warnIfEnvNotGitignored();
|
|
2808
2852
|
await program.parseAsync();
|
|
2809
2853
|
})().catch((error) => {
|
|
2810
|
-
console.error(
|
|
2854
|
+
console.error(pc12.red("Error:"), error.message || error);
|
|
2811
2855
|
process.exit(1);
|
|
2812
2856
|
});
|