@keywaysh/cli 0.1.12 → 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 +348 -263
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,17 +8,18 @@ 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";
|
|
19
19
|
import fs from "fs";
|
|
20
20
|
import path from "path";
|
|
21
21
|
import pc from "picocolors";
|
|
22
|
+
import prompts from "prompts";
|
|
22
23
|
function getCurrentRepoFullName() {
|
|
23
24
|
try {
|
|
24
25
|
if (!isGitRepository()) {
|
|
@@ -87,9 +88,47 @@ function checkEnvGitignore() {
|
|
|
87
88
|
return true;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
function addEnvToGitignore() {
|
|
92
|
+
try {
|
|
93
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
94
|
+
encoding: "utf-8",
|
|
95
|
+
stdio: "pipe"
|
|
96
|
+
}).trim();
|
|
97
|
+
const gitignorePath = path.join(gitRoot, ".gitignore");
|
|
98
|
+
const envEntry = ".env*";
|
|
99
|
+
if (fs.existsSync(gitignorePath)) {
|
|
100
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
101
|
+
const newContent = content.endsWith("\n") ? `${content}${envEntry}
|
|
102
|
+
` : `${content}
|
|
103
|
+
${envEntry}
|
|
104
|
+
`;
|
|
105
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
106
|
+
} else {
|
|
107
|
+
fs.writeFileSync(gitignorePath, `${envEntry}
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function warnIfEnvNotGitignored() {
|
|
116
|
+
if (checkEnvGitignore()) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(pc.yellow("\u26A0\uFE0F .env files are not in .gitignore - secrets may be committed"));
|
|
120
|
+
const { addToGitignore } = await prompts({
|
|
121
|
+
type: "confirm",
|
|
122
|
+
name: "addToGitignore",
|
|
123
|
+
message: "Add .env* to .gitignore?",
|
|
124
|
+
initial: true
|
|
125
|
+
});
|
|
126
|
+
if (addToGitignore) {
|
|
127
|
+
if (addEnvToGitignore()) {
|
|
128
|
+
console.log(pc.green("\u2713 Added .env* to .gitignore"));
|
|
129
|
+
} else {
|
|
130
|
+
console.log(pc.red("\u2717 Failed to update .gitignore"));
|
|
131
|
+
}
|
|
93
132
|
}
|
|
94
133
|
}
|
|
95
134
|
|
|
@@ -101,7 +140,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
101
140
|
// package.json
|
|
102
141
|
var package_default = {
|
|
103
142
|
name: "@keywaysh/cli",
|
|
104
|
-
version: "0.1.
|
|
143
|
+
version: "0.1.14",
|
|
105
144
|
description: "One link to all your secrets",
|
|
106
145
|
type: "module",
|
|
107
146
|
bin: {
|
|
@@ -719,7 +758,7 @@ var AnalyticsEvents = {
|
|
|
719
758
|
// src/cmds/readme.ts
|
|
720
759
|
import fs3 from "fs";
|
|
721
760
|
import path3 from "path";
|
|
722
|
-
import
|
|
761
|
+
import prompts2 from "prompts";
|
|
723
762
|
import pc2 from "picocolors";
|
|
724
763
|
import balanced from "balanced-match";
|
|
725
764
|
function generateBadge(repo) {
|
|
@@ -814,7 +853,7 @@ async function ensureReadme(repoName, cwd) {
|
|
|
814
853
|
console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
815
854
|
return null;
|
|
816
855
|
}
|
|
817
|
-
const { confirm } = await
|
|
856
|
+
const { confirm } = await prompts2(
|
|
818
857
|
{
|
|
819
858
|
type: "confirm",
|
|
820
859
|
name: "confirm",
|
|
@@ -861,15 +900,15 @@ async function addBadgeToReadme(silent = false) {
|
|
|
861
900
|
}
|
|
862
901
|
|
|
863
902
|
// src/cmds/push.ts
|
|
864
|
-
import
|
|
865
|
-
import
|
|
866
|
-
import
|
|
867
|
-
import
|
|
903
|
+
import pc6 from "picocolors";
|
|
904
|
+
import fs5 from "fs";
|
|
905
|
+
import path5 from "path";
|
|
906
|
+
import prompts5 from "prompts";
|
|
868
907
|
|
|
869
908
|
// src/cmds/login.ts
|
|
870
909
|
import pc4 from "picocolors";
|
|
871
910
|
import readline from "readline";
|
|
872
|
-
import
|
|
911
|
+
import prompts3 from "prompts";
|
|
873
912
|
|
|
874
913
|
// src/utils/helpers.ts
|
|
875
914
|
import pc3 from "picocolors";
|
|
@@ -1010,7 +1049,7 @@ async function runTokenLogin() {
|
|
|
1010
1049
|
await openUrl(url);
|
|
1011
1050
|
console.log(pc4.gray("Select the detected repo (or scope manually)."));
|
|
1012
1051
|
console.log(pc4.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
|
|
1013
|
-
const { token } = await
|
|
1052
|
+
const { token } = await prompts3(
|
|
1014
1053
|
{
|
|
1015
1054
|
type: "password",
|
|
1016
1055
|
name: "token",
|
|
@@ -1073,9 +1112,34 @@ async function logoutCommand() {
|
|
|
1073
1112
|
console.log(pc4.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
1074
1113
|
}
|
|
1075
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
|
+
|
|
1076
1140
|
// src/cmds/push.ts
|
|
1077
1141
|
function deriveEnvFromFile(file) {
|
|
1078
|
-
const base =
|
|
1142
|
+
const base = path5.basename(file);
|
|
1079
1143
|
const match = base.match(/\.env(?:\.(.+))?$/);
|
|
1080
1144
|
if (match) {
|
|
1081
1145
|
return match[1] || "development";
|
|
@@ -1084,15 +1148,15 @@ function deriveEnvFromFile(file) {
|
|
|
1084
1148
|
}
|
|
1085
1149
|
function discoverEnvCandidates(cwd) {
|
|
1086
1150
|
try {
|
|
1087
|
-
const entries =
|
|
1151
|
+
const entries = fs5.readdirSync(cwd);
|
|
1088
1152
|
const hasEnvLocal = entries.includes(".env.local");
|
|
1089
1153
|
if (hasEnvLocal) {
|
|
1090
|
-
console.log(
|
|
1154
|
+
console.log(pc6.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
|
|
1091
1155
|
}
|
|
1092
1156
|
const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
|
|
1093
|
-
const fullPath =
|
|
1157
|
+
const fullPath = path5.join(cwd, name);
|
|
1094
1158
|
try {
|
|
1095
|
-
const stat =
|
|
1159
|
+
const stat = fs5.statSync(fullPath);
|
|
1096
1160
|
if (!stat.isFile()) return null;
|
|
1097
1161
|
return { file: name, env: deriveEnvFromFile(name) };
|
|
1098
1162
|
} catch {
|
|
@@ -1113,13 +1177,21 @@ function discoverEnvCandidates(cwd) {
|
|
|
1113
1177
|
}
|
|
1114
1178
|
async function pushCommand(options) {
|
|
1115
1179
|
try {
|
|
1116
|
-
console.log(
|
|
1180
|
+
console.log(pc6.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
1117
1181
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1118
1182
|
let environment = options.env;
|
|
1119
1183
|
let envFile = options.file;
|
|
1120
1184
|
const candidates = discoverEnvCandidates(process.cwd());
|
|
1121
1185
|
if (candidates.length === 0 && !envFile) {
|
|
1122
|
-
|
|
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;
|
|
1123
1195
|
}
|
|
1124
1196
|
if (environment && !envFile) {
|
|
1125
1197
|
const match = candidates.find((c) => c.env === environment);
|
|
@@ -1128,7 +1200,7 @@ async function pushCommand(options) {
|
|
|
1128
1200
|
}
|
|
1129
1201
|
}
|
|
1130
1202
|
if (!environment && !envFile && isInteractive2 && candidates.length > 0) {
|
|
1131
|
-
const { choice } = await
|
|
1203
|
+
const { choice } = await prompts5(
|
|
1132
1204
|
{
|
|
1133
1205
|
type: "select",
|
|
1134
1206
|
name: "choice",
|
|
@@ -1151,15 +1223,15 @@ async function pushCommand(options) {
|
|
|
1151
1223
|
envFile = choice.file;
|
|
1152
1224
|
environment = choice.env;
|
|
1153
1225
|
} else if (choice === "custom") {
|
|
1154
|
-
const { fileInput } = await
|
|
1226
|
+
const { fileInput } = await prompts5(
|
|
1155
1227
|
{
|
|
1156
1228
|
type: "text",
|
|
1157
1229
|
name: "fileInput",
|
|
1158
1230
|
message: "Path to env file:",
|
|
1159
1231
|
validate: (value) => {
|
|
1160
1232
|
if (!value) return "Path is required";
|
|
1161
|
-
const resolved =
|
|
1162
|
-
if (!
|
|
1233
|
+
const resolved = path5.resolve(process.cwd(), value);
|
|
1234
|
+
if (!fs5.existsSync(resolved)) return `File not found: ${value}`;
|
|
1163
1235
|
return true;
|
|
1164
1236
|
}
|
|
1165
1237
|
},
|
|
@@ -1179,11 +1251,11 @@ async function pushCommand(options) {
|
|
|
1179
1251
|
if (!envFile) {
|
|
1180
1252
|
envFile = ".env";
|
|
1181
1253
|
}
|
|
1182
|
-
const envFilePath =
|
|
1183
|
-
if (!
|
|
1254
|
+
const envFilePath = path5.resolve(process.cwd(), envFile);
|
|
1255
|
+
if (!fs5.existsSync(envFilePath)) {
|
|
1184
1256
|
throw new Error(`File not found: ${envFile}`);
|
|
1185
1257
|
}
|
|
1186
|
-
const content =
|
|
1258
|
+
const content = fs5.readFileSync(envFilePath, "utf-8");
|
|
1187
1259
|
if (content.trim().length === 0) {
|
|
1188
1260
|
throw new Error(`File is empty: ${envFile}`);
|
|
1189
1261
|
}
|
|
@@ -1191,17 +1263,17 @@ async function pushCommand(options) {
|
|
|
1191
1263
|
const trimmed = line.trim();
|
|
1192
1264
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1193
1265
|
});
|
|
1194
|
-
console.log(`File: ${
|
|
1195
|
-
console.log(`Environment: ${
|
|
1196
|
-
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())}`);
|
|
1197
1269
|
const repoFullName = getCurrentRepoFullName();
|
|
1198
|
-
console.log(`Repository: ${
|
|
1270
|
+
console.log(`Repository: ${pc6.cyan(repoFullName)}`);
|
|
1199
1271
|
if (!options.yes) {
|
|
1200
1272
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1201
1273
|
if (!isInteractive3) {
|
|
1202
1274
|
throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
1203
1275
|
}
|
|
1204
|
-
const { confirm } = await
|
|
1276
|
+
const { confirm } = await prompts5(
|
|
1205
1277
|
{
|
|
1206
1278
|
type: "confirm",
|
|
1207
1279
|
name: "confirm",
|
|
@@ -1215,7 +1287,7 @@ async function pushCommand(options) {
|
|
|
1215
1287
|
}
|
|
1216
1288
|
);
|
|
1217
1289
|
if (!confirm) {
|
|
1218
|
-
console.log(
|
|
1290
|
+
console.log(pc6.yellow("Push aborted."));
|
|
1219
1291
|
return;
|
|
1220
1292
|
}
|
|
1221
1293
|
}
|
|
@@ -1227,13 +1299,13 @@ async function pushCommand(options) {
|
|
|
1227
1299
|
});
|
|
1228
1300
|
console.log("\nUploading secrets...");
|
|
1229
1301
|
const response = await pushSecrets(repoFullName, environment, content, accessToken);
|
|
1230
|
-
console.log(
|
|
1302
|
+
console.log(pc6.green("\n\u2713 " + response.message));
|
|
1231
1303
|
if (response.stats) {
|
|
1232
1304
|
const { created, updated, deleted } = response.stats;
|
|
1233
1305
|
const parts = [];
|
|
1234
|
-
if (created > 0) parts.push(
|
|
1235
|
-
if (updated > 0) parts.push(
|
|
1236
|
-
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`));
|
|
1237
1309
|
if (parts.length > 0) {
|
|
1238
1310
|
console.log(`Stats: ${parts.join(", ")}`);
|
|
1239
1311
|
}
|
|
@@ -1242,7 +1314,7 @@ async function pushCommand(options) {
|
|
|
1242
1314
|
Your secrets are now encrypted and stored securely.`);
|
|
1243
1315
|
const dashboardLink = `https://www.keyway.sh/dashboard/vaults/${repoFullName}`;
|
|
1244
1316
|
console.log(`
|
|
1245
|
-
${
|
|
1317
|
+
${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
|
|
1246
1318
|
await shutdownAnalytics();
|
|
1247
1319
|
} catch (error) {
|
|
1248
1320
|
let message;
|
|
@@ -1255,7 +1327,7 @@ ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
|
|
|
1255
1327
|
const availableEnvs = envNotFoundMatch[2];
|
|
1256
1328
|
message = `Environment '${requestedEnv}' does not exist in this vault.`;
|
|
1257
1329
|
hint = `Available environments: ${availableEnvs}
|
|
1258
|
-
Use ${
|
|
1330
|
+
Use ${pc6.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1259
1331
|
}
|
|
1260
1332
|
if (error.statusCode === 403 && (error.upgradeUrl || message.toLowerCase().includes("read-only"))) {
|
|
1261
1333
|
const upgradeMessage = message.toLowerCase().includes("read-only") ? "This vault is read-only on your current plan." : message;
|
|
@@ -1278,10 +1350,10 @@ Use ${pc5.cyan(`keyway push --env <environment>`)} to specify one, or create '${
|
|
|
1278
1350
|
error: message
|
|
1279
1351
|
});
|
|
1280
1352
|
await shutdownAnalytics();
|
|
1281
|
-
console.error(
|
|
1353
|
+
console.error(pc6.red(`
|
|
1282
1354
|
\u2717 ${message}`));
|
|
1283
1355
|
if (hint) {
|
|
1284
|
-
console.error(
|
|
1356
|
+
console.error(pc6.gray(`
|
|
1285
1357
|
${hint}`));
|
|
1286
1358
|
}
|
|
1287
1359
|
process.exit(1);
|
|
@@ -1316,7 +1388,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1316
1388
|
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1317
1389
|
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1318
1390
|
console.log("");
|
|
1319
|
-
const { shouldProceed } = await
|
|
1391
|
+
const { shouldProceed } = await prompts6({
|
|
1320
1392
|
type: "confirm",
|
|
1321
1393
|
name: "shouldProceed",
|
|
1322
1394
|
message: "Open browser to sign in?",
|
|
@@ -1326,8 +1398,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1326
1398
|
throw new Error('Setup required. Run "keyway init" when ready.');
|
|
1327
1399
|
}
|
|
1328
1400
|
await openUrl(deviceStart.verificationUriComplete);
|
|
1329
|
-
console.log(
|
|
1330
|
-
console.log(
|
|
1401
|
+
console.log(pc7.blue("\u23F3 Waiting for authorization..."));
|
|
1402
|
+
console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1331
1403
|
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1332
1404
|
const startTime = Date.now();
|
|
1333
1405
|
let accessToken = null;
|
|
@@ -1342,7 +1414,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1342
1414
|
githubLogin: result.githubLogin,
|
|
1343
1415
|
expiresAt: result.expiresAt
|
|
1344
1416
|
});
|
|
1345
|
-
console.log(
|
|
1417
|
+
console.log(pc7.green("\u2713 Signed in!"));
|
|
1346
1418
|
if (result.githubLogin) {
|
|
1347
1419
|
identifyUser(result.githubLogin, {
|
|
1348
1420
|
github_username: result.githubLogin,
|
|
@@ -1352,7 +1424,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1352
1424
|
break;
|
|
1353
1425
|
}
|
|
1354
1426
|
consecutiveErrors = 0;
|
|
1355
|
-
process.stdout.write(
|
|
1427
|
+
process.stdout.write(pc7.gray("."));
|
|
1356
1428
|
} catch (error) {
|
|
1357
1429
|
consecutiveErrors++;
|
|
1358
1430
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1363,35 +1435,35 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1363
1435
|
}
|
|
1364
1436
|
if (!accessToken) {
|
|
1365
1437
|
console.log("");
|
|
1366
|
-
console.log(
|
|
1438
|
+
console.log(pc7.yellow("\u26A0 Timed out waiting for sign in."));
|
|
1367
1439
|
throw new Error("Sign in timed out. Please try again.");
|
|
1368
1440
|
}
|
|
1369
1441
|
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1370
1442
|
if (installStatus.installed) {
|
|
1371
|
-
console.log(
|
|
1443
|
+
console.log(pc7.green("\u2713 GitHub App installed"));
|
|
1372
1444
|
console.log("");
|
|
1373
1445
|
return accessToken;
|
|
1374
1446
|
}
|
|
1375
1447
|
console.log("");
|
|
1376
|
-
console.log(
|
|
1377
|
-
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."));
|
|
1378
1450
|
console.log("");
|
|
1379
|
-
const { shouldInstall } = await
|
|
1451
|
+
const { shouldInstall } = await prompts6({
|
|
1380
1452
|
type: "confirm",
|
|
1381
1453
|
name: "shouldInstall",
|
|
1382
1454
|
message: "Open browser to install GitHub App?",
|
|
1383
1455
|
initial: true
|
|
1384
1456
|
});
|
|
1385
1457
|
if (!shouldInstall) {
|
|
1386
|
-
console.log(
|
|
1458
|
+
console.log(pc7.gray(`
|
|
1387
1459
|
Install later: ${installUrl}`));
|
|
1388
1460
|
throw new Error("GitHub App installation required.");
|
|
1389
1461
|
}
|
|
1390
1462
|
await openUrl(installUrl);
|
|
1391
|
-
console.log(
|
|
1392
|
-
console.log(
|
|
1393
|
-
console.log(
|
|
1394
|
-
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"));
|
|
1395
1467
|
const installStartTime = Date.now();
|
|
1396
1468
|
consecutiveErrors = 0;
|
|
1397
1469
|
while (Date.now() - installStartTime < POLL_TIMEOUT_MS) {
|
|
@@ -1399,12 +1471,12 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1399
1471
|
try {
|
|
1400
1472
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1401
1473
|
if (pollStatus.installed) {
|
|
1402
|
-
console.log(
|
|
1474
|
+
console.log(pc7.green("\u2713 GitHub App installed!"));
|
|
1403
1475
|
console.log("");
|
|
1404
1476
|
return accessToken;
|
|
1405
1477
|
}
|
|
1406
1478
|
consecutiveErrors = 0;
|
|
1407
|
-
process.stdout.write(
|
|
1479
|
+
process.stdout.write(pc7.gray("."));
|
|
1408
1480
|
} catch (error) {
|
|
1409
1481
|
consecutiveErrors++;
|
|
1410
1482
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1414,8 +1486,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1414
1486
|
}
|
|
1415
1487
|
}
|
|
1416
1488
|
console.log("");
|
|
1417
|
-
console.log(
|
|
1418
|
-
console.log(
|
|
1489
|
+
console.log(pc7.yellow("\u26A0 Timed out waiting for installation."));
|
|
1490
|
+
console.log(pc7.gray(` Install the GitHub App: ${installUrl}`));
|
|
1419
1491
|
throw new Error("GitHub App installation timed out.");
|
|
1420
1492
|
}
|
|
1421
1493
|
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
@@ -1425,7 +1497,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1425
1497
|
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1426
1498
|
} catch (error) {
|
|
1427
1499
|
if (error instanceof APIError && error.statusCode === 401) {
|
|
1428
|
-
console.log(
|
|
1500
|
+
console.log(pc7.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1429
1501
|
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1430
1502
|
clearAuth2();
|
|
1431
1503
|
return null;
|
|
@@ -1436,29 +1508,29 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1436
1508
|
return accessToken;
|
|
1437
1509
|
}
|
|
1438
1510
|
console.log("");
|
|
1439
|
-
console.log(
|
|
1511
|
+
console.log(pc7.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1440
1512
|
console.log("");
|
|
1441
|
-
console.log(
|
|
1442
|
-
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)."));
|
|
1443
1515
|
console.log("");
|
|
1444
1516
|
if (!isInteractive()) {
|
|
1445
|
-
console.log(
|
|
1517
|
+
console.log(pc7.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1446
1518
|
throw new Error("GitHub App installation required.");
|
|
1447
1519
|
}
|
|
1448
|
-
const { shouldInstall } = await
|
|
1520
|
+
const { shouldInstall } = await prompts6({
|
|
1449
1521
|
type: "confirm",
|
|
1450
1522
|
name: "shouldInstall",
|
|
1451
1523
|
message: "Open browser to install Keyway GitHub App?",
|
|
1452
1524
|
initial: true
|
|
1453
1525
|
});
|
|
1454
1526
|
if (!shouldInstall) {
|
|
1455
|
-
console.log(
|
|
1527
|
+
console.log(pc7.gray(`
|
|
1456
1528
|
You can install later: ${status.installUrl}`));
|
|
1457
1529
|
throw new Error("GitHub App installation required.");
|
|
1458
1530
|
}
|
|
1459
1531
|
await openUrl(status.installUrl);
|
|
1460
|
-
console.log(
|
|
1461
|
-
console.log(
|
|
1532
|
+
console.log(pc7.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1533
|
+
console.log(pc7.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1462
1534
|
const startTime = Date.now();
|
|
1463
1535
|
let consecutiveErrors = 0;
|
|
1464
1536
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
@@ -1466,12 +1538,12 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1466
1538
|
try {
|
|
1467
1539
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1468
1540
|
if (pollStatus.installed) {
|
|
1469
|
-
console.log(
|
|
1541
|
+
console.log(pc7.green("\u2713 GitHub App installed!"));
|
|
1470
1542
|
console.log("");
|
|
1471
1543
|
return accessToken;
|
|
1472
1544
|
}
|
|
1473
1545
|
consecutiveErrors = 0;
|
|
1474
|
-
process.stdout.write(
|
|
1546
|
+
process.stdout.write(pc7.gray("."));
|
|
1475
1547
|
} catch (error) {
|
|
1476
1548
|
consecutiveErrors++;
|
|
1477
1549
|
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
@@ -1481,35 +1553,35 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1481
1553
|
}
|
|
1482
1554
|
}
|
|
1483
1555
|
console.log("");
|
|
1484
|
-
console.log(
|
|
1485
|
-
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}`));
|
|
1486
1558
|
throw new Error("GitHub App installation timed out.");
|
|
1487
1559
|
}
|
|
1488
1560
|
async function initCommand(options = {}) {
|
|
1489
1561
|
try {
|
|
1490
1562
|
const repoFullName = getCurrentRepoFullName();
|
|
1491
1563
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1492
|
-
console.log(
|
|
1493
|
-
console.log(` ${
|
|
1564
|
+
console.log(pc7.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1565
|
+
console.log(` ${pc7.gray("Repository:")} ${pc7.white(repoFullName)}`);
|
|
1494
1566
|
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1495
1567
|
allowPrompt: options.loginPrompt !== false
|
|
1496
1568
|
});
|
|
1497
1569
|
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1498
1570
|
const vaultExists = await checkVaultExists(accessToken, repoFullName);
|
|
1499
1571
|
if (vaultExists) {
|
|
1500
|
-
console.log(
|
|
1501
|
-
console.log(` ${
|
|
1502
|
-
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)}`);
|
|
1503
1575
|
console.log("");
|
|
1504
1576
|
await shutdownAnalytics();
|
|
1505
1577
|
return;
|
|
1506
1578
|
}
|
|
1507
1579
|
await initVault(repoFullName, accessToken);
|
|
1508
|
-
console.log(
|
|
1580
|
+
console.log(pc7.green("\u2713 Vault created!"));
|
|
1509
1581
|
try {
|
|
1510
1582
|
const badgeAdded = await addBadgeToReadme(true);
|
|
1511
1583
|
if (badgeAdded) {
|
|
1512
|
-
console.log(
|
|
1584
|
+
console.log(pc7.green("\u2713 Badge added to README.md"));
|
|
1513
1585
|
}
|
|
1514
1586
|
} catch {
|
|
1515
1587
|
}
|
|
@@ -1517,9 +1589,9 @@ async function initCommand(options = {}) {
|
|
|
1517
1589
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1518
1590
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1519
1591
|
if (envCandidates.length > 0 && isInteractive2) {
|
|
1520
|
-
console.log(
|
|
1592
|
+
console.log(pc7.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1521
1593
|
`));
|
|
1522
|
-
const { shouldPush } = await
|
|
1594
|
+
const { shouldPush } = await prompts6({
|
|
1523
1595
|
type: "confirm",
|
|
1524
1596
|
name: "shouldPush",
|
|
1525
1597
|
message: "Push secrets now?",
|
|
@@ -1531,25 +1603,36 @@ async function initCommand(options = {}) {
|
|
|
1531
1603
|
return;
|
|
1532
1604
|
}
|
|
1533
1605
|
}
|
|
1534
|
-
console.log(
|
|
1606
|
+
console.log(pc7.dim("\u2500".repeat(50)));
|
|
1535
1607
|
console.log("");
|
|
1536
1608
|
if (envCandidates.length === 0) {
|
|
1537
|
-
|
|
1538
|
-
|
|
1609
|
+
if (isInteractive2) {
|
|
1610
|
+
const created = await promptCreateEnvFile();
|
|
1611
|
+
if (created) {
|
|
1612
|
+
console.log(` Add your variables and run ${pc7.cyan("keyway push")}
|
|
1539
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
|
+
}
|
|
1540
1623
|
} else {
|
|
1541
|
-
console.log(` ${
|
|
1624
|
+
console.log(` ${pc7.yellow("\u2192")} Run ${pc7.cyan("keyway push")} to sync your secrets
|
|
1542
1625
|
`);
|
|
1543
1626
|
}
|
|
1544
|
-
console.log(` ${
|
|
1627
|
+
console.log(` ${pc7.blue("\u2394")} Dashboard: ${pc7.underline(dashboardLink)}`);
|
|
1545
1628
|
console.log("");
|
|
1546
1629
|
await shutdownAnalytics();
|
|
1547
1630
|
} catch (error) {
|
|
1548
1631
|
if (error instanceof APIError) {
|
|
1549
1632
|
if (error.statusCode === 409) {
|
|
1550
|
-
console.log(
|
|
1551
|
-
console.log(` ${
|
|
1552
|
-
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()}`)}`);
|
|
1553
1636
|
console.log("");
|
|
1554
1637
|
await shutdownAnalytics();
|
|
1555
1638
|
return;
|
|
@@ -1567,25 +1650,25 @@ async function initCommand(options = {}) {
|
|
|
1567
1650
|
error: message
|
|
1568
1651
|
});
|
|
1569
1652
|
await shutdownAnalytics();
|
|
1570
|
-
console.error(
|
|
1653
|
+
console.error(pc7.red(`
|
|
1571
1654
|
\u2717 ${message}`));
|
|
1572
1655
|
process.exit(1);
|
|
1573
1656
|
}
|
|
1574
1657
|
}
|
|
1575
1658
|
|
|
1576
1659
|
// src/cmds/pull.ts
|
|
1577
|
-
import
|
|
1578
|
-
import
|
|
1579
|
-
import
|
|
1580
|
-
import
|
|
1660
|
+
import pc8 from "picocolors";
|
|
1661
|
+
import fs6 from "fs";
|
|
1662
|
+
import path6 from "path";
|
|
1663
|
+
import prompts7 from "prompts";
|
|
1581
1664
|
async function pullCommand(options) {
|
|
1582
1665
|
try {
|
|
1583
1666
|
const environment = options.env || "development";
|
|
1584
1667
|
const envFile = options.file || ".env";
|
|
1585
|
-
console.log(
|
|
1586
|
-
console.log(`Environment: ${
|
|
1668
|
+
console.log(pc8.blue("\u{1F510} Pulling secrets from Keyway...\n"));
|
|
1669
|
+
console.log(`Environment: ${pc8.cyan(environment)}`);
|
|
1587
1670
|
const repoFullName = getCurrentRepoFullName();
|
|
1588
|
-
console.log(`Repository: ${
|
|
1671
|
+
console.log(`Repository: ${pc8.cyan(repoFullName)}`);
|
|
1589
1672
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1590
1673
|
trackEvent(AnalyticsEvents.CLI_PULL, {
|
|
1591
1674
|
repoFullName,
|
|
@@ -1593,16 +1676,16 @@ async function pullCommand(options) {
|
|
|
1593
1676
|
});
|
|
1594
1677
|
console.log("\nDownloading secrets...");
|
|
1595
1678
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1596
|
-
const envFilePath =
|
|
1597
|
-
if (
|
|
1679
|
+
const envFilePath = path6.resolve(process.cwd(), envFile);
|
|
1680
|
+
if (fs6.existsSync(envFilePath)) {
|
|
1598
1681
|
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1599
1682
|
if (options.yes) {
|
|
1600
|
-
console.log(
|
|
1683
|
+
console.log(pc8.yellow(`
|
|
1601
1684
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1602
1685
|
} else if (!isInteractive2) {
|
|
1603
1686
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
1604
1687
|
} else {
|
|
1605
|
-
const { confirm } = await
|
|
1688
|
+
const { confirm } = await prompts7(
|
|
1606
1689
|
{
|
|
1607
1690
|
type: "confirm",
|
|
1608
1691
|
name: "confirm",
|
|
@@ -1616,21 +1699,21 @@ async function pullCommand(options) {
|
|
|
1616
1699
|
}
|
|
1617
1700
|
);
|
|
1618
1701
|
if (!confirm) {
|
|
1619
|
-
console.log(
|
|
1702
|
+
console.log(pc8.yellow("Pull aborted."));
|
|
1620
1703
|
return;
|
|
1621
1704
|
}
|
|
1622
1705
|
}
|
|
1623
1706
|
}
|
|
1624
|
-
|
|
1707
|
+
fs6.writeFileSync(envFilePath, response.content, "utf-8");
|
|
1625
1708
|
const lines = response.content.split("\n").filter((line) => {
|
|
1626
1709
|
const trimmed = line.trim();
|
|
1627
1710
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1628
1711
|
});
|
|
1629
|
-
console.log(
|
|
1712
|
+
console.log(pc8.green(`
|
|
1630
1713
|
\u2713 Secrets downloaded successfully`));
|
|
1631
1714
|
console.log(`
|
|
1632
|
-
File: ${
|
|
1633
|
-
console.log(`Variables: ${
|
|
1715
|
+
File: ${pc8.cyan(envFile)}`);
|
|
1716
|
+
console.log(`Variables: ${pc8.cyan(lines.length.toString())}`);
|
|
1634
1717
|
await shutdownAnalytics();
|
|
1635
1718
|
} catch (error) {
|
|
1636
1719
|
const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
|
|
@@ -1639,14 +1722,14 @@ File: ${pc7.cyan(envFile)}`);
|
|
|
1639
1722
|
error: message
|
|
1640
1723
|
});
|
|
1641
1724
|
await shutdownAnalytics();
|
|
1642
|
-
console.error(
|
|
1725
|
+
console.error(pc8.red(`
|
|
1643
1726
|
\u2717 ${message}`));
|
|
1644
1727
|
process.exit(1);
|
|
1645
1728
|
}
|
|
1646
1729
|
}
|
|
1647
1730
|
|
|
1648
1731
|
// src/cmds/doctor.ts
|
|
1649
|
-
import
|
|
1732
|
+
import pc9 from "picocolors";
|
|
1650
1733
|
|
|
1651
1734
|
// src/core/doctor.ts
|
|
1652
1735
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -1900,9 +1983,9 @@ async function runAllChecks(options = {}) {
|
|
|
1900
1983
|
// src/cmds/doctor.ts
|
|
1901
1984
|
function formatSummary(results) {
|
|
1902
1985
|
const parts = [
|
|
1903
|
-
|
|
1904
|
-
results.summary.warn > 0 ?
|
|
1905
|
-
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
|
|
1906
1989
|
].filter(Boolean);
|
|
1907
1990
|
return parts.join(", ");
|
|
1908
1991
|
}
|
|
@@ -1919,20 +2002,20 @@ async function doctorCommand(options = {}) {
|
|
|
1919
2002
|
process.stdout.write(JSON.stringify(results, null, 0) + "\n");
|
|
1920
2003
|
process.exit(results.exitCode);
|
|
1921
2004
|
}
|
|
1922
|
-
console.log(
|
|
2005
|
+
console.log(pc9.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
|
|
1923
2006
|
results.checks.forEach((check) => {
|
|
1924
|
-
const icon = check.status === "pass" ?
|
|
1925
|
-
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}`) : "";
|
|
1926
2009
|
console.log(` ${icon} ${check.name}${detail}`);
|
|
1927
2010
|
});
|
|
1928
2011
|
console.log(`
|
|
1929
2012
|
Summary: ${formatSummary(results)}`);
|
|
1930
2013
|
if (results.summary.fail > 0) {
|
|
1931
|
-
console.log(
|
|
2014
|
+
console.log(pc9.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
|
|
1932
2015
|
} else if (results.summary.warn > 0) {
|
|
1933
|
-
console.log(
|
|
2016
|
+
console.log(pc9.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
|
|
1934
2017
|
} else {
|
|
1935
|
-
console.log(
|
|
2018
|
+
console.log(pc9.green("\u2728 All checks passed! Your environment is ready for Keyway."));
|
|
1936
2019
|
}
|
|
1937
2020
|
process.exit(results.exitCode);
|
|
1938
2021
|
} catch (error) {
|
|
@@ -1953,7 +2036,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1953
2036
|
};
|
|
1954
2037
|
process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
|
|
1955
2038
|
} else {
|
|
1956
|
-
console.error(
|
|
2039
|
+
console.error(pc9.red(`
|
|
1957
2040
|
\u2717 ${message}`));
|
|
1958
2041
|
}
|
|
1959
2042
|
process.exit(1);
|
|
@@ -1961,8 +2044,8 @@ Summary: ${formatSummary(results)}`);
|
|
|
1961
2044
|
}
|
|
1962
2045
|
|
|
1963
2046
|
// src/cmds/connect.ts
|
|
1964
|
-
import
|
|
1965
|
-
import
|
|
2047
|
+
import pc10 from "picocolors";
|
|
2048
|
+
import prompts8 from "prompts";
|
|
1966
2049
|
var TOKEN_AUTH_PROVIDERS = ["railway"];
|
|
1967
2050
|
function getTokenCreationUrl(provider) {
|
|
1968
2051
|
switch (provider) {
|
|
@@ -1975,37 +2058,37 @@ function getTokenCreationUrl(provider) {
|
|
|
1975
2058
|
async function connectWithTokenFlow(accessToken, provider, displayName) {
|
|
1976
2059
|
const tokenUrl = getTokenCreationUrl(provider);
|
|
1977
2060
|
if (provider === "railway") {
|
|
1978
|
-
console.log(
|
|
1979
|
-
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.`));
|
|
1980
2063
|
}
|
|
1981
2064
|
await openUrl(tokenUrl);
|
|
1982
|
-
const { token } = await
|
|
2065
|
+
const { token } = await prompts8({
|
|
1983
2066
|
type: "password",
|
|
1984
2067
|
name: "token",
|
|
1985
2068
|
message: `${displayName} API Token:`
|
|
1986
2069
|
});
|
|
1987
2070
|
if (!token) {
|
|
1988
|
-
console.log(
|
|
2071
|
+
console.log(pc10.gray("Cancelled."));
|
|
1989
2072
|
return false;
|
|
1990
2073
|
}
|
|
1991
|
-
console.log(
|
|
2074
|
+
console.log(pc10.gray("\nValidating token..."));
|
|
1992
2075
|
try {
|
|
1993
2076
|
const result = await connectWithToken(accessToken, provider, token);
|
|
1994
2077
|
if (result.success) {
|
|
1995
|
-
console.log(
|
|
2078
|
+
console.log(pc10.green(`
|
|
1996
2079
|
\u2713 Connected to ${displayName}!`));
|
|
1997
|
-
console.log(
|
|
2080
|
+
console.log(pc10.gray(` Account: ${result.user.username}`));
|
|
1998
2081
|
if (result.user.teamName) {
|
|
1999
|
-
console.log(
|
|
2082
|
+
console.log(pc10.gray(` Team: ${result.user.teamName}`));
|
|
2000
2083
|
}
|
|
2001
2084
|
return true;
|
|
2002
2085
|
} else {
|
|
2003
|
-
console.log(
|
|
2086
|
+
console.log(pc10.red("\n\u2717 Connection failed."));
|
|
2004
2087
|
return false;
|
|
2005
2088
|
}
|
|
2006
2089
|
} catch (error) {
|
|
2007
2090
|
const message = error instanceof Error ? error.message : "Token validation failed";
|
|
2008
|
-
console.log(
|
|
2091
|
+
console.log(pc10.red(`
|
|
2009
2092
|
\u2717 ${message}`));
|
|
2010
2093
|
return false;
|
|
2011
2094
|
}
|
|
@@ -2014,7 +2097,7 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
|
2014
2097
|
const authUrl = getProviderAuthUrl(provider, accessToken);
|
|
2015
2098
|
const startTime = /* @__PURE__ */ new Date();
|
|
2016
2099
|
await openUrl(authUrl);
|
|
2017
|
-
console.log(
|
|
2100
|
+
console.log(pc10.gray("Waiting for authorization..."));
|
|
2018
2101
|
const maxAttempts = 60;
|
|
2019
2102
|
let attempts = 0;
|
|
2020
2103
|
while (attempts < maxAttempts) {
|
|
@@ -2026,15 +2109,15 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
|
2026
2109
|
(c) => c.provider === provider && new Date(c.createdAt) > startTime
|
|
2027
2110
|
);
|
|
2028
2111
|
if (newConn) {
|
|
2029
|
-
console.log(
|
|
2112
|
+
console.log(pc10.green(`
|
|
2030
2113
|
\u2713 Connected to ${displayName}!`));
|
|
2031
2114
|
return true;
|
|
2032
2115
|
}
|
|
2033
2116
|
} catch {
|
|
2034
2117
|
}
|
|
2035
2118
|
}
|
|
2036
|
-
console.log(
|
|
2037
|
-
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."));
|
|
2038
2121
|
return false;
|
|
2039
2122
|
}
|
|
2040
2123
|
async function connectCommand(provider, options = {}) {
|
|
@@ -2044,30 +2127,30 @@ async function connectCommand(provider, options = {}) {
|
|
|
2044
2127
|
const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
|
|
2045
2128
|
if (!providerInfo) {
|
|
2046
2129
|
const available = providers.map((p) => p.name).join(", ");
|
|
2047
|
-
console.error(
|
|
2048
|
-
console.log(
|
|
2130
|
+
console.error(pc10.red(`Unknown provider: ${provider}`));
|
|
2131
|
+
console.log(pc10.gray(`Available providers: ${available || "none"}`));
|
|
2049
2132
|
process.exit(1);
|
|
2050
2133
|
}
|
|
2051
2134
|
if (!providerInfo.configured) {
|
|
2052
|
-
console.error(
|
|
2053
|
-
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."));
|
|
2054
2137
|
process.exit(1);
|
|
2055
2138
|
}
|
|
2056
2139
|
const { connections } = await getConnections(accessToken);
|
|
2057
2140
|
const existingConnection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2058
2141
|
if (existingConnection) {
|
|
2059
|
-
const { reconnect } = await
|
|
2142
|
+
const { reconnect } = await prompts8({
|
|
2060
2143
|
type: "confirm",
|
|
2061
2144
|
name: "reconnect",
|
|
2062
2145
|
message: `You're already connected to ${providerInfo.displayName}. Reconnect?`,
|
|
2063
2146
|
initial: false
|
|
2064
2147
|
});
|
|
2065
2148
|
if (!reconnect) {
|
|
2066
|
-
console.log(
|
|
2149
|
+
console.log(pc10.gray("Keeping existing connection."));
|
|
2067
2150
|
return;
|
|
2068
2151
|
}
|
|
2069
2152
|
}
|
|
2070
|
-
console.log(
|
|
2153
|
+
console.log(pc10.blue(`
|
|
2071
2154
|
Connecting to ${providerInfo.displayName}...
|
|
2072
2155
|
`));
|
|
2073
2156
|
let connected = false;
|
|
@@ -2086,7 +2169,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
2086
2169
|
command: "connect",
|
|
2087
2170
|
error: truncateMessage(message)
|
|
2088
2171
|
});
|
|
2089
|
-
console.error(
|
|
2172
|
+
console.error(pc10.red(`
|
|
2090
2173
|
\u2717 ${message}`));
|
|
2091
2174
|
process.exit(1);
|
|
2092
2175
|
}
|
|
@@ -2096,24 +2179,24 @@ async function connectionsCommand(options = {}) {
|
|
|
2096
2179
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2097
2180
|
const { connections } = await getConnections(accessToken);
|
|
2098
2181
|
if (connections.length === 0) {
|
|
2099
|
-
console.log(
|
|
2100
|
-
console.log(
|
|
2101
|
-
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"));
|
|
2102
2185
|
return;
|
|
2103
2186
|
}
|
|
2104
|
-
console.log(
|
|
2187
|
+
console.log(pc10.blue("\n\u{1F4E1} Provider Connections\n"));
|
|
2105
2188
|
for (const conn of connections) {
|
|
2106
2189
|
const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
|
|
2107
|
-
const teamInfo = conn.providerTeamId ?
|
|
2190
|
+
const teamInfo = conn.providerTeamId ? pc10.gray(` (Team: ${conn.providerTeamId})`) : "";
|
|
2108
2191
|
const date = new Date(conn.createdAt).toLocaleDateString();
|
|
2109
|
-
console.log(` ${
|
|
2110
|
-
console.log(
|
|
2111
|
-
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}`));
|
|
2112
2195
|
console.log("");
|
|
2113
2196
|
}
|
|
2114
2197
|
} catch (error) {
|
|
2115
2198
|
const message = error instanceof Error ? error.message : "Failed to list connections";
|
|
2116
|
-
console.error(
|
|
2199
|
+
console.error(pc10.red(`
|
|
2117
2200
|
\u2717 ${message}`));
|
|
2118
2201
|
process.exit(1);
|
|
2119
2202
|
}
|
|
@@ -2124,22 +2207,22 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2124
2207
|
const { connections } = await getConnections(accessToken);
|
|
2125
2208
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2126
2209
|
if (!connection) {
|
|
2127
|
-
console.log(
|
|
2210
|
+
console.log(pc10.gray(`No connection found for provider: ${provider}`));
|
|
2128
2211
|
return;
|
|
2129
2212
|
}
|
|
2130
2213
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2131
|
-
const { confirm } = await
|
|
2214
|
+
const { confirm } = await prompts8({
|
|
2132
2215
|
type: "confirm",
|
|
2133
2216
|
name: "confirm",
|
|
2134
2217
|
message: `Disconnect from ${providerName}?`,
|
|
2135
2218
|
initial: false
|
|
2136
2219
|
});
|
|
2137
2220
|
if (!confirm) {
|
|
2138
|
-
console.log(
|
|
2221
|
+
console.log(pc10.gray("Cancelled."));
|
|
2139
2222
|
return;
|
|
2140
2223
|
}
|
|
2141
2224
|
await deleteConnection(accessToken, connection.id);
|
|
2142
|
-
console.log(
|
|
2225
|
+
console.log(pc10.green(`
|
|
2143
2226
|
\u2713 Disconnected from ${providerName}`));
|
|
2144
2227
|
trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
|
|
2145
2228
|
provider: provider.toLowerCase()
|
|
@@ -2150,15 +2233,15 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2150
2233
|
command: "disconnect",
|
|
2151
2234
|
error: truncateMessage(message)
|
|
2152
2235
|
});
|
|
2153
|
-
console.error(
|
|
2236
|
+
console.error(pc10.red(`
|
|
2154
2237
|
\u2717 ${message}`));
|
|
2155
2238
|
process.exit(1);
|
|
2156
2239
|
}
|
|
2157
2240
|
}
|
|
2158
2241
|
|
|
2159
2242
|
// src/cmds/sync.ts
|
|
2160
|
-
import
|
|
2161
|
-
import
|
|
2243
|
+
import pc11 from "picocolors";
|
|
2244
|
+
import prompts9 from "prompts";
|
|
2162
2245
|
function mapToVercelEnvironment(keywayEnv) {
|
|
2163
2246
|
const mapping = {
|
|
2164
2247
|
production: "production",
|
|
@@ -2202,36 +2285,36 @@ function mapToProviderEnvironment(provider, keywayEnv) {
|
|
|
2202
2285
|
function displayDiffSummary(diff, providerName) {
|
|
2203
2286
|
const totalDiff = diff.onlyInKeyway.length + diff.onlyInProvider.length + diff.different.length;
|
|
2204
2287
|
if (totalDiff === 0 && diff.same.length > 0) {
|
|
2205
|
-
console.log(
|
|
2288
|
+
console.log(pc11.green(`
|
|
2206
2289
|
\u2713 Already in sync (${diff.same.length} secrets)`));
|
|
2207
2290
|
return;
|
|
2208
2291
|
}
|
|
2209
|
-
console.log(
|
|
2210
|
-
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
|
|
2211
2294
|
`));
|
|
2212
2295
|
if (diff.onlyInKeyway.length > 0) {
|
|
2213
|
-
console.log(
|
|
2214
|
-
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}`)));
|
|
2215
2298
|
if (diff.onlyInKeyway.length > 3) {
|
|
2216
|
-
console.log(
|
|
2299
|
+
console.log(pc11.gray(` ... and ${diff.onlyInKeyway.length - 3} more`));
|
|
2217
2300
|
}
|
|
2218
2301
|
}
|
|
2219
2302
|
if (diff.onlyInProvider.length > 0) {
|
|
2220
|
-
console.log(
|
|
2221
|
-
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}`)));
|
|
2222
2305
|
if (diff.onlyInProvider.length > 3) {
|
|
2223
|
-
console.log(
|
|
2306
|
+
console.log(pc11.gray(` ... and ${diff.onlyInProvider.length - 3} more`));
|
|
2224
2307
|
}
|
|
2225
2308
|
}
|
|
2226
2309
|
if (diff.different.length > 0) {
|
|
2227
|
-
console.log(
|
|
2228
|
-
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}`)));
|
|
2229
2312
|
if (diff.different.length > 3) {
|
|
2230
|
-
console.log(
|
|
2313
|
+
console.log(pc11.gray(` ... and ${diff.different.length - 3} more`));
|
|
2231
2314
|
}
|
|
2232
2315
|
}
|
|
2233
2316
|
if (diff.same.length > 0) {
|
|
2234
|
-
console.log(
|
|
2317
|
+
console.log(pc11.gray(` = ${diff.same.length} identical`));
|
|
2235
2318
|
}
|
|
2236
2319
|
console.log("");
|
|
2237
2320
|
}
|
|
@@ -2278,25 +2361,25 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2278
2361
|
let title = displayName;
|
|
2279
2362
|
const badges = [];
|
|
2280
2363
|
if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
|
|
2281
|
-
badges.push(
|
|
2364
|
+
badges.push(pc11.green("\u2190 linked"));
|
|
2282
2365
|
} else if (p.name.toLowerCase() === repoName || p.serviceName?.toLowerCase() === repoName) {
|
|
2283
|
-
badges.push(
|
|
2366
|
+
badges.push(pc11.green("\u2190 same name"));
|
|
2284
2367
|
} else if (p.linkedRepo) {
|
|
2285
|
-
badges.push(
|
|
2368
|
+
badges.push(pc11.gray(`\u2192 ${p.linkedRepo}`));
|
|
2286
2369
|
}
|
|
2287
2370
|
if (badges.length > 0) {
|
|
2288
2371
|
title = `${displayName} ${badges.join(" ")}`;
|
|
2289
2372
|
}
|
|
2290
2373
|
return { title, value: p.id };
|
|
2291
2374
|
});
|
|
2292
|
-
const { projectChoice } = await
|
|
2375
|
+
const { projectChoice } = await prompts9({
|
|
2293
2376
|
type: "select",
|
|
2294
2377
|
name: "projectChoice",
|
|
2295
2378
|
message: "Select a project:",
|
|
2296
2379
|
choices
|
|
2297
2380
|
});
|
|
2298
2381
|
if (!projectChoice) {
|
|
2299
|
-
console.log(
|
|
2382
|
+
console.log(pc11.gray("Cancelled."));
|
|
2300
2383
|
process.exit(0);
|
|
2301
2384
|
}
|
|
2302
2385
|
return projects.find((p) => p.id === projectChoice);
|
|
@@ -2304,40 +2387,40 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2304
2387
|
async function syncCommand(provider, options = {}) {
|
|
2305
2388
|
try {
|
|
2306
2389
|
if (options.pull && options.allowDelete) {
|
|
2307
|
-
console.error(
|
|
2308
|
-
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."));
|
|
2309
2392
|
process.exit(1);
|
|
2310
2393
|
}
|
|
2311
2394
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2312
2395
|
const repoFullName = detectGitRepo();
|
|
2313
2396
|
if (!repoFullName) {
|
|
2314
|
-
console.error(
|
|
2315
|
-
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."));
|
|
2316
2399
|
process.exit(1);
|
|
2317
2400
|
}
|
|
2318
|
-
console.log(
|
|
2401
|
+
console.log(pc11.gray(`Repository: ${repoFullName}`));
|
|
2319
2402
|
const vaultExists = await checkVaultExists(accessToken, repoFullName);
|
|
2320
2403
|
if (!vaultExists) {
|
|
2321
|
-
console.log(
|
|
2404
|
+
console.log(pc11.yellow(`
|
|
2322
2405
|
No vault found for ${repoFullName}.`));
|
|
2323
|
-
const { shouldCreate } = await
|
|
2406
|
+
const { shouldCreate } = await prompts9({
|
|
2324
2407
|
type: "confirm",
|
|
2325
2408
|
name: "shouldCreate",
|
|
2326
2409
|
message: "Create vault now?",
|
|
2327
2410
|
initial: true
|
|
2328
2411
|
});
|
|
2329
2412
|
if (!shouldCreate) {
|
|
2330
|
-
console.log(
|
|
2413
|
+
console.log(pc11.gray("Cancelled. Run `keyway init` to create a vault first."));
|
|
2331
2414
|
process.exit(0);
|
|
2332
2415
|
}
|
|
2333
|
-
console.log(
|
|
2416
|
+
console.log(pc11.gray("\nCreating vault..."));
|
|
2334
2417
|
try {
|
|
2335
2418
|
await initVault(repoFullName, accessToken);
|
|
2336
|
-
console.log(
|
|
2419
|
+
console.log(pc11.green(`\u2713 Vault created for ${repoFullName}
|
|
2337
2420
|
`));
|
|
2338
2421
|
} catch (error) {
|
|
2339
2422
|
const message = error instanceof Error ? error.message : "Failed to create vault";
|
|
2340
|
-
console.error(
|
|
2423
|
+
console.error(pc11.red(`
|
|
2341
2424
|
\u2717 ${message}`));
|
|
2342
2425
|
process.exit(1);
|
|
2343
2426
|
}
|
|
@@ -2346,16 +2429,16 @@ No vault found for ${repoFullName}.`));
|
|
|
2346
2429
|
let connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2347
2430
|
if (!connection) {
|
|
2348
2431
|
const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2349
|
-
console.log(
|
|
2432
|
+
console.log(pc11.yellow(`
|
|
2350
2433
|
Not connected to ${providerDisplayName}.`));
|
|
2351
|
-
const { shouldConnect } = await
|
|
2434
|
+
const { shouldConnect } = await prompts9({
|
|
2352
2435
|
type: "confirm",
|
|
2353
2436
|
name: "shouldConnect",
|
|
2354
2437
|
message: `Connect to ${providerDisplayName} now?`,
|
|
2355
2438
|
initial: true
|
|
2356
2439
|
});
|
|
2357
2440
|
if (!shouldConnect) {
|
|
2358
|
-
console.log(
|
|
2441
|
+
console.log(pc11.gray("Cancelled."));
|
|
2359
2442
|
process.exit(0);
|
|
2360
2443
|
}
|
|
2361
2444
|
await connectCommand(provider, { loginPrompt: false });
|
|
@@ -2363,7 +2446,7 @@ Not connected to ${providerDisplayName}.`));
|
|
|
2363
2446
|
connections = refreshed.connections;
|
|
2364
2447
|
connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2365
2448
|
if (!connection) {
|
|
2366
|
-
console.error(
|
|
2449
|
+
console.error(pc11.red(`
|
|
2367
2450
|
Connection to ${providerDisplayName} failed.`));
|
|
2368
2451
|
process.exit(1);
|
|
2369
2452
|
}
|
|
@@ -2371,7 +2454,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2371
2454
|
}
|
|
2372
2455
|
const { projects } = await getConnectionProjects(accessToken, connection.id);
|
|
2373
2456
|
if (projects.length === 0) {
|
|
2374
|
-
console.error(
|
|
2457
|
+
console.error(pc11.red(`No projects found in your ${provider} account.`));
|
|
2375
2458
|
process.exit(1);
|
|
2376
2459
|
}
|
|
2377
2460
|
let selectedProject;
|
|
@@ -2380,21 +2463,21 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2380
2463
|
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase() || p.serviceName?.toLowerCase() === options.project?.toLowerCase()
|
|
2381
2464
|
);
|
|
2382
2465
|
if (!found) {
|
|
2383
|
-
console.error(
|
|
2384
|
-
console.log(
|
|
2385
|
-
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)}`)));
|
|
2386
2469
|
process.exit(1);
|
|
2387
2470
|
}
|
|
2388
2471
|
selectedProject = found;
|
|
2389
2472
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2390
2473
|
console.log("");
|
|
2391
|
-
console.log(
|
|
2392
|
-
console.log(
|
|
2393
|
-
console.log(
|
|
2394
|
-
console.log(
|
|
2395
|
-
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)}`));
|
|
2396
2479
|
if (selectedProject.linkedRepo) {
|
|
2397
|
-
console.log(
|
|
2480
|
+
console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2398
2481
|
}
|
|
2399
2482
|
console.log("");
|
|
2400
2483
|
}
|
|
@@ -2403,11 +2486,11 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2403
2486
|
if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
|
|
2404
2487
|
selectedProject = autoMatch.project;
|
|
2405
2488
|
const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
|
|
2406
|
-
console.log(
|
|
2489
|
+
console.log(pc11.green(`\u2713 Auto-selected project: ${getProjectDisplayName(selectedProject)} (${matchReason})`));
|
|
2407
2490
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2408
2491
|
const partialDisplayName = getProjectDisplayName(autoMatch.project);
|
|
2409
|
-
console.log(
|
|
2410
|
-
const { useDetected } = await
|
|
2492
|
+
console.log(pc11.yellow(`Detected project: ${partialDisplayName} (partial match)`));
|
|
2493
|
+
const { useDetected } = await prompts9({
|
|
2411
2494
|
type: "confirm",
|
|
2412
2495
|
name: "useDetected",
|
|
2413
2496
|
message: `Use ${partialDisplayName}?`,
|
|
@@ -2422,30 +2505,30 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2422
2505
|
selectedProject = projects[0];
|
|
2423
2506
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2424
2507
|
console.log("");
|
|
2425
|
-
console.log(
|
|
2426
|
-
console.log(
|
|
2427
|
-
console.log(
|
|
2428
|
-
console.log(
|
|
2429
|
-
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)}`));
|
|
2430
2513
|
if (selectedProject.linkedRepo) {
|
|
2431
|
-
console.log(
|
|
2514
|
+
console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2432
2515
|
}
|
|
2433
2516
|
console.log("");
|
|
2434
|
-
const { continueAnyway } = await
|
|
2517
|
+
const { continueAnyway } = await prompts9({
|
|
2435
2518
|
type: "confirm",
|
|
2436
2519
|
name: "continueAnyway",
|
|
2437
2520
|
message: "Continue anyway?",
|
|
2438
2521
|
initial: false
|
|
2439
2522
|
});
|
|
2440
2523
|
if (!continueAnyway) {
|
|
2441
|
-
console.log(
|
|
2524
|
+
console.log(pc11.gray("Cancelled."));
|
|
2442
2525
|
process.exit(0);
|
|
2443
2526
|
}
|
|
2444
2527
|
}
|
|
2445
2528
|
} else {
|
|
2446
|
-
console.log(
|
|
2529
|
+
console.log(pc11.yellow(`
|
|
2447
2530
|
\u26A0\uFE0F No matching project found for ${repoFullName}`));
|
|
2448
|
-
console.log(
|
|
2531
|
+
console.log(pc11.gray("Select a project manually:\n"));
|
|
2449
2532
|
selectedProject = await promptProjectSelection(projects, repoFullName);
|
|
2450
2533
|
}
|
|
2451
2534
|
}
|
|
@@ -2453,23 +2536,23 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2453
2536
|
const autoMatch = findMatchingProject(projects, repoFullName);
|
|
2454
2537
|
if (autoMatch && autoMatch.project.id !== selectedProject.id) {
|
|
2455
2538
|
console.log("");
|
|
2456
|
-
console.log(
|
|
2457
|
-
console.log(
|
|
2458
|
-
console.log(
|
|
2459
|
-
console.log(
|
|
2460
|
-
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)}`));
|
|
2461
2544
|
if (selectedProject.linkedRepo) {
|
|
2462
|
-
console.log(
|
|
2545
|
+
console.log(pc11.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2463
2546
|
}
|
|
2464
2547
|
console.log("");
|
|
2465
|
-
const { continueAnyway } = await
|
|
2548
|
+
const { continueAnyway } = await prompts9({
|
|
2466
2549
|
type: "confirm",
|
|
2467
2550
|
name: "continueAnyway",
|
|
2468
2551
|
message: "Are you sure you want to sync with this project?",
|
|
2469
2552
|
initial: false
|
|
2470
2553
|
});
|
|
2471
2554
|
if (!continueAnyway) {
|
|
2472
|
-
console.log(
|
|
2555
|
+
console.log(pc11.gray("Cancelled."));
|
|
2473
2556
|
process.exit(0);
|
|
2474
2557
|
}
|
|
2475
2558
|
}
|
|
@@ -2483,7 +2566,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2483
2566
|
if (needsEnvPrompt || needsDirectionPrompt) {
|
|
2484
2567
|
if (needsEnvPrompt) {
|
|
2485
2568
|
const vaultEnvs = await getVaultEnvironments(accessToken, repoFullName);
|
|
2486
|
-
const { selectedEnv } = await
|
|
2569
|
+
const { selectedEnv } = await prompts9({
|
|
2487
2570
|
type: "select",
|
|
2488
2571
|
name: "selectedEnv",
|
|
2489
2572
|
message: "Keyway environment:",
|
|
@@ -2491,7 +2574,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2491
2574
|
initial: Math.max(0, vaultEnvs.indexOf("production"))
|
|
2492
2575
|
});
|
|
2493
2576
|
if (!selectedEnv) {
|
|
2494
|
-
console.log(
|
|
2577
|
+
console.log(pc11.gray("Cancelled."));
|
|
2495
2578
|
process.exit(0);
|
|
2496
2579
|
}
|
|
2497
2580
|
keywayEnv = selectedEnv;
|
|
@@ -2505,9 +2588,9 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2505
2588
|
providerEnv = mappedEnv;
|
|
2506
2589
|
} else if (selectedProject.environments.length === 1) {
|
|
2507
2590
|
providerEnv = selectedProject.environments[0];
|
|
2508
|
-
console.log(
|
|
2591
|
+
console.log(pc11.gray(`Using ${providerName} environment: ${providerEnv}`));
|
|
2509
2592
|
} else {
|
|
2510
|
-
const { selectedProviderEnv } = await
|
|
2593
|
+
const { selectedProviderEnv } = await prompts9({
|
|
2511
2594
|
type: "select",
|
|
2512
2595
|
name: "selectedProviderEnv",
|
|
2513
2596
|
message: `${providerName} environment:`,
|
|
@@ -2517,7 +2600,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2517
2600
|
))
|
|
2518
2601
|
});
|
|
2519
2602
|
if (!selectedProviderEnv) {
|
|
2520
|
-
console.log(
|
|
2603
|
+
console.log(pc11.gray("Cancelled."));
|
|
2521
2604
|
process.exit(0);
|
|
2522
2605
|
}
|
|
2523
2606
|
providerEnv = selectedProviderEnv;
|
|
@@ -2531,7 +2614,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2531
2614
|
if (needsDirectionPrompt) {
|
|
2532
2615
|
const effectiveKeywayEnv = keywayEnv || "production";
|
|
2533
2616
|
const effectiveProviderEnv = providerEnv || mapToProviderEnvironment(provider, effectiveKeywayEnv);
|
|
2534
|
-
console.log(
|
|
2617
|
+
console.log(pc11.gray("\nComparing secrets..."));
|
|
2535
2618
|
diff = await getSyncDiff(accessToken, repoFullName, {
|
|
2536
2619
|
connectionId: connection.id,
|
|
2537
2620
|
projectId: selectedProject.id,
|
|
@@ -2553,7 +2636,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2553
2636
|
} else if (diff.providerCount === 0 && diff.keywayCount > 0) {
|
|
2554
2637
|
defaultDirection = 0;
|
|
2555
2638
|
}
|
|
2556
|
-
const { selectedDirection } = await
|
|
2639
|
+
const { selectedDirection } = await prompts9({
|
|
2557
2640
|
type: "select",
|
|
2558
2641
|
name: "selectedDirection",
|
|
2559
2642
|
message: "Sync direction:",
|
|
@@ -2564,7 +2647,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2564
2647
|
initial: defaultDirection
|
|
2565
2648
|
});
|
|
2566
2649
|
if (!selectedDirection) {
|
|
2567
|
-
console.log(
|
|
2650
|
+
console.log(pc11.gray("Cancelled."));
|
|
2568
2651
|
process.exit(0);
|
|
2569
2652
|
}
|
|
2570
2653
|
direction = selectedDirection;
|
|
@@ -2581,10 +2664,10 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2581
2664
|
keywayEnv
|
|
2582
2665
|
);
|
|
2583
2666
|
if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
|
|
2584
|
-
console.log(
|
|
2667
|
+
console.log(pc11.yellow(`
|
|
2585
2668
|
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2586
|
-
console.log(
|
|
2587
|
-
const { importFirst } = await
|
|
2669
|
+
console.log(pc11.gray(` (Use --environment to sync a different environment)`));
|
|
2670
|
+
const { importFirst } = await prompts9({
|
|
2588
2671
|
type: "confirm",
|
|
2589
2672
|
name: "importFirst",
|
|
2590
2673
|
message: `Import secrets from ${providerName} first?`,
|
|
@@ -2625,7 +2708,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2625
2708
|
command: "sync",
|
|
2626
2709
|
error: truncateMessage(message)
|
|
2627
2710
|
});
|
|
2628
|
-
console.error(
|
|
2711
|
+
console.error(pc11.red(`
|
|
2629
2712
|
\u2717 ${message}`));
|
|
2630
2713
|
process.exit(1);
|
|
2631
2714
|
}
|
|
@@ -2644,49 +2727,49 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2644
2727
|
});
|
|
2645
2728
|
const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
|
|
2646
2729
|
if (totalChanges === 0) {
|
|
2647
|
-
console.log(
|
|
2730
|
+
console.log(pc11.green("\n\u2713 Already in sync. No changes needed."));
|
|
2648
2731
|
return;
|
|
2649
2732
|
}
|
|
2650
|
-
console.log(
|
|
2733
|
+
console.log(pc11.blue("\n\u{1F4CB} Sync Preview\n"));
|
|
2651
2734
|
if (preview.toCreate.length > 0) {
|
|
2652
|
-
console.log(
|
|
2653
|
-
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}`)));
|
|
2654
2737
|
if (preview.toCreate.length > 5) {
|
|
2655
|
-
console.log(
|
|
2738
|
+
console.log(pc11.gray(` ... and ${preview.toCreate.length - 5} more`));
|
|
2656
2739
|
}
|
|
2657
2740
|
}
|
|
2658
2741
|
if (preview.toUpdate.length > 0) {
|
|
2659
|
-
console.log(
|
|
2660
|
-
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}`)));
|
|
2661
2744
|
if (preview.toUpdate.length > 5) {
|
|
2662
|
-
console.log(
|
|
2745
|
+
console.log(pc11.gray(` ... and ${preview.toUpdate.length - 5} more`));
|
|
2663
2746
|
}
|
|
2664
2747
|
}
|
|
2665
2748
|
if (preview.toDelete.length > 0) {
|
|
2666
|
-
console.log(
|
|
2667
|
-
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}`)));
|
|
2668
2751
|
if (preview.toDelete.length > 5) {
|
|
2669
|
-
console.log(
|
|
2752
|
+
console.log(pc11.gray(` ... and ${preview.toDelete.length - 5} more`));
|
|
2670
2753
|
}
|
|
2671
2754
|
}
|
|
2672
2755
|
if (preview.toSkip.length > 0) {
|
|
2673
|
-
console.log(
|
|
2756
|
+
console.log(pc11.gray(` \u25CB ${preview.toSkip.length} unchanged`));
|
|
2674
2757
|
}
|
|
2675
2758
|
console.log("");
|
|
2676
2759
|
if (!skipConfirm) {
|
|
2677
2760
|
const target = direction === "push" ? providerName : "Keyway";
|
|
2678
|
-
const { confirm } = await
|
|
2761
|
+
const { confirm } = await prompts9({
|
|
2679
2762
|
type: "confirm",
|
|
2680
2763
|
name: "confirm",
|
|
2681
2764
|
message: `Apply ${totalChanges} changes to ${target}?`,
|
|
2682
2765
|
initial: true
|
|
2683
2766
|
});
|
|
2684
2767
|
if (!confirm) {
|
|
2685
|
-
console.log(
|
|
2768
|
+
console.log(pc11.gray("Cancelled."));
|
|
2686
2769
|
return;
|
|
2687
2770
|
}
|
|
2688
2771
|
}
|
|
2689
|
-
console.log(
|
|
2772
|
+
console.log(pc11.blue("\n\u23F3 Syncing...\n"));
|
|
2690
2773
|
const result = await executeSync(accessToken, repoFullName, {
|
|
2691
2774
|
connectionId,
|
|
2692
2775
|
projectId: project.id,
|
|
@@ -2698,11 +2781,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2698
2781
|
allowDelete
|
|
2699
2782
|
});
|
|
2700
2783
|
if (result.success) {
|
|
2701
|
-
console.log(
|
|
2702
|
-
console.log(
|
|
2703
|
-
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}`));
|
|
2704
2787
|
if (result.stats.deleted > 0) {
|
|
2705
|
-
console.log(
|
|
2788
|
+
console.log(pc11.gray(` Deleted: ${result.stats.deleted}`));
|
|
2706
2789
|
}
|
|
2707
2790
|
trackEvent(AnalyticsEvents.CLI_SYNC, {
|
|
2708
2791
|
provider,
|
|
@@ -2712,7 +2795,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2712
2795
|
deleted: result.stats.deleted
|
|
2713
2796
|
});
|
|
2714
2797
|
} else {
|
|
2715
|
-
console.error(
|
|
2798
|
+
console.error(pc11.red(`
|
|
2716
2799
|
\u2717 ${result.error}`));
|
|
2717
2800
|
process.exit(1);
|
|
2718
2801
|
}
|
|
@@ -2720,20 +2803,19 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2720
2803
|
|
|
2721
2804
|
// src/cli.ts
|
|
2722
2805
|
process.on("unhandledRejection", (reason) => {
|
|
2723
|
-
console.error(
|
|
2806
|
+
console.error(pc12.red("Unhandled error:"), reason);
|
|
2724
2807
|
process.exit(1);
|
|
2725
2808
|
});
|
|
2726
2809
|
var program = new Command();
|
|
2727
2810
|
var TAGLINE = "Sync secrets with your team and infra";
|
|
2728
2811
|
var showBanner = () => {
|
|
2729
|
-
const text =
|
|
2812
|
+
const text = pc12.bold(pc12.cyan("Keyway CLI"));
|
|
2730
2813
|
console.log(`
|
|
2731
2814
|
${text}
|
|
2732
|
-
${
|
|
2815
|
+
${pc12.gray(TAGLINE)}
|
|
2733
2816
|
`);
|
|
2734
2817
|
};
|
|
2735
2818
|
showBanner();
|
|
2736
|
-
warnIfEnvNotGitignored();
|
|
2737
2819
|
program.name("keyway").description(TAGLINE).version(package_default.version);
|
|
2738
2820
|
program.command("init").description("Initialize a vault for the current repository").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (options) => {
|
|
2739
2821
|
await initCommand(options);
|
|
@@ -2765,7 +2847,10 @@ program.command("disconnect <provider>").description("Disconnect from a provider
|
|
|
2765
2847
|
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) => {
|
|
2766
2848
|
await syncCommand(provider, options);
|
|
2767
2849
|
});
|
|
2768
|
-
|
|
2769
|
-
|
|
2850
|
+
(async () => {
|
|
2851
|
+
await warnIfEnvNotGitignored();
|
|
2852
|
+
await program.parseAsync();
|
|
2853
|
+
})().catch((error) => {
|
|
2854
|
+
console.error(pc12.red("Error:"), error.message || error);
|
|
2770
2855
|
process.exit(1);
|
|
2771
2856
|
});
|