@keywaysh/cli 0.1.13 → 0.1.15
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 +522 -276
- package/package.json +4 -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 pc13 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.15",
|
|
144
144
|
description: "One link to all your secrets",
|
|
145
145
|
type: "module",
|
|
146
146
|
bin: {
|
|
@@ -185,9 +185,11 @@ var package_default = {
|
|
|
185
185
|
node: ">=18.0.0"
|
|
186
186
|
},
|
|
187
187
|
dependencies: {
|
|
188
|
+
"@octokit/rest": "^22.0.1",
|
|
188
189
|
"balanced-match": "^3.0.1",
|
|
189
190
|
commander: "^14.0.0",
|
|
190
191
|
conf: "^15.0.2",
|
|
192
|
+
"libsodium-wrappers": "^0.7.15",
|
|
191
193
|
open: "^11.0.0",
|
|
192
194
|
picocolors: "^1.1.1",
|
|
193
195
|
"posthog-node": "^3.5.0",
|
|
@@ -195,6 +197,7 @@ var package_default = {
|
|
|
195
197
|
},
|
|
196
198
|
devDependencies: {
|
|
197
199
|
"@types/balanced-match": "^3.0.2",
|
|
200
|
+
"@types/libsodium-wrappers": "^0.7.14",
|
|
198
201
|
"@types/node": "^24.2.0",
|
|
199
202
|
"@types/prompts": "^2.4.9",
|
|
200
203
|
"@vitest/coverage-v8": "^3.0.0",
|
|
@@ -453,8 +456,8 @@ function getProviderAuthUrl(provider, accessToken, redirectUri) {
|
|
|
453
456
|
if (redirectUri) params.set("redirect_uri", redirectUri);
|
|
454
457
|
return `${API_BASE_URL}/v1/integrations/${provider}/authorize?${params}`;
|
|
455
458
|
}
|
|
456
|
-
async function
|
|
457
|
-
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/
|
|
459
|
+
async function getAllProviderProjects(accessToken, provider) {
|
|
460
|
+
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/providers/${provider}/all-projects`, {
|
|
458
461
|
method: "GET",
|
|
459
462
|
headers: {
|
|
460
463
|
"User-Agent": USER_AGENT,
|
|
@@ -900,10 +903,10 @@ async function addBadgeToReadme(silent = false) {
|
|
|
900
903
|
}
|
|
901
904
|
|
|
902
905
|
// src/cmds/push.ts
|
|
903
|
-
import
|
|
904
|
-
import
|
|
905
|
-
import
|
|
906
|
-
import
|
|
906
|
+
import pc6 from "picocolors";
|
|
907
|
+
import fs5 from "fs";
|
|
908
|
+
import path5 from "path";
|
|
909
|
+
import prompts5 from "prompts";
|
|
907
910
|
|
|
908
911
|
// src/cmds/login.ts
|
|
909
912
|
import pc4 from "picocolors";
|
|
@@ -1112,9 +1115,34 @@ async function logoutCommand() {
|
|
|
1112
1115
|
console.log(pc4.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
1113
1116
|
}
|
|
1114
1117
|
|
|
1118
|
+
// src/utils/env.ts
|
|
1119
|
+
import fs4 from "fs";
|
|
1120
|
+
import path4 from "path";
|
|
1121
|
+
import pc5 from "picocolors";
|
|
1122
|
+
import prompts4 from "prompts";
|
|
1123
|
+
async function promptCreateEnvFile() {
|
|
1124
|
+
const { createEnv } = await prompts4({
|
|
1125
|
+
type: "confirm",
|
|
1126
|
+
name: "createEnv",
|
|
1127
|
+
message: "No .env file found. Create one?",
|
|
1128
|
+
initial: true
|
|
1129
|
+
}, {
|
|
1130
|
+
onCancel: () => {
|
|
1131
|
+
throw new Error("Cancelled by user.");
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
if (!createEnv) {
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
1137
|
+
const envFilePath = path4.join(process.cwd(), ".env");
|
|
1138
|
+
fs4.writeFileSync(envFilePath, "# Add your environment variables here\n# Example: API_KEY=your-api-key\n");
|
|
1139
|
+
console.log(pc5.green("\u2713 Created .env file"));
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1115
1143
|
// src/cmds/push.ts
|
|
1116
1144
|
function deriveEnvFromFile(file) {
|
|
1117
|
-
const base =
|
|
1145
|
+
const base = path5.basename(file);
|
|
1118
1146
|
const match = base.match(/\.env(?:\.(.+))?$/);
|
|
1119
1147
|
if (match) {
|
|
1120
1148
|
return match[1] || "development";
|
|
@@ -1123,15 +1151,15 @@ function deriveEnvFromFile(file) {
|
|
|
1123
1151
|
}
|
|
1124
1152
|
function discoverEnvCandidates(cwd) {
|
|
1125
1153
|
try {
|
|
1126
|
-
const entries =
|
|
1154
|
+
const entries = fs5.readdirSync(cwd);
|
|
1127
1155
|
const hasEnvLocal = entries.includes(".env.local");
|
|
1128
1156
|
if (hasEnvLocal) {
|
|
1129
|
-
console.log(
|
|
1157
|
+
console.log(pc6.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
|
|
1130
1158
|
}
|
|
1131
1159
|
const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
|
|
1132
|
-
const fullPath =
|
|
1160
|
+
const fullPath = path5.join(cwd, name);
|
|
1133
1161
|
try {
|
|
1134
|
-
const stat =
|
|
1162
|
+
const stat = fs5.statSync(fullPath);
|
|
1135
1163
|
if (!stat.isFile()) return null;
|
|
1136
1164
|
return { file: name, env: deriveEnvFromFile(name) };
|
|
1137
1165
|
} catch {
|
|
@@ -1152,13 +1180,21 @@ function discoverEnvCandidates(cwd) {
|
|
|
1152
1180
|
}
|
|
1153
1181
|
async function pushCommand(options) {
|
|
1154
1182
|
try {
|
|
1155
|
-
console.log(
|
|
1183
|
+
console.log(pc6.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
1156
1184
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1157
1185
|
let environment = options.env;
|
|
1158
1186
|
let envFile = options.file;
|
|
1159
1187
|
const candidates = discoverEnvCandidates(process.cwd());
|
|
1160
1188
|
if (candidates.length === 0 && !envFile) {
|
|
1161
|
-
|
|
1189
|
+
if (!isInteractive2) {
|
|
1190
|
+
throw new Error("No .env file found. Create a .env file first, or use --file <path> to specify one.");
|
|
1191
|
+
}
|
|
1192
|
+
const created = await promptCreateEnvFile();
|
|
1193
|
+
if (!created) {
|
|
1194
|
+
throw new Error("No .env file found.");
|
|
1195
|
+
}
|
|
1196
|
+
console.log(pc6.gray(" Add your variables and run keyway push again\n"));
|
|
1197
|
+
return;
|
|
1162
1198
|
}
|
|
1163
1199
|
if (environment && !envFile) {
|
|
1164
1200
|
const match = candidates.find((c) => c.env === environment);
|
|
@@ -1167,7 +1203,7 @@ async function pushCommand(options) {
|
|
|
1167
1203
|
}
|
|
1168
1204
|
}
|
|
1169
1205
|
if (!environment && !envFile && isInteractive2 && candidates.length > 0) {
|
|
1170
|
-
const { choice } = await
|
|
1206
|
+
const { choice } = await prompts5(
|
|
1171
1207
|
{
|
|
1172
1208
|
type: "select",
|
|
1173
1209
|
name: "choice",
|
|
@@ -1190,15 +1226,15 @@ async function pushCommand(options) {
|
|
|
1190
1226
|
envFile = choice.file;
|
|
1191
1227
|
environment = choice.env;
|
|
1192
1228
|
} else if (choice === "custom") {
|
|
1193
|
-
const { fileInput } = await
|
|
1229
|
+
const { fileInput } = await prompts5(
|
|
1194
1230
|
{
|
|
1195
1231
|
type: "text",
|
|
1196
1232
|
name: "fileInput",
|
|
1197
1233
|
message: "Path to env file:",
|
|
1198
1234
|
validate: (value) => {
|
|
1199
1235
|
if (!value) return "Path is required";
|
|
1200
|
-
const resolved =
|
|
1201
|
-
if (!
|
|
1236
|
+
const resolved = path5.resolve(process.cwd(), value);
|
|
1237
|
+
if (!fs5.existsSync(resolved)) return `File not found: ${value}`;
|
|
1202
1238
|
return true;
|
|
1203
1239
|
}
|
|
1204
1240
|
},
|
|
@@ -1218,11 +1254,11 @@ async function pushCommand(options) {
|
|
|
1218
1254
|
if (!envFile) {
|
|
1219
1255
|
envFile = ".env";
|
|
1220
1256
|
}
|
|
1221
|
-
const envFilePath =
|
|
1222
|
-
if (!
|
|
1257
|
+
const envFilePath = path5.resolve(process.cwd(), envFile);
|
|
1258
|
+
if (!fs5.existsSync(envFilePath)) {
|
|
1223
1259
|
throw new Error(`File not found: ${envFile}`);
|
|
1224
1260
|
}
|
|
1225
|
-
const content =
|
|
1261
|
+
const content = fs5.readFileSync(envFilePath, "utf-8");
|
|
1226
1262
|
if (content.trim().length === 0) {
|
|
1227
1263
|
throw new Error(`File is empty: ${envFile}`);
|
|
1228
1264
|
}
|
|
@@ -1230,17 +1266,17 @@ async function pushCommand(options) {
|
|
|
1230
1266
|
const trimmed = line.trim();
|
|
1231
1267
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1232
1268
|
});
|
|
1233
|
-
console.log(`File: ${
|
|
1234
|
-
console.log(`Environment: ${
|
|
1235
|
-
console.log(`Variables: ${
|
|
1269
|
+
console.log(`File: ${pc6.cyan(envFile)}`);
|
|
1270
|
+
console.log(`Environment: ${pc6.cyan(environment)}`);
|
|
1271
|
+
console.log(`Variables: ${pc6.cyan(lines.length.toString())}`);
|
|
1236
1272
|
const repoFullName = getCurrentRepoFullName();
|
|
1237
|
-
console.log(`Repository: ${
|
|
1273
|
+
console.log(`Repository: ${pc6.cyan(repoFullName)}`);
|
|
1238
1274
|
if (!options.yes) {
|
|
1239
1275
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1240
1276
|
if (!isInteractive3) {
|
|
1241
1277
|
throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
1242
1278
|
}
|
|
1243
|
-
const { confirm } = await
|
|
1279
|
+
const { confirm } = await prompts5(
|
|
1244
1280
|
{
|
|
1245
1281
|
type: "confirm",
|
|
1246
1282
|
name: "confirm",
|
|
@@ -1254,7 +1290,7 @@ async function pushCommand(options) {
|
|
|
1254
1290
|
}
|
|
1255
1291
|
);
|
|
1256
1292
|
if (!confirm) {
|
|
1257
|
-
console.log(
|
|
1293
|
+
console.log(pc6.yellow("Push aborted."));
|
|
1258
1294
|
return;
|
|
1259
1295
|
}
|
|
1260
1296
|
}
|
|
@@ -1266,13 +1302,13 @@ async function pushCommand(options) {
|
|
|
1266
1302
|
});
|
|
1267
1303
|
console.log("\nUploading secrets...");
|
|
1268
1304
|
const response = await pushSecrets(repoFullName, environment, content, accessToken);
|
|
1269
|
-
console.log(
|
|
1305
|
+
console.log(pc6.green("\n\u2713 " + response.message));
|
|
1270
1306
|
if (response.stats) {
|
|
1271
1307
|
const { created, updated, deleted } = response.stats;
|
|
1272
1308
|
const parts = [];
|
|
1273
|
-
if (created > 0) parts.push(
|
|
1274
|
-
if (updated > 0) parts.push(
|
|
1275
|
-
if (deleted > 0) parts.push(
|
|
1309
|
+
if (created > 0) parts.push(pc6.green(`+${created} created`));
|
|
1310
|
+
if (updated > 0) parts.push(pc6.yellow(`~${updated} updated`));
|
|
1311
|
+
if (deleted > 0) parts.push(pc6.red(`-${deleted} deleted`));
|
|
1276
1312
|
if (parts.length > 0) {
|
|
1277
1313
|
console.log(`Stats: ${parts.join(", ")}`);
|
|
1278
1314
|
}
|
|
@@ -1281,7 +1317,7 @@ async function pushCommand(options) {
|
|
|
1281
1317
|
Your secrets are now encrypted and stored securely.`);
|
|
1282
1318
|
const dashboardLink = `https://www.keyway.sh/dashboard/vaults/${repoFullName}`;
|
|
1283
1319
|
console.log(`
|
|
1284
|
-
${
|
|
1320
|
+
${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
|
|
1285
1321
|
await shutdownAnalytics();
|
|
1286
1322
|
} catch (error) {
|
|
1287
1323
|
let message;
|
|
@@ -1294,7 +1330,7 @@ ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
|
|
|
1294
1330
|
const availableEnvs = envNotFoundMatch[2];
|
|
1295
1331
|
message = `Environment '${requestedEnv}' does not exist in this vault.`;
|
|
1296
1332
|
hint = `Available environments: ${availableEnvs}
|
|
1297
|
-
Use ${
|
|
1333
|
+
Use ${pc6.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1298
1334
|
}
|
|
1299
1335
|
if (error.statusCode === 403 && (error.upgradeUrl || message.toLowerCase().includes("read-only"))) {
|
|
1300
1336
|
const upgradeMessage = message.toLowerCase().includes("read-only") ? "This vault is read-only on your current plan." : message;
|
|
@@ -1317,10 +1353,10 @@ Use ${pc5.cyan(`keyway push --env <environment>`)} to specify one, or create '${
|
|
|
1317
1353
|
error: message
|
|
1318
1354
|
});
|
|
1319
1355
|
await shutdownAnalytics();
|
|
1320
|
-
console.error(
|
|
1356
|
+
console.error(pc6.red(`
|
|
1321
1357
|
\u2717 ${message}`));
|
|
1322
1358
|
if (hint) {
|
|
1323
|
-
console.error(
|
|
1359
|
+
console.error(pc6.gray(`
|
|
1324
1360
|
${hint}`));
|
|
1325
1361
|
}
|
|
1326
1362
|
process.exit(1);
|
|
@@ -1355,7 +1391,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1355
1391
|
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1356
1392
|
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1357
1393
|
console.log("");
|
|
1358
|
-
const { shouldProceed } = await
|
|
1394
|
+
const { shouldProceed } = await prompts6({
|
|
1359
1395
|
type: "confirm",
|
|
1360
1396
|
name: "shouldProceed",
|
|
1361
1397
|
message: "Open browser to sign in?",
|
|
@@ -1365,8 +1401,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1365
1401
|
throw new Error('Setup required. Run "keyway init" when ready.');
|
|
1366
1402
|
}
|
|
1367
1403
|
await openUrl(deviceStart.verificationUriComplete);
|
|
1368
|
-
console.log(
|
|
1369
|
-
console.log(
|
|
1404
|
+
console.log(pc7.blue("\u23F3 Waiting for authorization..."));
|
|
1405
|
+
console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1370
1406
|
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1371
1407
|
const startTime = Date.now();
|
|
1372
1408
|
let accessToken = null;
|
|
@@ -1381,7 +1417,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1381
1417
|
githubLogin: result.githubLogin,
|
|
1382
1418
|
expiresAt: result.expiresAt
|
|
1383
1419
|
});
|
|
1384
|
-
console.log(
|
|
1420
|
+
console.log(pc7.green("\u2713 Signed in!"));
|
|
1385
1421
|
if (result.githubLogin) {
|
|
1386
1422
|
identifyUser(result.githubLogin, {
|
|
1387
1423
|
github_username: result.githubLogin,
|
|
@@ -1391,7 +1427,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1391
1427
|
break;
|
|
1392
1428
|
}
|
|
1393
1429
|
consecutiveErrors = 0;
|
|
1394
|
-
process.stdout.write(
|
|
1430
|
+
process.stdout.write(pc7.gray("."));
|
|
1395
1431
|
} catch (error) {
|
|
1396
1432
|
consecutiveErrors++;
|
|
1397
1433
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1402,35 +1438,35 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1402
1438
|
}
|
|
1403
1439
|
if (!accessToken) {
|
|
1404
1440
|
console.log("");
|
|
1405
|
-
console.log(
|
|
1441
|
+
console.log(pc7.yellow("\u26A0 Timed out waiting for sign in."));
|
|
1406
1442
|
throw new Error("Sign in timed out. Please try again.");
|
|
1407
1443
|
}
|
|
1408
1444
|
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1409
1445
|
if (installStatus.installed) {
|
|
1410
|
-
console.log(
|
|
1446
|
+
console.log(pc7.green("\u2713 GitHub App installed"));
|
|
1411
1447
|
console.log("");
|
|
1412
1448
|
return accessToken;
|
|
1413
1449
|
}
|
|
1414
1450
|
console.log("");
|
|
1415
|
-
console.log(
|
|
1416
|
-
console.log(
|
|
1451
|
+
console.log(pc7.yellow("\u26A0 GitHub App not installed on this repository"));
|
|
1452
|
+
console.log(pc7.gray(" The Keyway GitHub App is required for secure access."));
|
|
1417
1453
|
console.log("");
|
|
1418
|
-
const { shouldInstall } = await
|
|
1454
|
+
const { shouldInstall } = await prompts6({
|
|
1419
1455
|
type: "confirm",
|
|
1420
1456
|
name: "shouldInstall",
|
|
1421
1457
|
message: "Open browser to install GitHub App?",
|
|
1422
1458
|
initial: true
|
|
1423
1459
|
});
|
|
1424
1460
|
if (!shouldInstall) {
|
|
1425
|
-
console.log(
|
|
1461
|
+
console.log(pc7.gray(`
|
|
1426
1462
|
Install later: ${installUrl}`));
|
|
1427
1463
|
throw new Error("GitHub App installation required.");
|
|
1428
1464
|
}
|
|
1429
1465
|
await openUrl(installUrl);
|
|
1430
|
-
console.log(
|
|
1431
|
-
console.log(
|
|
1432
|
-
console.log(
|
|
1433
|
-
console.log(
|
|
1466
|
+
console.log(pc7.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1467
|
+
console.log(pc7.gray(' Add this repository and click "Install"'));
|
|
1468
|
+
console.log(pc7.gray(" Then return here - the CLI will detect it automatically"));
|
|
1469
|
+
console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1434
1470
|
const installStartTime = Date.now();
|
|
1435
1471
|
consecutiveErrors = 0;
|
|
1436
1472
|
while (Date.now() - installStartTime < POLL_TIMEOUT_MS) {
|
|
@@ -1438,12 +1474,12 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1438
1474
|
try {
|
|
1439
1475
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1440
1476
|
if (pollStatus.installed) {
|
|
1441
|
-
console.log(
|
|
1477
|
+
console.log(pc7.green("\u2713 GitHub App installed!"));
|
|
1442
1478
|
console.log("");
|
|
1443
1479
|
return accessToken;
|
|
1444
1480
|
}
|
|
1445
1481
|
consecutiveErrors = 0;
|
|
1446
|
-
process.stdout.write(
|
|
1482
|
+
process.stdout.write(pc7.gray("."));
|
|
1447
1483
|
} catch (error) {
|
|
1448
1484
|
consecutiveErrors++;
|
|
1449
1485
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1453,8 +1489,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1453
1489
|
}
|
|
1454
1490
|
}
|
|
1455
1491
|
console.log("");
|
|
1456
|
-
console.log(
|
|
1457
|
-
console.log(
|
|
1492
|
+
console.log(pc7.yellow("\u26A0 Timed out waiting for installation."));
|
|
1493
|
+
console.log(pc7.gray(` Install the GitHub App: ${installUrl}`));
|
|
1458
1494
|
throw new Error("GitHub App installation timed out.");
|
|
1459
1495
|
}
|
|
1460
1496
|
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
@@ -1464,7 +1500,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1464
1500
|
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1465
1501
|
} catch (error) {
|
|
1466
1502
|
if (error instanceof APIError && error.statusCode === 401) {
|
|
1467
|
-
console.log(
|
|
1503
|
+
console.log(pc7.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1468
1504
|
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1469
1505
|
clearAuth2();
|
|
1470
1506
|
return null;
|
|
@@ -1475,29 +1511,29 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1475
1511
|
return accessToken;
|
|
1476
1512
|
}
|
|
1477
1513
|
console.log("");
|
|
1478
|
-
console.log(
|
|
1514
|
+
console.log(pc7.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1479
1515
|
console.log("");
|
|
1480
|
-
console.log(
|
|
1481
|
-
console.log(
|
|
1516
|
+
console.log(pc7.gray(" The Keyway GitHub App is required to securely manage secrets."));
|
|
1517
|
+
console.log(pc7.gray(" It only requests minimal permissions (repository metadata)."));
|
|
1482
1518
|
console.log("");
|
|
1483
1519
|
if (!isInteractive()) {
|
|
1484
|
-
console.log(
|
|
1520
|
+
console.log(pc7.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1485
1521
|
throw new Error("GitHub App installation required.");
|
|
1486
1522
|
}
|
|
1487
|
-
const { shouldInstall } = await
|
|
1523
|
+
const { shouldInstall } = await prompts6({
|
|
1488
1524
|
type: "confirm",
|
|
1489
1525
|
name: "shouldInstall",
|
|
1490
1526
|
message: "Open browser to install Keyway GitHub App?",
|
|
1491
1527
|
initial: true
|
|
1492
1528
|
});
|
|
1493
1529
|
if (!shouldInstall) {
|
|
1494
|
-
console.log(
|
|
1530
|
+
console.log(pc7.gray(`
|
|
1495
1531
|
You can install later: ${status.installUrl}`));
|
|
1496
1532
|
throw new Error("GitHub App installation required.");
|
|
1497
1533
|
}
|
|
1498
1534
|
await openUrl(status.installUrl);
|
|
1499
|
-
console.log(
|
|
1500
|
-
console.log(
|
|
1535
|
+
console.log(pc7.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1536
|
+
console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1501
1537
|
const startTime = Date.now();
|
|
1502
1538
|
let consecutiveErrors = 0;
|
|
1503
1539
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
@@ -1505,12 +1541,12 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1505
1541
|
try {
|
|
1506
1542
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1507
1543
|
if (pollStatus.installed) {
|
|
1508
|
-
console.log(
|
|
1544
|
+
console.log(pc7.green("\u2713 GitHub App installed!"));
|
|
1509
1545
|
console.log("");
|
|
1510
1546
|
return accessToken;
|
|
1511
1547
|
}
|
|
1512
1548
|
consecutiveErrors = 0;
|
|
1513
|
-
process.stdout.write(
|
|
1549
|
+
process.stdout.write(pc7.gray("."));
|
|
1514
1550
|
} catch (error) {
|
|
1515
1551
|
consecutiveErrors++;
|
|
1516
1552
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1520,35 +1556,35 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1520
1556
|
}
|
|
1521
1557
|
}
|
|
1522
1558
|
console.log("");
|
|
1523
|
-
console.log(
|
|
1524
|
-
console.log(
|
|
1559
|
+
console.log(pc7.yellow("\u26A0 Timed out waiting for installation."));
|
|
1560
|
+
console.log(pc7.gray(` You can install the GitHub App later: ${status.installUrl}`));
|
|
1525
1561
|
throw new Error("GitHub App installation timed out.");
|
|
1526
1562
|
}
|
|
1527
1563
|
async function initCommand(options = {}) {
|
|
1528
1564
|
try {
|
|
1529
1565
|
const repoFullName = getCurrentRepoFullName();
|
|
1530
1566
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1531
|
-
console.log(
|
|
1532
|
-
console.log(` ${
|
|
1567
|
+
console.log(pc7.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1568
|
+
console.log(` ${pc7.gray("Repository:")} ${pc7.white(repoFullName)}`);
|
|
1533
1569
|
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1534
1570
|
allowPrompt: options.loginPrompt !== false
|
|
1535
1571
|
});
|
|
1536
1572
|
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1537
1573
|
const vaultExists = await checkVaultExists(accessToken, repoFullName);
|
|
1538
1574
|
if (vaultExists) {
|
|
1539
|
-
console.log(
|
|
1540
|
-
console.log(` ${
|
|
1541
|
-
console.log(` ${
|
|
1575
|
+
console.log(pc7.green("\n\u2713 Already initialized!\n"));
|
|
1576
|
+
console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets`);
|
|
1577
|
+
console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(dashboardLink)}`);
|
|
1542
1578
|
console.log("");
|
|
1543
1579
|
await shutdownAnalytics();
|
|
1544
1580
|
return;
|
|
1545
1581
|
}
|
|
1546
1582
|
await initVault(repoFullName, accessToken);
|
|
1547
|
-
console.log(
|
|
1583
|
+
console.log(pc7.green("\u2713 Vault created!"));
|
|
1548
1584
|
try {
|
|
1549
1585
|
const badgeAdded = await addBadgeToReadme(true);
|
|
1550
1586
|
if (badgeAdded) {
|
|
1551
|
-
console.log(
|
|
1587
|
+
console.log(pc7.green("\u2713 Badge added to README.md"));
|
|
1552
1588
|
}
|
|
1553
1589
|
} catch {
|
|
1554
1590
|
}
|
|
@@ -1556,9 +1592,9 @@ async function initCommand(options = {}) {
|
|
|
1556
1592
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1557
1593
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1558
1594
|
if (envCandidates.length > 0 && isInteractive2) {
|
|
1559
|
-
console.log(
|
|
1595
|
+
console.log(pc7.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1560
1596
|
`));
|
|
1561
|
-
const { shouldPush } = await
|
|
1597
|
+
const { shouldPush } = await prompts6({
|
|
1562
1598
|
type: "confirm",
|
|
1563
1599
|
name: "shouldPush",
|
|
1564
1600
|
message: "Push secrets now?",
|
|
@@ -1570,25 +1606,36 @@ async function initCommand(options = {}) {
|
|
|
1570
1606
|
return;
|
|
1571
1607
|
}
|
|
1572
1608
|
}
|
|
1573
|
-
console.log(
|
|
1609
|
+
console.log(pc7.dim("\u2500".repeat(50)));
|
|
1574
1610
|
console.log("");
|
|
1575
1611
|
if (envCandidates.length === 0) {
|
|
1576
|
-
|
|
1577
|
-
|
|
1612
|
+
if (isInteractive2) {
|
|
1613
|
+
const created = await promptCreateEnvFile();
|
|
1614
|
+
if (created) {
|
|
1615
|
+
console.log(` Add your variables and run ${pc7.cyan("keyway push")}
|
|
1616
|
+
`);
|
|
1617
|
+
} else {
|
|
1618
|
+
console.log(` Next: Create ${pc7.cyan(".env")} and run ${pc7.cyan("keyway push")}
|
|
1619
|
+
`);
|
|
1620
|
+
}
|
|
1621
|
+
} else {
|
|
1622
|
+
console.log(`${pc7.yellow("\u26A0")} No .env file found - your vault is empty`);
|
|
1623
|
+
console.log(` Next: Create ${pc7.cyan(".env")} and run ${pc7.cyan("keyway push")}
|
|
1578
1624
|
`);
|
|
1625
|
+
}
|
|
1579
1626
|
} else {
|
|
1580
|
-
console.log(` ${
|
|
1627
|
+
console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets
|
|
1581
1628
|
`);
|
|
1582
1629
|
}
|
|
1583
|
-
console.log(` ${
|
|
1630
|
+
console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(dashboardLink)}`);
|
|
1584
1631
|
console.log("");
|
|
1585
1632
|
await shutdownAnalytics();
|
|
1586
1633
|
} catch (error) {
|
|
1587
1634
|
if (error instanceof APIError) {
|
|
1588
1635
|
if (error.statusCode === 409) {
|
|
1589
|
-
console.log(
|
|
1590
|
-
console.log(` ${
|
|
1591
|
-
console.log(` ${
|
|
1636
|
+
console.log(pc7.green("\n\u2713 Already initialized!\n"));
|
|
1637
|
+
console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets`);
|
|
1638
|
+
console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
|
|
1592
1639
|
console.log("");
|
|
1593
1640
|
await shutdownAnalytics();
|
|
1594
1641
|
return;
|
|
@@ -1606,25 +1653,25 @@ async function initCommand(options = {}) {
|
|
|
1606
1653
|
error: message
|
|
1607
1654
|
});
|
|
1608
1655
|
await shutdownAnalytics();
|
|
1609
|
-
console.error(
|
|
1656
|
+
console.error(pc7.red(`
|
|
1610
1657
|
\u2717 ${message}`));
|
|
1611
1658
|
process.exit(1);
|
|
1612
1659
|
}
|
|
1613
1660
|
}
|
|
1614
1661
|
|
|
1615
1662
|
// src/cmds/pull.ts
|
|
1616
|
-
import
|
|
1617
|
-
import
|
|
1618
|
-
import
|
|
1619
|
-
import
|
|
1663
|
+
import pc8 from "picocolors";
|
|
1664
|
+
import fs6 from "fs";
|
|
1665
|
+
import path6 from "path";
|
|
1666
|
+
import prompts7 from "prompts";
|
|
1620
1667
|
async function pullCommand(options) {
|
|
1621
1668
|
try {
|
|
1622
1669
|
const environment = options.env || "development";
|
|
1623
1670
|
const envFile = options.file || ".env";
|
|
1624
|
-
console.log(
|
|
1625
|
-
console.log(`Environment: ${
|
|
1671
|
+
console.log(pc8.blue("\u{1F510} Pulling secrets from Keyway...\n"));
|
|
1672
|
+
console.log(`Environment: ${pc8.cyan(environment)}`);
|
|
1626
1673
|
const repoFullName = getCurrentRepoFullName();
|
|
1627
|
-
console.log(`Repository: ${
|
|
1674
|
+
console.log(`Repository: ${pc8.cyan(repoFullName)}`);
|
|
1628
1675
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1629
1676
|
trackEvent(AnalyticsEvents.CLI_PULL, {
|
|
1630
1677
|
repoFullName,
|
|
@@ -1632,16 +1679,16 @@ async function pullCommand(options) {
|
|
|
1632
1679
|
});
|
|
1633
1680
|
console.log("\nDownloading secrets...");
|
|
1634
1681
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1635
|
-
const envFilePath =
|
|
1636
|
-
if (
|
|
1682
|
+
const envFilePath = path6.resolve(process.cwd(), envFile);
|
|
1683
|
+
if (fs6.existsSync(envFilePath)) {
|
|
1637
1684
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1638
1685
|
if (options.yes) {
|
|
1639
|
-
console.log(
|
|
1686
|
+
console.log(pc8.yellow(`
|
|
1640
1687
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1641
1688
|
} else if (!isInteractive2) {
|
|
1642
1689
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
1643
1690
|
} else {
|
|
1644
|
-
const { confirm } = await
|
|
1691
|
+
const { confirm } = await prompts7(
|
|
1645
1692
|
{
|
|
1646
1693
|
type: "confirm",
|
|
1647
1694
|
name: "confirm",
|
|
@@ -1655,21 +1702,21 @@ async function pullCommand(options) {
|
|
|
1655
1702
|
}
|
|
1656
1703
|
);
|
|
1657
1704
|
if (!confirm) {
|
|
1658
|
-
console.log(
|
|
1705
|
+
console.log(pc8.yellow("Pull aborted."));
|
|
1659
1706
|
return;
|
|
1660
1707
|
}
|
|
1661
1708
|
}
|
|
1662
1709
|
}
|
|
1663
|
-
|
|
1710
|
+
fs6.writeFileSync(envFilePath, response.content, "utf-8");
|
|
1664
1711
|
const lines = response.content.split("\n").filter((line) => {
|
|
1665
1712
|
const trimmed = line.trim();
|
|
1666
1713
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1667
1714
|
});
|
|
1668
|
-
console.log(
|
|
1715
|
+
console.log(pc8.green(`
|
|
1669
1716
|
\u2713 Secrets downloaded successfully`));
|
|
1670
1717
|
console.log(`
|
|
1671
|
-
File: ${
|
|
1672
|
-
console.log(`Variables: ${
|
|
1718
|
+
File: ${pc8.cyan(envFile)}`);
|
|
1719
|
+
console.log(`Variables: ${pc8.cyan(lines.length.toString())}`);
|
|
1673
1720
|
await shutdownAnalytics();
|
|
1674
1721
|
} catch (error) {
|
|
1675
1722
|
const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
|
|
@@ -1678,14 +1725,14 @@ File: ${pc7.cyan(envFile)}`);
|
|
|
1678
1725
|
error: message
|
|
1679
1726
|
});
|
|
1680
1727
|
await shutdownAnalytics();
|
|
1681
|
-
console.error(
|
|
1728
|
+
console.error(pc8.red(`
|
|
1682
1729
|
\u2717 ${message}`));
|
|
1683
1730
|
process.exit(1);
|
|
1684
1731
|
}
|
|
1685
1732
|
}
|
|
1686
1733
|
|
|
1687
1734
|
// src/cmds/doctor.ts
|
|
1688
|
-
import
|
|
1735
|
+
import pc9 from "picocolors";
|
|
1689
1736
|
|
|
1690
1737
|
// src/core/doctor.ts
|
|
1691
1738
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -1939,9 +1986,9 @@ async function runAllChecks(options = {}) {
|
|
|
1939
1986
|
// src/cmds/doctor.ts
|
|
1940
1987
|
function formatSummary(results) {
|
|
1941
1988
|
const parts = [
|
|
1942
|
-
|
|
1943
|
-
results.summary.warn > 0 ?
|
|
1944
|
-
results.summary.fail > 0 ?
|
|
1989
|
+
pc9.green(`${results.summary.pass} passed`),
|
|
1990
|
+
results.summary.warn > 0 ? pc9.yellow(`${results.summary.warn} warnings`) : null,
|
|
1991
|
+
results.summary.fail > 0 ? pc9.red(`${results.summary.fail} failed`) : null
|
|
1945
1992
|
].filter(Boolean);
|
|
1946
1993
|
return parts.join(", ");
|
|
1947
1994
|
}
|
|
@@ -1958,20 +2005,20 @@ async function doctorCommand(options = {}) {
|
|
|
1958
2005
|
process.stdout.write(JSON.stringify(results, null, 0) + "\n");
|
|
1959
2006
|
process.exit(results.exitCode);
|
|
1960
2007
|
}
|
|
1961
|
-
console.log(
|
|
2008
|
+
console.log(pc9.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
|
|
1962
2009
|
results.checks.forEach((check) => {
|
|
1963
|
-
const icon = check.status === "pass" ?
|
|
1964
|
-
const detail = check.detail ?
|
|
2010
|
+
const icon = check.status === "pass" ? pc9.green("\u2713") : check.status === "warn" ? pc9.yellow("!") : pc9.red("\u2717");
|
|
2011
|
+
const detail = check.detail ? pc9.dim(` \u2014 ${check.detail}`) : "";
|
|
1965
2012
|
console.log(` ${icon} ${check.name}${detail}`);
|
|
1966
2013
|
});
|
|
1967
2014
|
console.log(`
|
|
1968
2015
|
Summary: ${formatSummary(results)}`);
|
|
1969
2016
|
if (results.summary.fail > 0) {
|
|
1970
|
-
console.log(
|
|
2017
|
+
console.log(pc9.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
|
|
1971
2018
|
} else if (results.summary.warn > 0) {
|
|
1972
|
-
console.log(
|
|
2019
|
+
console.log(pc9.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
|
|
1973
2020
|
} else {
|
|
1974
|
-
console.log(
|
|
2021
|
+
console.log(pc9.green("\u2728 All checks passed! Your environment is ready for Keyway."));
|
|
1975
2022
|
}
|
|
1976
2023
|
process.exit(results.exitCode);
|
|
1977
2024
|
} catch (error) {
|
|
@@ -1992,16 +2039,146 @@ Summary: ${formatSummary(results)}`);
|
|
|
1992
2039
|
};
|
|
1993
2040
|
process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
|
|
1994
2041
|
} else {
|
|
1995
|
-
console.error(
|
|
2042
|
+
console.error(pc9.red(`
|
|
1996
2043
|
\u2717 ${message}`));
|
|
1997
2044
|
}
|
|
1998
2045
|
process.exit(1);
|
|
1999
2046
|
}
|
|
2000
2047
|
}
|
|
2001
2048
|
|
|
2049
|
+
// src/cmds/ci.ts
|
|
2050
|
+
import { execSync as execSync3 } from "child_process";
|
|
2051
|
+
import { Octokit } from "@octokit/rest";
|
|
2052
|
+
import pc10 from "picocolors";
|
|
2053
|
+
import prompts8 from "prompts";
|
|
2054
|
+
function isGhAvailable() {
|
|
2055
|
+
try {
|
|
2056
|
+
execSync3("gh auth status", { stdio: "ignore" });
|
|
2057
|
+
return true;
|
|
2058
|
+
} catch {
|
|
2059
|
+
return false;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
function addSecretWithGh(repo, secretName, secretValue) {
|
|
2063
|
+
execSync3(`gh secret set ${secretName} --repo ${repo}`, {
|
|
2064
|
+
input: secretValue,
|
|
2065
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
async function ciSetupCommand(options) {
|
|
2069
|
+
const repo = options.repo || detectGitRepo();
|
|
2070
|
+
if (!repo) {
|
|
2071
|
+
console.error(pc10.red("Not in a git repository. Use --repo owner/repo"));
|
|
2072
|
+
process.exit(1);
|
|
2073
|
+
}
|
|
2074
|
+
console.log(pc10.bold(`
|
|
2075
|
+
\u{1F510} Setting up GitHub Actions for ${repo}
|
|
2076
|
+
`));
|
|
2077
|
+
console.log(pc10.dim("Step 1: Keyway Authentication"));
|
|
2078
|
+
let keywayToken;
|
|
2079
|
+
try {
|
|
2080
|
+
keywayToken = await ensureLogin({ allowPrompt: true });
|
|
2081
|
+
console.log(pc10.green(" \u2713 Authenticated with Keyway\n"));
|
|
2082
|
+
} catch {
|
|
2083
|
+
console.error(pc10.red(" \u2717 Failed to authenticate with Keyway"));
|
|
2084
|
+
console.error(pc10.dim(" Run `keyway login` first"));
|
|
2085
|
+
process.exit(1);
|
|
2086
|
+
}
|
|
2087
|
+
const useGh = isGhAvailable();
|
|
2088
|
+
if (useGh) {
|
|
2089
|
+
console.log(pc10.dim("Step 2: Adding secret via GitHub CLI"));
|
|
2090
|
+
try {
|
|
2091
|
+
addSecretWithGh(repo, "KEYWAY_TOKEN", keywayToken);
|
|
2092
|
+
console.log(pc10.green(` \u2713 Secret KEYWAY_TOKEN added to ${repo}
|
|
2093
|
+
`));
|
|
2094
|
+
} catch (error) {
|
|
2095
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2096
|
+
console.error(pc10.red(` \u2717 Failed to add secret: ${message}`));
|
|
2097
|
+
console.error(pc10.dim(" Try running: gh auth login"));
|
|
2098
|
+
process.exit(1);
|
|
2099
|
+
}
|
|
2100
|
+
} else {
|
|
2101
|
+
console.log(pc10.dim("Step 2: Temporary GitHub PAT"));
|
|
2102
|
+
console.log(" gh CLI not found. We need a one-time GitHub PAT.");
|
|
2103
|
+
console.log(pc10.dim(" You can delete it immediately after setup.\n"));
|
|
2104
|
+
const patUrl = "https://github.com/settings/tokens/new?scopes=repo&description=Keyway%20CI%20Setup%20(temporary)";
|
|
2105
|
+
await openUrl(patUrl);
|
|
2106
|
+
const { githubToken } = await prompts8({
|
|
2107
|
+
type: "password",
|
|
2108
|
+
name: "githubToken",
|
|
2109
|
+
message: "Paste your GitHub PAT:"
|
|
2110
|
+
});
|
|
2111
|
+
if (!githubToken) {
|
|
2112
|
+
console.error(pc10.red("\n \u2717 GitHub PAT is required"));
|
|
2113
|
+
process.exit(1);
|
|
2114
|
+
}
|
|
2115
|
+
const octokit = new Octokit({ auth: githubToken });
|
|
2116
|
+
try {
|
|
2117
|
+
await octokit.users.getAuthenticated();
|
|
2118
|
+
console.log(pc10.green(" \u2713 GitHub PAT validated\n"));
|
|
2119
|
+
} catch {
|
|
2120
|
+
console.error(pc10.red(" \u2717 Invalid GitHub PAT"));
|
|
2121
|
+
process.exit(1);
|
|
2122
|
+
}
|
|
2123
|
+
console.log(pc10.dim("Step 3: Adding secret to repository"));
|
|
2124
|
+
const [owner, repoName] = repo.split("/");
|
|
2125
|
+
try {
|
|
2126
|
+
await addRepoSecret(octokit, owner, repoName, "KEYWAY_TOKEN", keywayToken);
|
|
2127
|
+
console.log(pc10.green(` \u2713 Secret KEYWAY_TOKEN added to ${repo}
|
|
2128
|
+
`));
|
|
2129
|
+
} catch (error) {
|
|
2130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2131
|
+
if (message.includes("Not Found")) {
|
|
2132
|
+
console.error(pc10.red(` \u2717 Repository not found or no access: ${repo}`));
|
|
2133
|
+
console.error(pc10.dim(" Make sure the PAT has access to this repository"));
|
|
2134
|
+
} else {
|
|
2135
|
+
console.error(pc10.red(` \u2717 Failed to add secret: ${message}`));
|
|
2136
|
+
}
|
|
2137
|
+
process.exit(1);
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
console.log(pc10.green(pc10.bold("\u2713 Setup complete!\n")));
|
|
2141
|
+
console.log("Add this to your workflow (.github/workflows/*.yml):\n");
|
|
2142
|
+
console.log(
|
|
2143
|
+
pc10.cyan(` - uses: keywaysh/keyway-action@v1
|
|
2144
|
+
with:
|
|
2145
|
+
token: \${{ secrets.KEYWAY_TOKEN }}
|
|
2146
|
+
environment: production`)
|
|
2147
|
+
);
|
|
2148
|
+
console.log();
|
|
2149
|
+
if (!useGh) {
|
|
2150
|
+
console.log(`\u{1F5D1}\uFE0F Delete the temporary PAT: ${pc10.underline("https://github.com/settings/tokens")}`);
|
|
2151
|
+
}
|
|
2152
|
+
console.log(pc10.dim(`\u{1F4D6} Docs: ${pc10.underline("https://docs.keyway.sh/ci")}
|
|
2153
|
+
`));
|
|
2154
|
+
}
|
|
2155
|
+
async function addRepoSecret(octokit, owner, repo, secretName, secretValue) {
|
|
2156
|
+
const { data: publicKey } = await octokit.rest.actions.getRepoPublicKey({
|
|
2157
|
+
owner,
|
|
2158
|
+
repo
|
|
2159
|
+
});
|
|
2160
|
+
const encryptedValue = await encryptSecret(publicKey.key, secretValue);
|
|
2161
|
+
await octokit.rest.actions.createOrUpdateRepoSecret({
|
|
2162
|
+
owner,
|
|
2163
|
+
repo,
|
|
2164
|
+
secret_name: secretName,
|
|
2165
|
+
encrypted_value: encryptedValue,
|
|
2166
|
+
key_id: publicKey.key_id
|
|
2167
|
+
});
|
|
2168
|
+
}
|
|
2169
|
+
async function encryptSecret(publicKey, secret) {
|
|
2170
|
+
const sodiumModule = await import("libsodium-wrappers");
|
|
2171
|
+
const sodium = sodiumModule.default || sodiumModule;
|
|
2172
|
+
await sodium.ready;
|
|
2173
|
+
const binkey = sodium.from_base64(publicKey, sodium.base64_variants.ORIGINAL);
|
|
2174
|
+
const binsec = sodium.from_string(secret);
|
|
2175
|
+
const encBytes = sodium.crypto_box_seal(binsec, binkey);
|
|
2176
|
+
return sodium.to_base64(encBytes, sodium.base64_variants.ORIGINAL);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2002
2179
|
// src/cmds/connect.ts
|
|
2003
|
-
import
|
|
2004
|
-
import
|
|
2180
|
+
import pc11 from "picocolors";
|
|
2181
|
+
import prompts9 from "prompts";
|
|
2005
2182
|
var TOKEN_AUTH_PROVIDERS = ["railway"];
|
|
2006
2183
|
function getTokenCreationUrl(provider) {
|
|
2007
2184
|
switch (provider) {
|
|
@@ -2014,37 +2191,37 @@ function getTokenCreationUrl(provider) {
|
|
|
2014
2191
|
async function connectWithTokenFlow(accessToken, provider, displayName) {
|
|
2015
2192
|
const tokenUrl = getTokenCreationUrl(provider);
|
|
2016
2193
|
if (provider === "railway") {
|
|
2017
|
-
console.log(
|
|
2018
|
-
console.log(
|
|
2194
|
+
console.log(pc11.yellow("\nTip: Select the workspace containing your projects."));
|
|
2195
|
+
console.log(pc11.yellow(` Do NOT use "No workspace" - it won't have access to your projects.`));
|
|
2019
2196
|
}
|
|
2020
2197
|
await openUrl(tokenUrl);
|
|
2021
|
-
const { token } = await
|
|
2198
|
+
const { token } = await prompts9({
|
|
2022
2199
|
type: "password",
|
|
2023
2200
|
name: "token",
|
|
2024
2201
|
message: `${displayName} API Token:`
|
|
2025
2202
|
});
|
|
2026
2203
|
if (!token) {
|
|
2027
|
-
console.log(
|
|
2204
|
+
console.log(pc11.gray("Cancelled."));
|
|
2028
2205
|
return false;
|
|
2029
2206
|
}
|
|
2030
|
-
console.log(
|
|
2207
|
+
console.log(pc11.gray("\nValidating token..."));
|
|
2031
2208
|
try {
|
|
2032
2209
|
const result = await connectWithToken(accessToken, provider, token);
|
|
2033
2210
|
if (result.success) {
|
|
2034
|
-
console.log(
|
|
2211
|
+
console.log(pc11.green(`
|
|
2035
2212
|
\u2713 Connected to ${displayName}!`));
|
|
2036
|
-
console.log(
|
|
2213
|
+
console.log(pc11.gray(` Account: ${result.user.username}`));
|
|
2037
2214
|
if (result.user.teamName) {
|
|
2038
|
-
console.log(
|
|
2215
|
+
console.log(pc11.gray(` Team: ${result.user.teamName}`));
|
|
2039
2216
|
}
|
|
2040
2217
|
return true;
|
|
2041
2218
|
} else {
|
|
2042
|
-
console.log(
|
|
2219
|
+
console.log(pc11.red("\n\u2717 Connection failed."));
|
|
2043
2220
|
return false;
|
|
2044
2221
|
}
|
|
2045
2222
|
} catch (error) {
|
|
2046
2223
|
const message = error instanceof Error ? error.message : "Token validation failed";
|
|
2047
|
-
console.log(
|
|
2224
|
+
console.log(pc11.red(`
|
|
2048
2225
|
\u2717 ${message}`));
|
|
2049
2226
|
return false;
|
|
2050
2227
|
}
|
|
@@ -2053,7 +2230,7 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
|
2053
2230
|
const authUrl = getProviderAuthUrl(provider, accessToken);
|
|
2054
2231
|
const startTime = /* @__PURE__ */ new Date();
|
|
2055
2232
|
await openUrl(authUrl);
|
|
2056
|
-
console.log(
|
|
2233
|
+
console.log(pc11.gray("Waiting for authorization..."));
|
|
2057
2234
|
const maxAttempts = 60;
|
|
2058
2235
|
let attempts = 0;
|
|
2059
2236
|
while (attempts < maxAttempts) {
|
|
@@ -2065,15 +2242,15 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
|
2065
2242
|
(c) => c.provider === provider && new Date(c.createdAt) > startTime
|
|
2066
2243
|
);
|
|
2067
2244
|
if (newConn) {
|
|
2068
|
-
console.log(
|
|
2245
|
+
console.log(pc11.green(`
|
|
2069
2246
|
\u2713 Connected to ${displayName}!`));
|
|
2070
2247
|
return true;
|
|
2071
2248
|
}
|
|
2072
2249
|
} catch {
|
|
2073
2250
|
}
|
|
2074
2251
|
}
|
|
2075
|
-
console.log(
|
|
2076
|
-
console.log(
|
|
2252
|
+
console.log(pc11.red("\n\u2717 Authorization timeout."));
|
|
2253
|
+
console.log(pc11.gray("Run `keyway connections` to check if the connection was established."));
|
|
2077
2254
|
return false;
|
|
2078
2255
|
}
|
|
2079
2256
|
async function connectCommand(provider, options = {}) {
|
|
@@ -2083,30 +2260,40 @@ async function connectCommand(provider, options = {}) {
|
|
|
2083
2260
|
const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
|
|
2084
2261
|
if (!providerInfo) {
|
|
2085
2262
|
const available = providers.map((p) => p.name).join(", ");
|
|
2086
|
-
console.error(
|
|
2087
|
-
console.log(
|
|
2263
|
+
console.error(pc11.red(`Unknown provider: ${provider}`));
|
|
2264
|
+
console.log(pc11.gray(`Available providers: ${available || "none"}`));
|
|
2088
2265
|
process.exit(1);
|
|
2089
2266
|
}
|
|
2090
2267
|
if (!providerInfo.configured) {
|
|
2091
|
-
console.error(
|
|
2092
|
-
console.log(
|
|
2268
|
+
console.error(pc11.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
|
|
2269
|
+
console.log(pc11.gray("Contact your administrator to enable this integration."));
|
|
2093
2270
|
process.exit(1);
|
|
2094
2271
|
}
|
|
2095
2272
|
const { connections } = await getConnections(accessToken);
|
|
2096
|
-
const
|
|
2097
|
-
if (
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2273
|
+
const existingConnections = connections.filter((c) => c.provider === provider.toLowerCase());
|
|
2274
|
+
if (existingConnections.length > 0) {
|
|
2275
|
+
console.log(pc11.gray(`
|
|
2276
|
+
You have ${existingConnections.length} ${providerInfo.displayName} connection(s):`));
|
|
2277
|
+
for (const conn of existingConnections) {
|
|
2278
|
+
const teamInfo = conn.providerTeamId ? `(Team: ${conn.providerTeamId})` : "(Personal)";
|
|
2279
|
+
console.log(pc11.gray(` - ${teamInfo}`));
|
|
2280
|
+
}
|
|
2281
|
+
console.log("");
|
|
2282
|
+
const { action } = await prompts9({
|
|
2283
|
+
type: "select",
|
|
2284
|
+
name: "action",
|
|
2285
|
+
message: "What would you like to do?",
|
|
2286
|
+
choices: [
|
|
2287
|
+
{ title: "Add another account/team", value: "add" },
|
|
2288
|
+
{ title: "Cancel", value: "cancel" }
|
|
2289
|
+
]
|
|
2103
2290
|
});
|
|
2104
|
-
if (
|
|
2105
|
-
console.log(
|
|
2291
|
+
if (action !== "add") {
|
|
2292
|
+
console.log(pc11.gray("Keeping existing connections."));
|
|
2106
2293
|
return;
|
|
2107
2294
|
}
|
|
2108
2295
|
}
|
|
2109
|
-
console.log(
|
|
2296
|
+
console.log(pc11.blue(`
|
|
2110
2297
|
Connecting to ${providerInfo.displayName}...
|
|
2111
2298
|
`));
|
|
2112
2299
|
let connected = false;
|
|
@@ -2125,7 +2312,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
2125
2312
|
command: "connect",
|
|
2126
2313
|
error: truncateMessage(message)
|
|
2127
2314
|
});
|
|
2128
|
-
console.error(
|
|
2315
|
+
console.error(pc11.red(`
|
|
2129
2316
|
\u2717 ${message}`));
|
|
2130
2317
|
process.exit(1);
|
|
2131
2318
|
}
|
|
@@ -2135,24 +2322,24 @@ async function connectionsCommand(options = {}) {
|
|
|
2135
2322
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2136
2323
|
const { connections } = await getConnections(accessToken);
|
|
2137
2324
|
if (connections.length === 0) {
|
|
2138
|
-
console.log(
|
|
2139
|
-
console.log(
|
|
2140
|
-
console.log(
|
|
2325
|
+
console.log(pc11.gray("No provider connections found."));
|
|
2326
|
+
console.log(pc11.gray("\nConnect to a provider with: keyway connect <provider>"));
|
|
2327
|
+
console.log(pc11.gray("Available providers: vercel, railway"));
|
|
2141
2328
|
return;
|
|
2142
2329
|
}
|
|
2143
|
-
console.log(
|
|
2330
|
+
console.log(pc11.blue("\n\u{1F4E1} Provider Connections\n"));
|
|
2144
2331
|
for (const conn of connections) {
|
|
2145
2332
|
const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
|
|
2146
|
-
const teamInfo = conn.providerTeamId ?
|
|
2333
|
+
const teamInfo = conn.providerTeamId ? pc11.gray(` (Team: ${conn.providerTeamId})`) : "";
|
|
2147
2334
|
const date = new Date(conn.createdAt).toLocaleDateString();
|
|
2148
|
-
console.log(` ${
|
|
2149
|
-
console.log(
|
|
2150
|
-
console.log(
|
|
2335
|
+
console.log(` ${pc11.green("\u25CF")} ${pc11.bold(providerName)}${teamInfo}`);
|
|
2336
|
+
console.log(pc11.gray(` Connected: ${date}`));
|
|
2337
|
+
console.log(pc11.gray(` ID: ${conn.id}`));
|
|
2151
2338
|
console.log("");
|
|
2152
2339
|
}
|
|
2153
2340
|
} catch (error) {
|
|
2154
2341
|
const message = error instanceof Error ? error.message : "Failed to list connections";
|
|
2155
|
-
console.error(
|
|
2342
|
+
console.error(pc11.red(`
|
|
2156
2343
|
\u2717 ${message}`));
|
|
2157
2344
|
process.exit(1);
|
|
2158
2345
|
}
|
|
@@ -2163,22 +2350,22 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2163
2350
|
const { connections } = await getConnections(accessToken);
|
|
2164
2351
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2165
2352
|
if (!connection) {
|
|
2166
|
-
console.log(
|
|
2353
|
+
console.log(pc11.gray(`No connection found for provider: ${provider}`));
|
|
2167
2354
|
return;
|
|
2168
2355
|
}
|
|
2169
2356
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2170
|
-
const { confirm } = await
|
|
2357
|
+
const { confirm } = await prompts9({
|
|
2171
2358
|
type: "confirm",
|
|
2172
2359
|
name: "confirm",
|
|
2173
2360
|
message: `Disconnect from ${providerName}?`,
|
|
2174
2361
|
initial: false
|
|
2175
2362
|
});
|
|
2176
2363
|
if (!confirm) {
|
|
2177
|
-
console.log(
|
|
2364
|
+
console.log(pc11.gray("Cancelled."));
|
|
2178
2365
|
return;
|
|
2179
2366
|
}
|
|
2180
2367
|
await deleteConnection(accessToken, connection.id);
|
|
2181
|
-
console.log(
|
|
2368
|
+
console.log(pc11.green(`
|
|
2182
2369
|
\u2713 Disconnected from ${providerName}`));
|
|
2183
2370
|
trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
|
|
2184
2371
|
provider: provider.toLowerCase()
|
|
@@ -2189,15 +2376,15 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2189
2376
|
command: "disconnect",
|
|
2190
2377
|
error: truncateMessage(message)
|
|
2191
2378
|
});
|
|
2192
|
-
console.error(
|
|
2379
|
+
console.error(pc11.red(`
|
|
2193
2380
|
\u2717 ${message}`));
|
|
2194
2381
|
process.exit(1);
|
|
2195
2382
|
}
|
|
2196
2383
|
}
|
|
2197
2384
|
|
|
2198
2385
|
// src/cmds/sync.ts
|
|
2199
|
-
import
|
|
2200
|
-
import
|
|
2386
|
+
import pc12 from "picocolors";
|
|
2387
|
+
import prompts10 from "prompts";
|
|
2201
2388
|
function mapToVercelEnvironment(keywayEnv) {
|
|
2202
2389
|
const mapping = {
|
|
2203
2390
|
production: "production",
|
|
@@ -2241,36 +2428,36 @@ function mapToProviderEnvironment(provider, keywayEnv) {
|
|
|
2241
2428
|
function displayDiffSummary(diff, providerName) {
|
|
2242
2429
|
const totalDiff = diff.onlyInKeyway.length + diff.onlyInProvider.length + diff.different.length;
|
|
2243
2430
|
if (totalDiff === 0 && diff.same.length > 0) {
|
|
2244
|
-
console.log(
|
|
2431
|
+
console.log(pc12.green(`
|
|
2245
2432
|
\u2713 Already in sync (${diff.same.length} secrets)`));
|
|
2246
2433
|
return;
|
|
2247
2434
|
}
|
|
2248
|
-
console.log(
|
|
2249
|
-
console.log(
|
|
2435
|
+
console.log(pc12.blue("\n\u{1F4CA} Comparison Summary\n"));
|
|
2436
|
+
console.log(pc12.gray(` Keyway: ${diff.keywayCount} secrets | ${providerName}: ${diff.providerCount} secrets
|
|
2250
2437
|
`));
|
|
2251
2438
|
if (diff.onlyInKeyway.length > 0) {
|
|
2252
|
-
console.log(
|
|
2253
|
-
diff.onlyInKeyway.slice(0, 3).forEach((key) => console.log(
|
|
2439
|
+
console.log(pc12.cyan(` \u2192 ${diff.onlyInKeyway.length} only in Keyway`));
|
|
2440
|
+
diff.onlyInKeyway.slice(0, 3).forEach((key) => console.log(pc12.gray(` ${key}`)));
|
|
2254
2441
|
if (diff.onlyInKeyway.length > 3) {
|
|
2255
|
-
console.log(
|
|
2442
|
+
console.log(pc12.gray(` ... and ${diff.onlyInKeyway.length - 3} more`));
|
|
2256
2443
|
}
|
|
2257
2444
|
}
|
|
2258
2445
|
if (diff.onlyInProvider.length > 0) {
|
|
2259
|
-
console.log(
|
|
2260
|
-
diff.onlyInProvider.slice(0, 3).forEach((key) => console.log(
|
|
2446
|
+
console.log(pc12.magenta(` \u2190 ${diff.onlyInProvider.length} only in ${providerName}`));
|
|
2447
|
+
diff.onlyInProvider.slice(0, 3).forEach((key) => console.log(pc12.gray(` ${key}`)));
|
|
2261
2448
|
if (diff.onlyInProvider.length > 3) {
|
|
2262
|
-
console.log(
|
|
2449
|
+
console.log(pc12.gray(` ... and ${diff.onlyInProvider.length - 3} more`));
|
|
2263
2450
|
}
|
|
2264
2451
|
}
|
|
2265
2452
|
if (diff.different.length > 0) {
|
|
2266
|
-
console.log(
|
|
2267
|
-
diff.different.slice(0, 3).forEach((key) => console.log(
|
|
2453
|
+
console.log(pc12.yellow(` \u2260 ${diff.different.length} with different values`));
|
|
2454
|
+
diff.different.slice(0, 3).forEach((key) => console.log(pc12.gray(` ${key}`)));
|
|
2268
2455
|
if (diff.different.length > 3) {
|
|
2269
|
-
console.log(
|
|
2456
|
+
console.log(pc12.gray(` ... and ${diff.different.length - 3} more`));
|
|
2270
2457
|
}
|
|
2271
2458
|
}
|
|
2272
2459
|
if (diff.same.length > 0) {
|
|
2273
|
-
console.log(
|
|
2460
|
+
console.log(pc12.gray(` = ${diff.same.length} identical`));
|
|
2274
2461
|
}
|
|
2275
2462
|
console.log("");
|
|
2276
2463
|
}
|
|
@@ -2312,30 +2499,42 @@ function projectMatchesRepo(project, repoFullName) {
|
|
|
2312
2499
|
}
|
|
2313
2500
|
async function promptProjectSelection(projects, repoFullName) {
|
|
2314
2501
|
const repoName = repoFullName.split("/")[1]?.toLowerCase() || "";
|
|
2502
|
+
const uniqueTeams = new Set(projects.map((p) => p.teamId || "personal"));
|
|
2503
|
+
const hasMultipleAccounts = uniqueTeams.size > 1;
|
|
2315
2504
|
const choices = projects.map((p) => {
|
|
2316
2505
|
const displayName = getProjectDisplayName(p);
|
|
2317
2506
|
let title = displayName;
|
|
2318
2507
|
const badges = [];
|
|
2508
|
+
if (hasMultipleAccounts) {
|
|
2509
|
+
if (p.teamName) {
|
|
2510
|
+
badges.push(pc12.cyan(`[${p.teamName}]`));
|
|
2511
|
+
} else if (p.teamId) {
|
|
2512
|
+
const shortTeamId = p.teamId.length > 12 ? p.teamId.slice(0, 12) + "..." : p.teamId;
|
|
2513
|
+
badges.push(pc12.cyan(`[team:${shortTeamId}]`));
|
|
2514
|
+
} else {
|
|
2515
|
+
badges.push(pc12.cyan("[personal]"));
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2319
2518
|
if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
|
|
2320
|
-
badges.push(
|
|
2519
|
+
badges.push(pc12.green("\u2190 linked"));
|
|
2321
2520
|
} else if (p.name.toLowerCase() === repoName || p.serviceName?.toLowerCase() === repoName) {
|
|
2322
|
-
badges.push(
|
|
2521
|
+
badges.push(pc12.green("\u2190 same name"));
|
|
2323
2522
|
} else if (p.linkedRepo) {
|
|
2324
|
-
badges.push(
|
|
2523
|
+
badges.push(pc12.gray(`\u2192 ${p.linkedRepo}`));
|
|
2325
2524
|
}
|
|
2326
2525
|
if (badges.length > 0) {
|
|
2327
2526
|
title = `${displayName} ${badges.join(" ")}`;
|
|
2328
2527
|
}
|
|
2329
2528
|
return { title, value: p.id };
|
|
2330
2529
|
});
|
|
2331
|
-
const { projectChoice } = await
|
|
2530
|
+
const { projectChoice } = await prompts10({
|
|
2332
2531
|
type: "select",
|
|
2333
2532
|
name: "projectChoice",
|
|
2334
2533
|
message: "Select a project:",
|
|
2335
2534
|
choices
|
|
2336
2535
|
});
|
|
2337
2536
|
if (!projectChoice) {
|
|
2338
|
-
console.log(
|
|
2537
|
+
console.log(pc12.gray("Cancelled."));
|
|
2339
2538
|
process.exit(0);
|
|
2340
2539
|
}
|
|
2341
2540
|
return projects.find((p) => p.id === projectChoice);
|
|
@@ -2343,97 +2542,133 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2343
2542
|
async function syncCommand(provider, options = {}) {
|
|
2344
2543
|
try {
|
|
2345
2544
|
if (options.pull && options.allowDelete) {
|
|
2346
|
-
console.error(
|
|
2347
|
-
console.log(
|
|
2545
|
+
console.error(pc12.red("Error: --allow-delete cannot be used with --pull"));
|
|
2546
|
+
console.log(pc12.gray("The --allow-delete flag is only for push operations."));
|
|
2348
2547
|
process.exit(1);
|
|
2349
2548
|
}
|
|
2350
2549
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2351
2550
|
const repoFullName = detectGitRepo();
|
|
2352
2551
|
if (!repoFullName) {
|
|
2353
|
-
console.error(
|
|
2354
|
-
console.log(
|
|
2552
|
+
console.error(pc12.red("Could not detect Git repository."));
|
|
2553
|
+
console.log(pc12.gray("Run this command from a Git repository directory."));
|
|
2355
2554
|
process.exit(1);
|
|
2356
2555
|
}
|
|
2357
|
-
console.log(
|
|
2556
|
+
console.log(pc12.gray(`Repository: ${repoFullName}`));
|
|
2358
2557
|
const vaultExists = await checkVaultExists(accessToken, repoFullName);
|
|
2359
2558
|
if (!vaultExists) {
|
|
2360
|
-
console.log(
|
|
2559
|
+
console.log(pc12.yellow(`
|
|
2361
2560
|
No vault found for ${repoFullName}.`));
|
|
2362
|
-
const { shouldCreate } = await
|
|
2561
|
+
const { shouldCreate } = await prompts10({
|
|
2363
2562
|
type: "confirm",
|
|
2364
2563
|
name: "shouldCreate",
|
|
2365
2564
|
message: "Create vault now?",
|
|
2366
2565
|
initial: true
|
|
2367
2566
|
});
|
|
2368
2567
|
if (!shouldCreate) {
|
|
2369
|
-
console.log(
|
|
2568
|
+
console.log(pc12.gray("Cancelled. Run `keyway init` to create a vault first."));
|
|
2370
2569
|
process.exit(0);
|
|
2371
2570
|
}
|
|
2372
|
-
console.log(
|
|
2571
|
+
console.log(pc12.gray("\nCreating vault..."));
|
|
2373
2572
|
try {
|
|
2374
2573
|
await initVault(repoFullName, accessToken);
|
|
2375
|
-
console.log(
|
|
2574
|
+
console.log(pc12.green(`\u2713 Vault created for ${repoFullName}
|
|
2376
2575
|
`));
|
|
2377
2576
|
} catch (error) {
|
|
2378
2577
|
const message = error instanceof Error ? error.message : "Failed to create vault";
|
|
2379
|
-
console.error(
|
|
2578
|
+
console.error(pc12.red(`
|
|
2380
2579
|
\u2717 ${message}`));
|
|
2381
2580
|
process.exit(1);
|
|
2382
2581
|
}
|
|
2383
2582
|
}
|
|
2384
|
-
|
|
2385
|
-
let
|
|
2386
|
-
if (
|
|
2387
|
-
|
|
2388
|
-
console.log(pc10.yellow(`
|
|
2583
|
+
const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2584
|
+
let { projects: allProjects, connections } = await getAllProviderProjects(accessToken, provider.toLowerCase());
|
|
2585
|
+
if (connections.length === 0) {
|
|
2586
|
+
console.log(pc12.yellow(`
|
|
2389
2587
|
Not connected to ${providerDisplayName}.`));
|
|
2390
|
-
const { shouldConnect } = await
|
|
2588
|
+
const { shouldConnect } = await prompts10({
|
|
2391
2589
|
type: "confirm",
|
|
2392
2590
|
name: "shouldConnect",
|
|
2393
2591
|
message: `Connect to ${providerDisplayName} now?`,
|
|
2394
2592
|
initial: true
|
|
2395
2593
|
});
|
|
2396
2594
|
if (!shouldConnect) {
|
|
2397
|
-
console.log(
|
|
2595
|
+
console.log(pc12.gray("Cancelled."));
|
|
2398
2596
|
process.exit(0);
|
|
2399
2597
|
}
|
|
2400
2598
|
await connectCommand(provider, { loginPrompt: false });
|
|
2401
|
-
const refreshed = await
|
|
2599
|
+
const refreshed = await getAllProviderProjects(accessToken, provider.toLowerCase());
|
|
2600
|
+
allProjects = refreshed.projects;
|
|
2402
2601
|
connections = refreshed.connections;
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
console.error(pc10.red(`
|
|
2602
|
+
if (connections.length === 0) {
|
|
2603
|
+
console.error(pc12.red(`
|
|
2406
2604
|
Connection to ${providerDisplayName} failed.`));
|
|
2407
2605
|
process.exit(1);
|
|
2408
2606
|
}
|
|
2409
2607
|
console.log("");
|
|
2410
2608
|
}
|
|
2411
|
-
|
|
2609
|
+
let projects = allProjects.map((p) => ({
|
|
2610
|
+
id: p.id,
|
|
2611
|
+
name: p.name,
|
|
2612
|
+
serviceId: p.serviceId,
|
|
2613
|
+
serviceName: p.serviceName,
|
|
2614
|
+
linkedRepo: p.linkedRepo,
|
|
2615
|
+
environments: p.environments,
|
|
2616
|
+
connectionId: p.connectionId,
|
|
2617
|
+
teamId: p.teamId,
|
|
2618
|
+
teamName: p.teamName
|
|
2619
|
+
}));
|
|
2620
|
+
if (options.team) {
|
|
2621
|
+
const teamFilter = options.team.toLowerCase();
|
|
2622
|
+
const filteredProjects = projects.filter(
|
|
2623
|
+
(p) => p.teamId?.toLowerCase() === teamFilter || p.teamName?.toLowerCase() === teamFilter || // Match "personal" for null teamId
|
|
2624
|
+
teamFilter === "personal" && !p.teamId
|
|
2625
|
+
);
|
|
2626
|
+
if (filteredProjects.length === 0) {
|
|
2627
|
+
console.error(pc12.red(`No projects found for team: ${options.team}`));
|
|
2628
|
+
console.log(pc12.gray("Available teams:"));
|
|
2629
|
+
const teams = /* @__PURE__ */ new Set();
|
|
2630
|
+
projects.forEach((p) => {
|
|
2631
|
+
if (p.teamName) teams.add(p.teamName);
|
|
2632
|
+
else if (p.teamId) teams.add(p.teamId);
|
|
2633
|
+
else teams.add("personal");
|
|
2634
|
+
});
|
|
2635
|
+
teams.forEach((t) => console.log(pc12.gray(` - ${t}`)));
|
|
2636
|
+
process.exit(1);
|
|
2637
|
+
}
|
|
2638
|
+
projects = filteredProjects;
|
|
2639
|
+
console.log(pc12.gray(`Filtered to ${projects.length} projects in team: ${options.team}`));
|
|
2640
|
+
}
|
|
2412
2641
|
if (projects.length === 0) {
|
|
2413
|
-
console.error(
|
|
2642
|
+
console.error(pc12.red(`No projects found in your ${providerDisplayName} account(s).`));
|
|
2643
|
+
if (connections.length > 1) {
|
|
2644
|
+
console.log(pc12.gray(`Checked ${connections.length} connected accounts.`));
|
|
2645
|
+
}
|
|
2414
2646
|
process.exit(1);
|
|
2415
2647
|
}
|
|
2648
|
+
if (connections.length > 1 && !options.team) {
|
|
2649
|
+
console.log(pc12.gray(`Searching ${projects.length} projects across ${connections.length} ${providerDisplayName} accounts...`));
|
|
2650
|
+
}
|
|
2416
2651
|
let selectedProject;
|
|
2417
2652
|
if (options.project) {
|
|
2418
2653
|
const found = projects.find(
|
|
2419
2654
|
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase() || p.serviceName?.toLowerCase() === options.project?.toLowerCase()
|
|
2420
2655
|
);
|
|
2421
2656
|
if (!found) {
|
|
2422
|
-
console.error(
|
|
2423
|
-
console.log(
|
|
2424
|
-
projects.forEach((p) => console.log(
|
|
2657
|
+
console.error(pc12.red(`Project not found: ${options.project}`));
|
|
2658
|
+
console.log(pc12.gray("Available projects:"));
|
|
2659
|
+
projects.forEach((p) => console.log(pc12.gray(` - ${getProjectDisplayName(p)}`)));
|
|
2425
2660
|
process.exit(1);
|
|
2426
2661
|
}
|
|
2427
2662
|
selectedProject = found;
|
|
2428
2663
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2429
2664
|
console.log("");
|
|
2430
|
-
console.log(
|
|
2431
|
-
console.log(
|
|
2432
|
-
console.log(
|
|
2433
|
-
console.log(
|
|
2434
|
-
console.log(
|
|
2665
|
+
console.log(pc12.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"));
|
|
2666
|
+
console.log(pc12.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2667
|
+
console.log(pc12.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"));
|
|
2668
|
+
console.log(pc12.yellow(` Current repo: ${repoFullName}`));
|
|
2669
|
+
console.log(pc12.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
|
|
2435
2670
|
if (selectedProject.linkedRepo) {
|
|
2436
|
-
console.log(
|
|
2671
|
+
console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2437
2672
|
}
|
|
2438
2673
|
console.log("");
|
|
2439
2674
|
}
|
|
@@ -2442,11 +2677,18 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2442
2677
|
if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
|
|
2443
2678
|
selectedProject = autoMatch.project;
|
|
2444
2679
|
const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
|
|
2445
|
-
|
|
2680
|
+
let teamInfo = "";
|
|
2681
|
+
if (selectedProject.teamName) {
|
|
2682
|
+
teamInfo = pc12.gray(` (${selectedProject.teamName})`);
|
|
2683
|
+
} else if (selectedProject.teamId && connections.length > 1) {
|
|
2684
|
+
const shortTeamId = selectedProject.teamId.length > 12 ? selectedProject.teamId.slice(0, 12) + "..." : selectedProject.teamId;
|
|
2685
|
+
teamInfo = pc12.gray(` (team:${shortTeamId})`);
|
|
2686
|
+
}
|
|
2687
|
+
console.log(pc12.green(`\u2713 Auto-selected project: ${getProjectDisplayName(selectedProject)}${teamInfo} (${matchReason})`));
|
|
2446
2688
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2447
2689
|
const partialDisplayName = getProjectDisplayName(autoMatch.project);
|
|
2448
|
-
console.log(
|
|
2449
|
-
const { useDetected } = await
|
|
2690
|
+
console.log(pc12.yellow(`Detected project: ${partialDisplayName} (partial match)`));
|
|
2691
|
+
const { useDetected } = await prompts10({
|
|
2450
2692
|
type: "confirm",
|
|
2451
2693
|
name: "useDetected",
|
|
2452
2694
|
message: `Use ${partialDisplayName}?`,
|
|
@@ -2461,30 +2703,30 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2461
2703
|
selectedProject = projects[0];
|
|
2462
2704
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2463
2705
|
console.log("");
|
|
2464
|
-
console.log(
|
|
2465
|
-
console.log(
|
|
2466
|
-
console.log(
|
|
2467
|
-
console.log(
|
|
2468
|
-
console.log(
|
|
2706
|
+
console.log(pc12.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"));
|
|
2707
|
+
console.log(pc12.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2708
|
+
console.log(pc12.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"));
|
|
2709
|
+
console.log(pc12.yellow(` Current repo: ${repoFullName}`));
|
|
2710
|
+
console.log(pc12.yellow(` Only project: ${getProjectDisplayName(selectedProject)}`));
|
|
2469
2711
|
if (selectedProject.linkedRepo) {
|
|
2470
|
-
console.log(
|
|
2712
|
+
console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2471
2713
|
}
|
|
2472
2714
|
console.log("");
|
|
2473
|
-
const { continueAnyway } = await
|
|
2715
|
+
const { continueAnyway } = await prompts10({
|
|
2474
2716
|
type: "confirm",
|
|
2475
2717
|
name: "continueAnyway",
|
|
2476
2718
|
message: "Continue anyway?",
|
|
2477
2719
|
initial: false
|
|
2478
2720
|
});
|
|
2479
2721
|
if (!continueAnyway) {
|
|
2480
|
-
console.log(
|
|
2722
|
+
console.log(pc12.gray("Cancelled."));
|
|
2481
2723
|
process.exit(0);
|
|
2482
2724
|
}
|
|
2483
2725
|
}
|
|
2484
2726
|
} else {
|
|
2485
|
-
console.log(
|
|
2727
|
+
console.log(pc12.yellow(`
|
|
2486
2728
|
\u26A0\uFE0F No matching project found for ${repoFullName}`));
|
|
2487
|
-
console.log(
|
|
2729
|
+
console.log(pc12.gray("Select a project manually:\n"));
|
|
2488
2730
|
selectedProject = await promptProjectSelection(projects, repoFullName);
|
|
2489
2731
|
}
|
|
2490
2732
|
}
|
|
@@ -2492,23 +2734,23 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2492
2734
|
const autoMatch = findMatchingProject(projects, repoFullName);
|
|
2493
2735
|
if (autoMatch && autoMatch.project.id !== selectedProject.id) {
|
|
2494
2736
|
console.log("");
|
|
2495
|
-
console.log(
|
|
2496
|
-
console.log(
|
|
2497
|
-
console.log(
|
|
2498
|
-
console.log(
|
|
2499
|
-
console.log(
|
|
2737
|
+
console.log(pc12.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"));
|
|
2738
|
+
console.log(pc12.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
|
|
2739
|
+
console.log(pc12.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"));
|
|
2740
|
+
console.log(pc12.yellow(` Current repo: ${repoFullName}`));
|
|
2741
|
+
console.log(pc12.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
|
|
2500
2742
|
if (selectedProject.linkedRepo) {
|
|
2501
|
-
console.log(
|
|
2743
|
+
console.log(pc12.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2502
2744
|
}
|
|
2503
2745
|
console.log("");
|
|
2504
|
-
const { continueAnyway } = await
|
|
2746
|
+
const { continueAnyway } = await prompts10({
|
|
2505
2747
|
type: "confirm",
|
|
2506
2748
|
name: "continueAnyway",
|
|
2507
2749
|
message: "Are you sure you want to sync with this project?",
|
|
2508
2750
|
initial: false
|
|
2509
2751
|
});
|
|
2510
2752
|
if (!continueAnyway) {
|
|
2511
|
-
console.log(
|
|
2753
|
+
console.log(pc12.gray("Cancelled."));
|
|
2512
2754
|
process.exit(0);
|
|
2513
2755
|
}
|
|
2514
2756
|
}
|
|
@@ -2522,7 +2764,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2522
2764
|
if (needsEnvPrompt || needsDirectionPrompt) {
|
|
2523
2765
|
if (needsEnvPrompt) {
|
|
2524
2766
|
const vaultEnvs = await getVaultEnvironments(accessToken, repoFullName);
|
|
2525
|
-
const { selectedEnv } = await
|
|
2767
|
+
const { selectedEnv } = await prompts10({
|
|
2526
2768
|
type: "select",
|
|
2527
2769
|
name: "selectedEnv",
|
|
2528
2770
|
message: "Keyway environment:",
|
|
@@ -2530,7 +2772,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2530
2772
|
initial: Math.max(0, vaultEnvs.indexOf("production"))
|
|
2531
2773
|
});
|
|
2532
2774
|
if (!selectedEnv) {
|
|
2533
|
-
console.log(
|
|
2775
|
+
console.log(pc12.gray("Cancelled."));
|
|
2534
2776
|
process.exit(0);
|
|
2535
2777
|
}
|
|
2536
2778
|
keywayEnv = selectedEnv;
|
|
@@ -2544,9 +2786,9 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2544
2786
|
providerEnv = mappedEnv;
|
|
2545
2787
|
} else if (selectedProject.environments.length === 1) {
|
|
2546
2788
|
providerEnv = selectedProject.environments[0];
|
|
2547
|
-
console.log(
|
|
2789
|
+
console.log(pc12.gray(`Using ${providerName} environment: ${providerEnv}`));
|
|
2548
2790
|
} else {
|
|
2549
|
-
const { selectedProviderEnv } = await
|
|
2791
|
+
const { selectedProviderEnv } = await prompts10({
|
|
2550
2792
|
type: "select",
|
|
2551
2793
|
name: "selectedProviderEnv",
|
|
2552
2794
|
message: `${providerName} environment:`,
|
|
@@ -2556,7 +2798,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2556
2798
|
))
|
|
2557
2799
|
});
|
|
2558
2800
|
if (!selectedProviderEnv) {
|
|
2559
|
-
console.log(
|
|
2801
|
+
console.log(pc12.gray("Cancelled."));
|
|
2560
2802
|
process.exit(0);
|
|
2561
2803
|
}
|
|
2562
2804
|
providerEnv = selectedProviderEnv;
|
|
@@ -2570,9 +2812,9 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2570
2812
|
if (needsDirectionPrompt) {
|
|
2571
2813
|
const effectiveKeywayEnv = keywayEnv || "production";
|
|
2572
2814
|
const effectiveProviderEnv = providerEnv || mapToProviderEnvironment(provider, effectiveKeywayEnv);
|
|
2573
|
-
console.log(
|
|
2815
|
+
console.log(pc12.gray("\nComparing secrets..."));
|
|
2574
2816
|
diff = await getSyncDiff(accessToken, repoFullName, {
|
|
2575
|
-
connectionId:
|
|
2817
|
+
connectionId: selectedProject.connectionId,
|
|
2576
2818
|
projectId: selectedProject.id,
|
|
2577
2819
|
serviceId: selectedProject.serviceId,
|
|
2578
2820
|
// Railway: service ID for service-specific variables
|
|
@@ -2592,7 +2834,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2592
2834
|
} else if (diff.providerCount === 0 && diff.keywayCount > 0) {
|
|
2593
2835
|
defaultDirection = 0;
|
|
2594
2836
|
}
|
|
2595
|
-
const { selectedDirection } = await
|
|
2837
|
+
const { selectedDirection } = await prompts10({
|
|
2596
2838
|
type: "select",
|
|
2597
2839
|
name: "selectedDirection",
|
|
2598
2840
|
message: "Sync direction:",
|
|
@@ -2603,7 +2845,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2603
2845
|
initial: defaultDirection
|
|
2604
2846
|
});
|
|
2605
2847
|
if (!selectedDirection) {
|
|
2606
|
-
console.log(
|
|
2848
|
+
console.log(pc12.gray("Cancelled."));
|
|
2607
2849
|
process.exit(0);
|
|
2608
2850
|
}
|
|
2609
2851
|
direction = selectedDirection;
|
|
@@ -2615,15 +2857,15 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2615
2857
|
const status = await getSyncStatus(
|
|
2616
2858
|
accessToken,
|
|
2617
2859
|
repoFullName,
|
|
2618
|
-
|
|
2860
|
+
selectedProject.connectionId,
|
|
2619
2861
|
selectedProject.id,
|
|
2620
2862
|
keywayEnv
|
|
2621
2863
|
);
|
|
2622
2864
|
if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
|
|
2623
|
-
console.log(
|
|
2865
|
+
console.log(pc12.yellow(`
|
|
2624
2866
|
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2625
|
-
console.log(
|
|
2626
|
-
const { importFirst } = await
|
|
2867
|
+
console.log(pc12.gray(` (Use --environment to sync a different environment)`));
|
|
2868
|
+
const { importFirst } = await prompts10({
|
|
2627
2869
|
type: "confirm",
|
|
2628
2870
|
name: "importFirst",
|
|
2629
2871
|
message: `Import secrets from ${providerName} first?`,
|
|
@@ -2633,7 +2875,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2633
2875
|
await executeSyncOperation(
|
|
2634
2876
|
accessToken,
|
|
2635
2877
|
repoFullName,
|
|
2636
|
-
|
|
2878
|
+
selectedProject.connectionId,
|
|
2637
2879
|
selectedProject,
|
|
2638
2880
|
keywayEnv,
|
|
2639
2881
|
providerEnv,
|
|
@@ -2649,7 +2891,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2649
2891
|
await executeSyncOperation(
|
|
2650
2892
|
accessToken,
|
|
2651
2893
|
repoFullName,
|
|
2652
|
-
|
|
2894
|
+
selectedProject.connectionId,
|
|
2653
2895
|
selectedProject,
|
|
2654
2896
|
keywayEnv,
|
|
2655
2897
|
providerEnv,
|
|
@@ -2664,7 +2906,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2664
2906
|
command: "sync",
|
|
2665
2907
|
error: truncateMessage(message)
|
|
2666
2908
|
});
|
|
2667
|
-
console.error(
|
|
2909
|
+
console.error(pc12.red(`
|
|
2668
2910
|
\u2717 ${message}`));
|
|
2669
2911
|
process.exit(1);
|
|
2670
2912
|
}
|
|
@@ -2683,49 +2925,49 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2683
2925
|
});
|
|
2684
2926
|
const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
|
|
2685
2927
|
if (totalChanges === 0) {
|
|
2686
|
-
console.log(
|
|
2928
|
+
console.log(pc12.green("\n\u2713 Already in sync. No changes needed."));
|
|
2687
2929
|
return;
|
|
2688
2930
|
}
|
|
2689
|
-
console.log(
|
|
2931
|
+
console.log(pc12.blue("\n\u{1F4CB} Sync Preview\n"));
|
|
2690
2932
|
if (preview.toCreate.length > 0) {
|
|
2691
|
-
console.log(
|
|
2692
|
-
preview.toCreate.slice(0, 5).forEach((key) => console.log(
|
|
2933
|
+
console.log(pc12.green(` + ${preview.toCreate.length} to create`));
|
|
2934
|
+
preview.toCreate.slice(0, 5).forEach((key) => console.log(pc12.gray(` ${key}`)));
|
|
2693
2935
|
if (preview.toCreate.length > 5) {
|
|
2694
|
-
console.log(
|
|
2936
|
+
console.log(pc12.gray(` ... and ${preview.toCreate.length - 5} more`));
|
|
2695
2937
|
}
|
|
2696
2938
|
}
|
|
2697
2939
|
if (preview.toUpdate.length > 0) {
|
|
2698
|
-
console.log(
|
|
2699
|
-
preview.toUpdate.slice(0, 5).forEach((key) => console.log(
|
|
2940
|
+
console.log(pc12.yellow(` ~ ${preview.toUpdate.length} to update`));
|
|
2941
|
+
preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc12.gray(` ${key}`)));
|
|
2700
2942
|
if (preview.toUpdate.length > 5) {
|
|
2701
|
-
console.log(
|
|
2943
|
+
console.log(pc12.gray(` ... and ${preview.toUpdate.length - 5} more`));
|
|
2702
2944
|
}
|
|
2703
2945
|
}
|
|
2704
2946
|
if (preview.toDelete.length > 0) {
|
|
2705
|
-
console.log(
|
|
2706
|
-
preview.toDelete.slice(0, 5).forEach((key) => console.log(
|
|
2947
|
+
console.log(pc12.red(` - ${preview.toDelete.length} to delete`));
|
|
2948
|
+
preview.toDelete.slice(0, 5).forEach((key) => console.log(pc12.gray(` ${key}`)));
|
|
2707
2949
|
if (preview.toDelete.length > 5) {
|
|
2708
|
-
console.log(
|
|
2950
|
+
console.log(pc12.gray(` ... and ${preview.toDelete.length - 5} more`));
|
|
2709
2951
|
}
|
|
2710
2952
|
}
|
|
2711
2953
|
if (preview.toSkip.length > 0) {
|
|
2712
|
-
console.log(
|
|
2954
|
+
console.log(pc12.gray(` \u25CB ${preview.toSkip.length} unchanged`));
|
|
2713
2955
|
}
|
|
2714
2956
|
console.log("");
|
|
2715
2957
|
if (!skipConfirm) {
|
|
2716
2958
|
const target = direction === "push" ? providerName : "Keyway";
|
|
2717
|
-
const { confirm } = await
|
|
2959
|
+
const { confirm } = await prompts10({
|
|
2718
2960
|
type: "confirm",
|
|
2719
2961
|
name: "confirm",
|
|
2720
2962
|
message: `Apply ${totalChanges} changes to ${target}?`,
|
|
2721
2963
|
initial: true
|
|
2722
2964
|
});
|
|
2723
2965
|
if (!confirm) {
|
|
2724
|
-
console.log(
|
|
2966
|
+
console.log(pc12.gray("Cancelled."));
|
|
2725
2967
|
return;
|
|
2726
2968
|
}
|
|
2727
2969
|
}
|
|
2728
|
-
console.log(
|
|
2970
|
+
console.log(pc12.blue("\n\u23F3 Syncing...\n"));
|
|
2729
2971
|
const result = await executeSync(accessToken, repoFullName, {
|
|
2730
2972
|
connectionId,
|
|
2731
2973
|
projectId: project.id,
|
|
@@ -2737,11 +2979,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2737
2979
|
allowDelete
|
|
2738
2980
|
});
|
|
2739
2981
|
if (result.success) {
|
|
2740
|
-
console.log(
|
|
2741
|
-
console.log(
|
|
2742
|
-
console.log(
|
|
2982
|
+
console.log(pc12.green("\u2713 Sync complete"));
|
|
2983
|
+
console.log(pc12.gray(` Created: ${result.stats.created}`));
|
|
2984
|
+
console.log(pc12.gray(` Updated: ${result.stats.updated}`));
|
|
2743
2985
|
if (result.stats.deleted > 0) {
|
|
2744
|
-
console.log(
|
|
2986
|
+
console.log(pc12.gray(` Deleted: ${result.stats.deleted}`));
|
|
2745
2987
|
}
|
|
2746
2988
|
trackEvent(AnalyticsEvents.CLI_SYNC, {
|
|
2747
2989
|
provider,
|
|
@@ -2751,7 +2993,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2751
2993
|
deleted: result.stats.deleted
|
|
2752
2994
|
});
|
|
2753
2995
|
} else {
|
|
2754
|
-
console.error(
|
|
2996
|
+
console.error(pc12.red(`
|
|
2755
2997
|
\u2717 ${result.error}`));
|
|
2756
2998
|
process.exit(1);
|
|
2757
2999
|
}
|
|
@@ -2759,16 +3001,16 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2759
3001
|
|
|
2760
3002
|
// src/cli.ts
|
|
2761
3003
|
process.on("unhandledRejection", (reason) => {
|
|
2762
|
-
console.error(
|
|
3004
|
+
console.error(pc13.red("Unhandled error:"), reason);
|
|
2763
3005
|
process.exit(1);
|
|
2764
3006
|
});
|
|
2765
3007
|
var program = new Command();
|
|
2766
3008
|
var TAGLINE = "Sync secrets with your team and infra";
|
|
2767
3009
|
var showBanner = () => {
|
|
2768
|
-
const text =
|
|
3010
|
+
const text = pc13.bold(pc13.cyan("Keyway CLI"));
|
|
2769
3011
|
console.log(`
|
|
2770
3012
|
${text}
|
|
2771
|
-
${
|
|
3013
|
+
${pc13.gray(TAGLINE)}
|
|
2772
3014
|
`);
|
|
2773
3015
|
};
|
|
2774
3016
|
showBanner();
|
|
@@ -2800,13 +3042,17 @@ program.command("connections").description("List your provider connections").opt
|
|
|
2800
3042
|
program.command("disconnect <provider>").description("Disconnect from a provider").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
|
|
2801
3043
|
await disconnectCommand(provider, options);
|
|
2802
3044
|
});
|
|
2803
|
-
program.command("sync <provider>").description("Sync secrets with a provider (e.g., vercel)").option("--push", "Export secrets from Keyway to provider").option("--pull", "Import secrets from provider to Keyway").option("-e, --environment <env>", "Keyway environment (default: production)").option("--provider-env <env>", "Provider environment (default: production)").option("--project <project>", "Provider project name or ID").option("--allow-delete", "Allow deleting secrets not in source").option("-y, --yes", "Skip confirmation prompt").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
|
|
3045
|
+
program.command("sync <provider>").description("Sync secrets with a provider (e.g., vercel)").option("--push", "Export secrets from Keyway to provider").option("--pull", "Import secrets from provider to Keyway").option("-e, --environment <env>", "Keyway environment (default: production)").option("--provider-env <env>", "Provider environment (default: production)").option("--project <project>", "Provider project name or ID").option("--team <team>", "Team/org name or ID (for multi-account)").option("--allow-delete", "Allow deleting secrets not in source").option("-y, --yes", "Skip confirmation prompt").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
|
|
2804
3046
|
await syncCommand(provider, options);
|
|
2805
3047
|
});
|
|
3048
|
+
var ci = program.command("ci").description("CI/CD integration commands");
|
|
3049
|
+
ci.command("setup").description("Setup GitHub Actions integration (adds KEYWAY_TOKEN secret)").option("--repo <repo>", "Repository in owner/repo format (auto-detected)").action(async (options) => {
|
|
3050
|
+
await ciSetupCommand(options);
|
|
3051
|
+
});
|
|
2806
3052
|
(async () => {
|
|
2807
3053
|
await warnIfEnvNotGitignored();
|
|
2808
3054
|
await program.parseAsync();
|
|
2809
3055
|
})().catch((error) => {
|
|
2810
|
-
console.error(
|
|
3056
|
+
console.error(pc13.red("Error:"), error.message || error);
|
|
2811
3057
|
process.exit(1);
|
|
2812
3058
|
});
|