@keywaysh/cli 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +306 -276
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -8,12 +8,11 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
-
import
|
|
11
|
+
import pc11 from "picocolors";
|
|
12
12
|
|
|
13
13
|
// src/cmds/init.ts
|
|
14
|
-
import
|
|
14
|
+
import pc6 from "picocolors";
|
|
15
15
|
import prompts4 from "prompts";
|
|
16
|
-
import open2 from "open";
|
|
17
16
|
|
|
18
17
|
// src/utils/git.ts
|
|
19
18
|
import { execSync } from "child_process";
|
|
@@ -102,7 +101,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
102
101
|
// package.json
|
|
103
102
|
var package_default = {
|
|
104
103
|
name: "@keywaysh/cli",
|
|
105
|
-
version: "0.1.
|
|
104
|
+
version: "0.1.6",
|
|
106
105
|
description: "One link to all your secrets",
|
|
107
106
|
type: "module",
|
|
108
107
|
bin: {
|
|
@@ -120,6 +119,7 @@ var package_default = {
|
|
|
120
119
|
prepublishOnly: "pnpm run build",
|
|
121
120
|
test: "pnpm exec vitest run",
|
|
122
121
|
"test:watch": "pnpm exec vitest",
|
|
122
|
+
"test:coverage": "pnpm exec vitest run --coverage",
|
|
123
123
|
release: "npm version patch && git push && git push --tags",
|
|
124
124
|
"release:minor": "npm version minor && git push && git push --tags",
|
|
125
125
|
"release:major": "npm version major && git push && git push --tags"
|
|
@@ -158,6 +158,8 @@ var package_default = {
|
|
|
158
158
|
"@types/balanced-match": "^3.0.2",
|
|
159
159
|
"@types/node": "^24.2.0",
|
|
160
160
|
"@types/prompts": "^2.4.9",
|
|
161
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
162
|
+
msw: "^2.12.4",
|
|
161
163
|
tsup: "^8.5.0",
|
|
162
164
|
tsx: "^4.20.3",
|
|
163
165
|
typescript: "^5.9.2",
|
|
@@ -776,8 +778,8 @@ function findReadmePath(cwd) {
|
|
|
776
778
|
async function ensureReadme(repoName, cwd) {
|
|
777
779
|
const existing = findReadmePath(cwd);
|
|
778
780
|
if (existing) return existing;
|
|
779
|
-
const
|
|
780
|
-
if (!
|
|
781
|
+
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
782
|
+
if (!isInteractive2) {
|
|
781
783
|
console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
782
784
|
return null;
|
|
783
785
|
}
|
|
@@ -828,22 +830,49 @@ async function addBadgeToReadme(silent = false) {
|
|
|
828
830
|
}
|
|
829
831
|
|
|
830
832
|
// src/cmds/push.ts
|
|
831
|
-
import
|
|
833
|
+
import pc5 from "picocolors";
|
|
832
834
|
import fs4 from "fs";
|
|
833
835
|
import path4 from "path";
|
|
834
836
|
import prompts3 from "prompts";
|
|
835
837
|
|
|
836
838
|
// src/cmds/login.ts
|
|
837
|
-
import
|
|
839
|
+
import pc4 from "picocolors";
|
|
838
840
|
import readline from "readline";
|
|
839
|
-
import open from "open";
|
|
840
841
|
import prompts2 from "prompts";
|
|
842
|
+
|
|
843
|
+
// src/utils/helpers.ts
|
|
844
|
+
import pc3 from "picocolors";
|
|
845
|
+
import open from "open";
|
|
841
846
|
function sleep(ms) {
|
|
842
847
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
843
848
|
}
|
|
849
|
+
async function openUrl(url) {
|
|
850
|
+
console.log(pc3.gray(`
|
|
851
|
+
Open this URL in your browser:
|
|
852
|
+
${url}
|
|
853
|
+
`));
|
|
854
|
+
await open(url).catch(() => {
|
|
855
|
+
});
|
|
856
|
+
}
|
|
844
857
|
function isInteractive() {
|
|
845
858
|
return Boolean(process.stdout.isTTY && process.stdin.isTTY && !process.env.CI);
|
|
846
859
|
}
|
|
860
|
+
function showUpgradePrompt(message, upgradeUrl) {
|
|
861
|
+
console.log("");
|
|
862
|
+
console.log(pc3.dim("\u2500".repeat(50)));
|
|
863
|
+
console.log("");
|
|
864
|
+
console.log(` ${pc3.yellow("\u26A1")} ${pc3.bold("Plan Limit Reached")}`);
|
|
865
|
+
console.log("");
|
|
866
|
+
console.log(pc3.white(` ${message}`));
|
|
867
|
+
console.log("");
|
|
868
|
+
console.log(` ${pc3.cyan("Upgrade now \u2192")} ${pc3.underline(upgradeUrl)}`);
|
|
869
|
+
console.log("");
|
|
870
|
+
console.log(pc3.dim("\u2500".repeat(50)));
|
|
871
|
+
console.log("");
|
|
872
|
+
}
|
|
873
|
+
var MAX_CONSECUTIVE_ERRORS = 5;
|
|
874
|
+
|
|
875
|
+
// src/cmds/login.ts
|
|
847
876
|
async function promptYesNo(question, defaultYes = true) {
|
|
848
877
|
return new Promise((resolve) => {
|
|
849
878
|
const rl = readline.createInterface({
|
|
@@ -861,52 +890,60 @@ async function promptYesNo(question, defaultYes = true) {
|
|
|
861
890
|
});
|
|
862
891
|
}
|
|
863
892
|
async function runLoginFlow() {
|
|
864
|
-
console.log(
|
|
893
|
+
console.log(pc4.blue("\u{1F510} Starting Keyway login...\n"));
|
|
865
894
|
const repoName = detectGitRepo();
|
|
866
895
|
const start = await startDeviceLogin(repoName);
|
|
867
896
|
const verifyUrl = start.verificationUriComplete || start.verificationUri;
|
|
868
897
|
if (!verifyUrl) {
|
|
869
898
|
throw new Error("Missing verification URL from the auth server.");
|
|
870
899
|
}
|
|
871
|
-
console.log(`Code: ${
|
|
900
|
+
console.log(`Code: ${pc4.bold(pc4.green(start.userCode))}`);
|
|
901
|
+
await openUrl(verifyUrl);
|
|
872
902
|
console.log("Waiting for auth...");
|
|
873
|
-
open(verifyUrl).catch(() => {
|
|
874
|
-
console.log(pc3.gray(`Open this URL in your browser: ${verifyUrl}`));
|
|
875
|
-
});
|
|
876
903
|
const pollIntervalMs = (start.interval ?? 5) * 1e3;
|
|
877
904
|
const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
|
|
878
905
|
const startTime = Date.now();
|
|
906
|
+
let consecutiveErrors = 0;
|
|
879
907
|
while (true) {
|
|
880
908
|
if (Date.now() - startTime > maxTimeoutMs) {
|
|
881
909
|
throw new Error('Login timed out. Please run "keyway login" again.');
|
|
882
910
|
}
|
|
883
911
|
await sleep(pollIntervalMs);
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
identifyUser(result.githubLogin, {
|
|
899
|
-
github_username: result.githubLogin,
|
|
900
|
-
login_method: "device"
|
|
912
|
+
try {
|
|
913
|
+
const result = await pollDeviceLogin(start.deviceCode);
|
|
914
|
+
consecutiveErrors = 0;
|
|
915
|
+
if (result.status === "pending") {
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
if (result.status === "approved" && result.keywayToken) {
|
|
919
|
+
await saveAuthToken(result.keywayToken, {
|
|
920
|
+
githubLogin: result.githubLogin,
|
|
921
|
+
expiresAt: result.expiresAt
|
|
922
|
+
});
|
|
923
|
+
trackEvent(AnalyticsEvents.CLI_LOGIN, {
|
|
924
|
+
method: "device",
|
|
925
|
+
repo: repoName
|
|
901
926
|
});
|
|
927
|
+
if (result.githubLogin) {
|
|
928
|
+
identifyUser(result.githubLogin, {
|
|
929
|
+
github_username: result.githubLogin,
|
|
930
|
+
login_method: "device"
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
console.log(pc4.green("\n\u2713 Login successful"));
|
|
934
|
+
if (result.githubLogin) {
|
|
935
|
+
console.log(`Authenticated GitHub user: ${pc4.cyan(result.githubLogin)}`);
|
|
936
|
+
}
|
|
937
|
+
return result.keywayToken;
|
|
902
938
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
939
|
+
throw new Error(result.message || "Authentication failed");
|
|
940
|
+
} catch (error) {
|
|
941
|
+
consecutiveErrors++;
|
|
942
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
943
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
944
|
+
throw new Error(`Login failed after ${MAX_CONSECUTIVE_ERRORS} consecutive errors: ${errorMsg}`);
|
|
906
945
|
}
|
|
907
|
-
return result.keywayToken;
|
|
908
946
|
}
|
|
909
|
-
throw new Error(result.message || "Authentication failed");
|
|
910
947
|
}
|
|
911
948
|
}
|
|
912
949
|
async function ensureLogin(options = {}) {
|
|
@@ -915,7 +952,7 @@ async function ensureLogin(options = {}) {
|
|
|
915
952
|
return envToken;
|
|
916
953
|
}
|
|
917
954
|
if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
|
|
918
|
-
console.warn(
|
|
955
|
+
console.warn(pc4.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
|
|
919
956
|
}
|
|
920
957
|
const stored = await getStoredAuth();
|
|
921
958
|
if (stored?.keywayToken) {
|
|
@@ -935,16 +972,13 @@ async function ensureLogin(options = {}) {
|
|
|
935
972
|
async function runTokenLogin() {
|
|
936
973
|
const repoName = detectGitRepo();
|
|
937
974
|
if (repoName) {
|
|
938
|
-
console.log(`\u{1F4C1} Detected: ${
|
|
975
|
+
console.log(`\u{1F4C1} Detected: ${pc4.cyan(repoName)}`);
|
|
939
976
|
}
|
|
940
977
|
const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
|
|
941
978
|
const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
});
|
|
946
|
-
console.log(pc3.gray("Select the detected repo (or scope manually)."));
|
|
947
|
-
console.log(pc3.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
|
|
979
|
+
await openUrl(url);
|
|
980
|
+
console.log(pc4.gray("Select the detected repo (or scope manually)."));
|
|
981
|
+
console.log(pc4.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
|
|
948
982
|
const { token } = await prompts2(
|
|
949
983
|
{
|
|
950
984
|
type: "password",
|
|
@@ -981,7 +1015,7 @@ async function runTokenLogin() {
|
|
|
981
1015
|
github_username: validation.username,
|
|
982
1016
|
login_method: "pat"
|
|
983
1017
|
});
|
|
984
|
-
console.log(
|
|
1018
|
+
console.log(pc4.green("\u2705 Authenticated"), `as ${pc4.cyan(`@${validation.username}`)}`);
|
|
985
1019
|
return trimmedToken;
|
|
986
1020
|
}
|
|
987
1021
|
async function loginCommand(options = {}) {
|
|
@@ -997,15 +1031,15 @@ async function loginCommand(options = {}) {
|
|
|
997
1031
|
command: "login",
|
|
998
1032
|
error: truncateMessage(message)
|
|
999
1033
|
});
|
|
1000
|
-
console.error(
|
|
1034
|
+
console.error(pc4.red(`
|
|
1001
1035
|
\u2717 ${message}`));
|
|
1002
1036
|
process.exit(1);
|
|
1003
1037
|
}
|
|
1004
1038
|
}
|
|
1005
1039
|
async function logoutCommand() {
|
|
1006
1040
|
clearAuth();
|
|
1007
|
-
console.log(
|
|
1008
|
-
console.log(
|
|
1041
|
+
console.log(pc4.green("\u2713 Logged out of Keyway"));
|
|
1042
|
+
console.log(pc4.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
1009
1043
|
}
|
|
1010
1044
|
|
|
1011
1045
|
// src/cmds/push.ts
|
|
@@ -1022,7 +1056,7 @@ function discoverEnvCandidates(cwd) {
|
|
|
1022
1056
|
const entries = fs4.readdirSync(cwd);
|
|
1023
1057
|
const hasEnvLocal = entries.includes(".env.local");
|
|
1024
1058
|
if (hasEnvLocal) {
|
|
1025
|
-
console.log(
|
|
1059
|
+
console.log(pc5.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
|
|
1026
1060
|
}
|
|
1027
1061
|
const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
|
|
1028
1062
|
const fullPath = path4.join(cwd, name);
|
|
@@ -1048,8 +1082,8 @@ function discoverEnvCandidates(cwd) {
|
|
|
1048
1082
|
}
|
|
1049
1083
|
async function pushCommand(options) {
|
|
1050
1084
|
try {
|
|
1051
|
-
console.log(
|
|
1052
|
-
const
|
|
1085
|
+
console.log(pc5.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
1086
|
+
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1053
1087
|
let environment = options.env;
|
|
1054
1088
|
let envFile = options.file;
|
|
1055
1089
|
const candidates = discoverEnvCandidates(process.cwd());
|
|
@@ -1059,7 +1093,7 @@ async function pushCommand(options) {
|
|
|
1059
1093
|
envFile = match.file;
|
|
1060
1094
|
}
|
|
1061
1095
|
}
|
|
1062
|
-
if (!environment && !envFile &&
|
|
1096
|
+
if (!environment && !envFile && isInteractive2 && candidates.length > 0) {
|
|
1063
1097
|
const { choice } = await prompts3(
|
|
1064
1098
|
{
|
|
1065
1099
|
type: "select",
|
|
@@ -1113,7 +1147,7 @@ async function pushCommand(options) {
|
|
|
1113
1147
|
}
|
|
1114
1148
|
let envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1115
1149
|
if (!fs4.existsSync(envFilePath)) {
|
|
1116
|
-
if (!
|
|
1150
|
+
if (!isInteractive2) {
|
|
1117
1151
|
throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
|
|
1118
1152
|
}
|
|
1119
1153
|
const { newPath } = await prompts3(
|
|
@@ -1148,14 +1182,14 @@ async function pushCommand(options) {
|
|
|
1148
1182
|
const trimmed = line.trim();
|
|
1149
1183
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1150
1184
|
});
|
|
1151
|
-
console.log(`File: ${
|
|
1152
|
-
console.log(`Environment: ${
|
|
1153
|
-
console.log(`Variables: ${
|
|
1185
|
+
console.log(`File: ${pc5.cyan(envFile)}`);
|
|
1186
|
+
console.log(`Environment: ${pc5.cyan(environment)}`);
|
|
1187
|
+
console.log(`Variables: ${pc5.cyan(lines.length.toString())}`);
|
|
1154
1188
|
const repoFullName = getCurrentRepoFullName();
|
|
1155
|
-
console.log(`Repository: ${
|
|
1189
|
+
console.log(`Repository: ${pc5.cyan(repoFullName)}`);
|
|
1156
1190
|
if (!options.yes) {
|
|
1157
|
-
const
|
|
1158
|
-
if (!
|
|
1191
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1192
|
+
if (!isInteractive3) {
|
|
1159
1193
|
throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
1160
1194
|
}
|
|
1161
1195
|
const { confirm } = await prompts3(
|
|
@@ -1172,7 +1206,7 @@ async function pushCommand(options) {
|
|
|
1172
1206
|
}
|
|
1173
1207
|
);
|
|
1174
1208
|
if (!confirm) {
|
|
1175
|
-
console.log(
|
|
1209
|
+
console.log(pc5.yellow("Push aborted."));
|
|
1176
1210
|
return;
|
|
1177
1211
|
}
|
|
1178
1212
|
}
|
|
@@ -1184,20 +1218,20 @@ async function pushCommand(options) {
|
|
|
1184
1218
|
});
|
|
1185
1219
|
console.log("\nUploading secrets...");
|
|
1186
1220
|
const response = await pushSecrets(repoFullName, environment, content, accessToken);
|
|
1187
|
-
console.log(
|
|
1221
|
+
console.log(pc5.green("\n\u2713 " + response.message));
|
|
1188
1222
|
if (response.stats) {
|
|
1189
1223
|
const { created, updated, deleted } = response.stats;
|
|
1190
1224
|
const parts = [];
|
|
1191
|
-
if (created > 0) parts.push(
|
|
1192
|
-
if (updated > 0) parts.push(
|
|
1193
|
-
if (deleted > 0) parts.push(
|
|
1225
|
+
if (created > 0) parts.push(pc5.green(`+${created} created`));
|
|
1226
|
+
if (updated > 0) parts.push(pc5.yellow(`~${updated} updated`));
|
|
1227
|
+
if (deleted > 0) parts.push(pc5.red(`-${deleted} deleted`));
|
|
1194
1228
|
if (parts.length > 0) {
|
|
1195
1229
|
console.log(`Stats: ${parts.join(", ")}`);
|
|
1196
1230
|
}
|
|
1197
1231
|
}
|
|
1198
1232
|
console.log(`
|
|
1199
1233
|
Your secrets are now encrypted and stored securely.`);
|
|
1200
|
-
console.log(`To retrieve them, run: ${
|
|
1234
|
+
console.log(`To retrieve them, run: ${pc5.cyan(`keyway pull --env ${environment}`)}`);
|
|
1201
1235
|
await shutdownAnalytics();
|
|
1202
1236
|
} catch (error) {
|
|
1203
1237
|
let message;
|
|
@@ -1210,13 +1244,18 @@ Your secrets are now encrypted and stored securely.`);
|
|
|
1210
1244
|
const availableEnvs = envNotFoundMatch[2];
|
|
1211
1245
|
message = `Environment '${requestedEnv}' does not exist in this vault.`;
|
|
1212
1246
|
hint = `Available environments: ${availableEnvs}
|
|
1213
|
-
Use ${
|
|
1247
|
+
Use ${pc5.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1214
1248
|
}
|
|
1215
|
-
if (error.statusCode === 403 && error.upgradeUrl) {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1249
|
+
if (error.statusCode === 403 && (error.upgradeUrl || message.toLowerCase().includes("read-only"))) {
|
|
1250
|
+
const upgradeMessage = message.toLowerCase().includes("read-only") ? "This vault is read-only on your current plan." : message;
|
|
1251
|
+
const upgradeUrl = error.upgradeUrl || "https://keyway.sh/settings";
|
|
1252
|
+
trackEvent(AnalyticsEvents.CLI_ERROR, {
|
|
1253
|
+
command: "push",
|
|
1254
|
+
error: upgradeMessage
|
|
1255
|
+
});
|
|
1256
|
+
await shutdownAnalytics();
|
|
1257
|
+
showUpgradePrompt(upgradeMessage, upgradeUrl);
|
|
1258
|
+
process.exit(1);
|
|
1220
1259
|
}
|
|
1221
1260
|
} else if (error instanceof Error) {
|
|
1222
1261
|
message = truncateMessage(error.message);
|
|
@@ -1228,10 +1267,10 @@ Use ${pc4.cyan(`keyway push --env <environment>`)} to specify one, or create '${
|
|
|
1228
1267
|
error: message
|
|
1229
1268
|
});
|
|
1230
1269
|
await shutdownAnalytics();
|
|
1231
|
-
console.error(
|
|
1270
|
+
console.error(pc5.red(`
|
|
1232
1271
|
\u2717 ${message}`));
|
|
1233
1272
|
if (hint) {
|
|
1234
|
-
console.error(
|
|
1273
|
+
console.error(pc5.gray(`
|
|
1235
1274
|
${hint}`));
|
|
1236
1275
|
}
|
|
1237
1276
|
process.exit(1);
|
|
@@ -1242,12 +1281,6 @@ ${hint}`));
|
|
|
1242
1281
|
var DASHBOARD_URL = "https://www.keyway.sh/dashboard/vaults";
|
|
1243
1282
|
var POLL_INTERVAL_MS = 3e3;
|
|
1244
1283
|
var POLL_TIMEOUT_MS = 12e4;
|
|
1245
|
-
function sleep2(ms) {
|
|
1246
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1247
|
-
}
|
|
1248
|
-
function isInteractive2() {
|
|
1249
|
-
return Boolean(process.stdout.isTTY && process.stdin.isTTY && !process.env.CI);
|
|
1250
|
-
}
|
|
1251
1284
|
async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
1252
1285
|
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1253
1286
|
const envToken = process.env.KEYWAY_TOKEN;
|
|
@@ -1266,12 +1299,12 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1266
1299
|
}
|
|
1267
1300
|
}
|
|
1268
1301
|
const allowPrompt = options.allowPrompt !== false;
|
|
1269
|
-
if (!allowPrompt || !
|
|
1302
|
+
if (!allowPrompt || !isInteractive()) {
|
|
1270
1303
|
throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
|
|
1271
1304
|
}
|
|
1272
1305
|
console.log("");
|
|
1273
|
-
console.log(
|
|
1274
|
-
console.log(
|
|
1306
|
+
console.log(pc6.gray(" Keyway uses a GitHub App for secure access."));
|
|
1307
|
+
console.log(pc6.gray(" Installing the app will also log you in."));
|
|
1275
1308
|
console.log("");
|
|
1276
1309
|
const { shouldProceed } = await prompts4({
|
|
1277
1310
|
type: "confirm",
|
|
@@ -1284,16 +1317,15 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1284
1317
|
}
|
|
1285
1318
|
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1286
1319
|
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
console.log("");
|
|
1290
|
-
console.log(pc5.blue("\u23F3 Waiting for installation & authorization..."));
|
|
1291
|
-
console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1320
|
+
await openUrl(installUrl);
|
|
1321
|
+
console.log(pc6.blue("\u23F3 Waiting for installation & authorization..."));
|
|
1322
|
+
console.log(pc6.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1292
1323
|
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1293
1324
|
const startTime = Date.now();
|
|
1294
1325
|
let accessToken = null;
|
|
1326
|
+
let consecutiveErrors = 0;
|
|
1295
1327
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1296
|
-
await
|
|
1328
|
+
await sleep(pollIntervalMs);
|
|
1297
1329
|
try {
|
|
1298
1330
|
if (!accessToken) {
|
|
1299
1331
|
const result = await pollDeviceLogin(deviceStart.deviceCode);
|
|
@@ -1303,7 +1335,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1303
1335
|
githubLogin: result.githubLogin,
|
|
1304
1336
|
expiresAt: result.expiresAt
|
|
1305
1337
|
});
|
|
1306
|
-
console.log(
|
|
1338
|
+
console.log(pc6.green("\u2713 Signed in!"));
|
|
1307
1339
|
if (result.githubLogin) {
|
|
1308
1340
|
identifyUser(result.githubLogin, {
|
|
1309
1341
|
github_username: result.githubLogin,
|
|
@@ -1315,18 +1347,24 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1315
1347
|
if (accessToken) {
|
|
1316
1348
|
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1317
1349
|
if (installStatus.installed) {
|
|
1318
|
-
console.log(
|
|
1350
|
+
console.log(pc6.green("\u2713 GitHub App installed!"));
|
|
1319
1351
|
console.log("");
|
|
1320
1352
|
return accessToken;
|
|
1321
1353
|
}
|
|
1322
1354
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1355
|
+
consecutiveErrors = 0;
|
|
1356
|
+
process.stdout.write(pc6.gray("."));
|
|
1357
|
+
} catch (error) {
|
|
1358
|
+
consecutiveErrors++;
|
|
1359
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
1360
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
1361
|
+
throw new Error(`Setup failed after ${MAX_CONSECUTIVE_ERRORS} consecutive errors: ${errorMsg}`);
|
|
1362
|
+
}
|
|
1325
1363
|
}
|
|
1326
1364
|
}
|
|
1327
1365
|
console.log("");
|
|
1328
|
-
console.log(
|
|
1329
|
-
console.log(
|
|
1366
|
+
console.log(pc6.yellow("\u26A0 Timed out waiting for setup."));
|
|
1367
|
+
console.log(pc6.gray(` Install the GitHub App: ${installUrl}`));
|
|
1330
1368
|
throw new Error("Setup timed out. Please try again.");
|
|
1331
1369
|
}
|
|
1332
1370
|
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
@@ -1336,7 +1374,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1336
1374
|
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1337
1375
|
} catch (error) {
|
|
1338
1376
|
if (error instanceof APIError && error.statusCode === 401) {
|
|
1339
|
-
console.log(
|
|
1377
|
+
console.log(pc6.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1340
1378
|
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1341
1379
|
clearAuth2();
|
|
1342
1380
|
return null;
|
|
@@ -1347,13 +1385,13 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1347
1385
|
return accessToken;
|
|
1348
1386
|
}
|
|
1349
1387
|
console.log("");
|
|
1350
|
-
console.log(
|
|
1388
|
+
console.log(pc6.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1351
1389
|
console.log("");
|
|
1352
|
-
console.log(
|
|
1353
|
-
console.log(
|
|
1390
|
+
console.log(pc6.gray(" The Keyway GitHub App is required to securely manage secrets."));
|
|
1391
|
+
console.log(pc6.gray(" It only requests minimal permissions (repository metadata)."));
|
|
1354
1392
|
console.log("");
|
|
1355
|
-
if (!
|
|
1356
|
-
console.log(
|
|
1393
|
+
if (!isInteractive()) {
|
|
1394
|
+
console.log(pc6.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1357
1395
|
throw new Error("GitHub App installation required.");
|
|
1358
1396
|
}
|
|
1359
1397
|
const { shouldInstall } = await prompts4({
|
|
@@ -1363,67 +1401,72 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1363
1401
|
initial: true
|
|
1364
1402
|
});
|
|
1365
1403
|
if (!shouldInstall) {
|
|
1366
|
-
console.log(
|
|
1404
|
+
console.log(pc6.gray(`
|
|
1367
1405
|
You can install later: ${status.installUrl}`));
|
|
1368
1406
|
throw new Error("GitHub App installation required.");
|
|
1369
1407
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
console.log("");
|
|
1373
|
-
console.log(pc5.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1374
|
-
console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1408
|
+
await openUrl(status.installUrl);
|
|
1409
|
+
console.log(pc6.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1410
|
+
console.log(pc6.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1375
1411
|
const startTime = Date.now();
|
|
1412
|
+
let consecutiveErrors = 0;
|
|
1376
1413
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1377
|
-
await
|
|
1414
|
+
await sleep(POLL_INTERVAL_MS);
|
|
1378
1415
|
try {
|
|
1379
1416
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1380
1417
|
if (pollStatus.installed) {
|
|
1381
|
-
console.log(
|
|
1418
|
+
console.log(pc6.green("\u2713 GitHub App installed!"));
|
|
1382
1419
|
console.log("");
|
|
1383
1420
|
return accessToken;
|
|
1384
1421
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1422
|
+
consecutiveErrors = 0;
|
|
1423
|
+
process.stdout.write(pc6.gray("."));
|
|
1424
|
+
} catch (error) {
|
|
1425
|
+
consecutiveErrors++;
|
|
1426
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
1427
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
1428
|
+
throw new Error(`Installation check failed after ${MAX_CONSECUTIVE_ERRORS} consecutive errors: ${errorMsg}`);
|
|
1429
|
+
}
|
|
1387
1430
|
}
|
|
1388
1431
|
}
|
|
1389
1432
|
console.log("");
|
|
1390
|
-
console.log(
|
|
1391
|
-
console.log(
|
|
1433
|
+
console.log(pc6.yellow("\u26A0 Timed out waiting for installation."));
|
|
1434
|
+
console.log(pc6.gray(` You can install the GitHub App later: ${status.installUrl}`));
|
|
1392
1435
|
throw new Error("GitHub App installation timed out.");
|
|
1393
1436
|
}
|
|
1394
1437
|
async function initCommand(options = {}) {
|
|
1395
1438
|
try {
|
|
1396
1439
|
const repoFullName = getCurrentRepoFullName();
|
|
1397
1440
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1398
|
-
console.log(
|
|
1399
|
-
console.log(` ${
|
|
1441
|
+
console.log(pc6.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1442
|
+
console.log(` ${pc6.gray("Repository:")} ${pc6.white(repoFullName)}`);
|
|
1400
1443
|
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1401
1444
|
allowPrompt: options.loginPrompt !== false
|
|
1402
1445
|
});
|
|
1403
1446
|
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1404
1447
|
const vaultExists = await checkVaultExists(accessToken, repoFullName);
|
|
1405
1448
|
if (vaultExists) {
|
|
1406
|
-
console.log(
|
|
1407
|
-
console.log(` ${
|
|
1408
|
-
console.log(` ${
|
|
1449
|
+
console.log(pc6.green("\n\u2713 Already initialized!\n"));
|
|
1450
|
+
console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets`);
|
|
1451
|
+
console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
|
|
1409
1452
|
console.log("");
|
|
1410
1453
|
await shutdownAnalytics();
|
|
1411
1454
|
return;
|
|
1412
1455
|
}
|
|
1413
1456
|
await initVault(repoFullName, accessToken);
|
|
1414
|
-
console.log(
|
|
1457
|
+
console.log(pc6.green("\u2713 Vault created!"));
|
|
1415
1458
|
try {
|
|
1416
1459
|
const badgeAdded = await addBadgeToReadme(true);
|
|
1417
1460
|
if (badgeAdded) {
|
|
1418
|
-
console.log(
|
|
1461
|
+
console.log(pc6.green("\u2713 Badge added to README.md"));
|
|
1419
1462
|
}
|
|
1420
1463
|
} catch {
|
|
1421
1464
|
}
|
|
1422
1465
|
console.log("");
|
|
1423
1466
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1424
|
-
const
|
|
1425
|
-
if (envCandidates.length > 0 &&
|
|
1426
|
-
console.log(
|
|
1467
|
+
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1468
|
+
if (envCandidates.length > 0 && isInteractive2) {
|
|
1469
|
+
console.log(pc6.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1427
1470
|
`));
|
|
1428
1471
|
const { shouldPush } = await prompts4({
|
|
1429
1472
|
type: "confirm",
|
|
@@ -1437,42 +1480,32 @@ async function initCommand(options = {}) {
|
|
|
1437
1480
|
return;
|
|
1438
1481
|
}
|
|
1439
1482
|
}
|
|
1440
|
-
console.log(
|
|
1483
|
+
console.log(pc6.dim("\u2500".repeat(50)));
|
|
1441
1484
|
console.log("");
|
|
1442
1485
|
if (envCandidates.length === 0) {
|
|
1443
|
-
console.log(
|
|
1444
|
-
console.log(` ${
|
|
1486
|
+
console.log(`${pc6.yellow("\u26A0")} No .env file found - your vault is empty`);
|
|
1487
|
+
console.log(` Next: Create ${pc6.cyan(".env")} and run ${pc6.cyan("keyway push")}
|
|
1445
1488
|
`);
|
|
1446
1489
|
} else {
|
|
1447
|
-
console.log(` ${
|
|
1490
|
+
console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets
|
|
1448
1491
|
`);
|
|
1449
1492
|
}
|
|
1450
|
-
console.log(` ${
|
|
1493
|
+
console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
|
|
1451
1494
|
console.log("");
|
|
1452
1495
|
await shutdownAnalytics();
|
|
1453
1496
|
} catch (error) {
|
|
1454
1497
|
if (error instanceof APIError) {
|
|
1455
1498
|
if (error.statusCode === 409) {
|
|
1456
|
-
console.log(
|
|
1457
|
-
console.log(` ${
|
|
1458
|
-
console.log(` ${
|
|
1499
|
+
console.log(pc6.green("\n\u2713 Already initialized!\n"));
|
|
1500
|
+
console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets`);
|
|
1501
|
+
console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
|
|
1459
1502
|
console.log("");
|
|
1460
1503
|
await shutdownAnalytics();
|
|
1461
1504
|
return;
|
|
1462
1505
|
}
|
|
1463
1506
|
if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
|
|
1464
1507
|
const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
|
|
1465
|
-
|
|
1466
|
-
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1467
|
-
console.log("");
|
|
1468
|
-
console.log(` ${pc5.yellow("\u26A1")} ${pc5.bold("Plan Limit Reached")}`);
|
|
1469
|
-
console.log("");
|
|
1470
|
-
console.log(pc5.white(` ${error.message}`));
|
|
1471
|
-
console.log("");
|
|
1472
|
-
console.log(` ${pc5.cyan("Upgrade now \u2192")} ${pc5.underline(upgradeUrl)}`);
|
|
1473
|
-
console.log("");
|
|
1474
|
-
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1475
|
-
console.log("");
|
|
1508
|
+
showUpgradePrompt(error.message, upgradeUrl);
|
|
1476
1509
|
await shutdownAnalytics();
|
|
1477
1510
|
process.exit(1);
|
|
1478
1511
|
}
|
|
@@ -1483,14 +1516,14 @@ async function initCommand(options = {}) {
|
|
|
1483
1516
|
error: message
|
|
1484
1517
|
});
|
|
1485
1518
|
await shutdownAnalytics();
|
|
1486
|
-
console.error(
|
|
1519
|
+
console.error(pc6.red(`
|
|
1487
1520
|
\u2717 ${message}`));
|
|
1488
1521
|
process.exit(1);
|
|
1489
1522
|
}
|
|
1490
1523
|
}
|
|
1491
1524
|
|
|
1492
1525
|
// src/cmds/pull.ts
|
|
1493
|
-
import
|
|
1526
|
+
import pc7 from "picocolors";
|
|
1494
1527
|
import fs5 from "fs";
|
|
1495
1528
|
import path5 from "path";
|
|
1496
1529
|
import prompts5 from "prompts";
|
|
@@ -1498,10 +1531,10 @@ async function pullCommand(options) {
|
|
|
1498
1531
|
try {
|
|
1499
1532
|
const environment = options.env || "development";
|
|
1500
1533
|
const envFile = options.file || ".env";
|
|
1501
|
-
console.log(
|
|
1502
|
-
console.log(`Environment: ${
|
|
1534
|
+
console.log(pc7.blue("\u{1F510} Pulling secrets from Keyway...\n"));
|
|
1535
|
+
console.log(`Environment: ${pc7.cyan(environment)}`);
|
|
1503
1536
|
const repoFullName = getCurrentRepoFullName();
|
|
1504
|
-
console.log(`Repository: ${
|
|
1537
|
+
console.log(`Repository: ${pc7.cyan(repoFullName)}`);
|
|
1505
1538
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1506
1539
|
trackEvent(AnalyticsEvents.CLI_PULL, {
|
|
1507
1540
|
repoFullName,
|
|
@@ -1511,11 +1544,11 @@ async function pullCommand(options) {
|
|
|
1511
1544
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1512
1545
|
const envFilePath = path5.resolve(process.cwd(), envFile);
|
|
1513
1546
|
if (fs5.existsSync(envFilePath)) {
|
|
1514
|
-
const
|
|
1547
|
+
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1515
1548
|
if (options.yes) {
|
|
1516
|
-
console.log(
|
|
1549
|
+
console.log(pc7.yellow(`
|
|
1517
1550
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1518
|
-
} else if (!
|
|
1551
|
+
} else if (!isInteractive2) {
|
|
1519
1552
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
1520
1553
|
} else {
|
|
1521
1554
|
const { confirm } = await prompts5(
|
|
@@ -1532,7 +1565,7 @@ async function pullCommand(options) {
|
|
|
1532
1565
|
}
|
|
1533
1566
|
);
|
|
1534
1567
|
if (!confirm) {
|
|
1535
|
-
console.log(
|
|
1568
|
+
console.log(pc7.yellow("Pull aborted."));
|
|
1536
1569
|
return;
|
|
1537
1570
|
}
|
|
1538
1571
|
}
|
|
@@ -1542,11 +1575,11 @@ async function pullCommand(options) {
|
|
|
1542
1575
|
const trimmed = line.trim();
|
|
1543
1576
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1544
1577
|
});
|
|
1545
|
-
console.log(
|
|
1578
|
+
console.log(pc7.green(`
|
|
1546
1579
|
\u2713 Secrets downloaded successfully`));
|
|
1547
1580
|
console.log(`
|
|
1548
|
-
File: ${
|
|
1549
|
-
console.log(`Variables: ${
|
|
1581
|
+
File: ${pc7.cyan(envFile)}`);
|
|
1582
|
+
console.log(`Variables: ${pc7.cyan(lines.length.toString())}`);
|
|
1550
1583
|
await shutdownAnalytics();
|
|
1551
1584
|
} catch (error) {
|
|
1552
1585
|
const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
|
|
@@ -1555,14 +1588,14 @@ File: ${pc6.cyan(envFile)}`);
|
|
|
1555
1588
|
error: message
|
|
1556
1589
|
});
|
|
1557
1590
|
await shutdownAnalytics();
|
|
1558
|
-
console.error(
|
|
1591
|
+
console.error(pc7.red(`
|
|
1559
1592
|
\u2717 ${message}`));
|
|
1560
1593
|
process.exit(1);
|
|
1561
1594
|
}
|
|
1562
1595
|
}
|
|
1563
1596
|
|
|
1564
1597
|
// src/cmds/doctor.ts
|
|
1565
|
-
import
|
|
1598
|
+
import pc8 from "picocolors";
|
|
1566
1599
|
|
|
1567
1600
|
// src/core/doctor.ts
|
|
1568
1601
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -1816,9 +1849,9 @@ async function runAllChecks(options = {}) {
|
|
|
1816
1849
|
// src/cmds/doctor.ts
|
|
1817
1850
|
function formatSummary(results) {
|
|
1818
1851
|
const parts = [
|
|
1819
|
-
|
|
1820
|
-
results.summary.warn > 0 ?
|
|
1821
|
-
results.summary.fail > 0 ?
|
|
1852
|
+
pc8.green(`${results.summary.pass} passed`),
|
|
1853
|
+
results.summary.warn > 0 ? pc8.yellow(`${results.summary.warn} warnings`) : null,
|
|
1854
|
+
results.summary.fail > 0 ? pc8.red(`${results.summary.fail} failed`) : null
|
|
1822
1855
|
].filter(Boolean);
|
|
1823
1856
|
return parts.join(", ");
|
|
1824
1857
|
}
|
|
@@ -1835,20 +1868,20 @@ async function doctorCommand(options = {}) {
|
|
|
1835
1868
|
process.stdout.write(JSON.stringify(results, null, 0) + "\n");
|
|
1836
1869
|
process.exit(results.exitCode);
|
|
1837
1870
|
}
|
|
1838
|
-
console.log(
|
|
1871
|
+
console.log(pc8.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
|
|
1839
1872
|
results.checks.forEach((check) => {
|
|
1840
|
-
const icon = check.status === "pass" ?
|
|
1841
|
-
const detail = check.detail ?
|
|
1873
|
+
const icon = check.status === "pass" ? pc8.green("\u2713") : check.status === "warn" ? pc8.yellow("!") : pc8.red("\u2717");
|
|
1874
|
+
const detail = check.detail ? pc8.dim(` \u2014 ${check.detail}`) : "";
|
|
1842
1875
|
console.log(` ${icon} ${check.name}${detail}`);
|
|
1843
1876
|
});
|
|
1844
1877
|
console.log(`
|
|
1845
1878
|
Summary: ${formatSummary(results)}`);
|
|
1846
1879
|
if (results.summary.fail > 0) {
|
|
1847
|
-
console.log(
|
|
1880
|
+
console.log(pc8.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
|
|
1848
1881
|
} else if (results.summary.warn > 0) {
|
|
1849
|
-
console.log(
|
|
1882
|
+
console.log(pc8.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
|
|
1850
1883
|
} else {
|
|
1851
|
-
console.log(
|
|
1884
|
+
console.log(pc8.green("\u2728 All checks passed! Your environment is ready for Keyway."));
|
|
1852
1885
|
}
|
|
1853
1886
|
process.exit(results.exitCode);
|
|
1854
1887
|
} catch (error) {
|
|
@@ -1869,7 +1902,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1869
1902
|
};
|
|
1870
1903
|
process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
|
|
1871
1904
|
} else {
|
|
1872
|
-
console.error(
|
|
1905
|
+
console.error(pc8.red(`
|
|
1873
1906
|
\u2717 ${message}`));
|
|
1874
1907
|
}
|
|
1875
1908
|
process.exit(1);
|
|
@@ -1877,8 +1910,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1877
1910
|
}
|
|
1878
1911
|
|
|
1879
1912
|
// src/cmds/connect.ts
|
|
1880
|
-
import
|
|
1881
|
-
import open3 from "open";
|
|
1913
|
+
import pc9 from "picocolors";
|
|
1882
1914
|
import prompts6 from "prompts";
|
|
1883
1915
|
var TOKEN_AUTH_PROVIDERS = ["railway"];
|
|
1884
1916
|
function getTokenCreationUrl(provider) {
|
|
@@ -1891,41 +1923,38 @@ function getTokenCreationUrl(provider) {
|
|
|
1891
1923
|
}
|
|
1892
1924
|
async function connectWithTokenFlow(accessToken, provider, displayName) {
|
|
1893
1925
|
const tokenUrl = getTokenCreationUrl(provider);
|
|
1894
|
-
console.log(pc8.gray(`Create a ${displayName} API Token at:`));
|
|
1895
|
-
console.log(pc8.cyan(`\u2192 ${tokenUrl}
|
|
1896
|
-
`));
|
|
1897
1926
|
if (provider === "railway") {
|
|
1898
|
-
console.log(
|
|
1899
|
-
console.log(
|
|
1900
|
-
`));
|
|
1927
|
+
console.log(pc9.yellow("\nTip: Select the workspace containing your projects."));
|
|
1928
|
+
console.log(pc9.yellow(` Do NOT use "No workspace" - it won't have access to your projects.`));
|
|
1901
1929
|
}
|
|
1930
|
+
await openUrl(tokenUrl);
|
|
1902
1931
|
const { token } = await prompts6({
|
|
1903
1932
|
type: "password",
|
|
1904
1933
|
name: "token",
|
|
1905
1934
|
message: `${displayName} API Token:`
|
|
1906
1935
|
});
|
|
1907
1936
|
if (!token) {
|
|
1908
|
-
console.log(
|
|
1937
|
+
console.log(pc9.gray("Cancelled."));
|
|
1909
1938
|
return false;
|
|
1910
1939
|
}
|
|
1911
|
-
console.log(
|
|
1940
|
+
console.log(pc9.gray("\nValidating token..."));
|
|
1912
1941
|
try {
|
|
1913
1942
|
const result = await connectWithToken(accessToken, provider, token);
|
|
1914
1943
|
if (result.success) {
|
|
1915
|
-
console.log(
|
|
1944
|
+
console.log(pc9.green(`
|
|
1916
1945
|
\u2713 Connected to ${displayName}!`));
|
|
1917
|
-
console.log(
|
|
1946
|
+
console.log(pc9.gray(` Account: ${result.user.username}`));
|
|
1918
1947
|
if (result.user.teamName) {
|
|
1919
|
-
console.log(
|
|
1948
|
+
console.log(pc9.gray(` Team: ${result.user.teamName}`));
|
|
1920
1949
|
}
|
|
1921
1950
|
return true;
|
|
1922
1951
|
} else {
|
|
1923
|
-
console.log(
|
|
1952
|
+
console.log(pc9.red("\n\u2717 Connection failed."));
|
|
1924
1953
|
return false;
|
|
1925
1954
|
}
|
|
1926
1955
|
} catch (error) {
|
|
1927
1956
|
const message = error instanceof Error ? error.message : "Token validation failed";
|
|
1928
|
-
console.log(
|
|
1957
|
+
console.log(pc9.red(`
|
|
1929
1958
|
\u2717 ${message}`));
|
|
1930
1959
|
return false;
|
|
1931
1960
|
}
|
|
@@ -1933,11 +1962,8 @@ async function connectWithTokenFlow(accessToken, provider, displayName) {
|
|
|
1933
1962
|
async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
1934
1963
|
const authUrl = getProviderAuthUrl(provider);
|
|
1935
1964
|
const startTime = /* @__PURE__ */ new Date();
|
|
1936
|
-
|
|
1937
|
-
console.log(
|
|
1938
|
-
await open3(authUrl).catch(() => {
|
|
1939
|
-
});
|
|
1940
|
-
console.log(pc8.gray("Waiting for authorization..."));
|
|
1965
|
+
await openUrl(authUrl);
|
|
1966
|
+
console.log(pc9.gray("Waiting for authorization..."));
|
|
1941
1967
|
const maxAttempts = 60;
|
|
1942
1968
|
let attempts = 0;
|
|
1943
1969
|
while (attempts < maxAttempts) {
|
|
@@ -1949,15 +1975,15 @@ async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
|
1949
1975
|
(c) => c.provider === provider && new Date(c.createdAt) > startTime
|
|
1950
1976
|
);
|
|
1951
1977
|
if (newConn) {
|
|
1952
|
-
console.log(
|
|
1978
|
+
console.log(pc9.green(`
|
|
1953
1979
|
\u2713 Connected to ${displayName}!`));
|
|
1954
1980
|
return true;
|
|
1955
1981
|
}
|
|
1956
1982
|
} catch {
|
|
1957
1983
|
}
|
|
1958
1984
|
}
|
|
1959
|
-
console.log(
|
|
1960
|
-
console.log(
|
|
1985
|
+
console.log(pc9.red("\n\u2717 Authorization timeout."));
|
|
1986
|
+
console.log(pc9.gray("Run `keyway connections` to check if the connection was established."));
|
|
1961
1987
|
return false;
|
|
1962
1988
|
}
|
|
1963
1989
|
async function connectCommand(provider, options = {}) {
|
|
@@ -1967,13 +1993,13 @@ async function connectCommand(provider, options = {}) {
|
|
|
1967
1993
|
const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
|
|
1968
1994
|
if (!providerInfo) {
|
|
1969
1995
|
const available = providers.map((p) => p.name).join(", ");
|
|
1970
|
-
console.error(
|
|
1971
|
-
console.log(
|
|
1996
|
+
console.error(pc9.red(`Unknown provider: ${provider}`));
|
|
1997
|
+
console.log(pc9.gray(`Available providers: ${available || "none"}`));
|
|
1972
1998
|
process.exit(1);
|
|
1973
1999
|
}
|
|
1974
2000
|
if (!providerInfo.configured) {
|
|
1975
|
-
console.error(
|
|
1976
|
-
console.log(
|
|
2001
|
+
console.error(pc9.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
|
|
2002
|
+
console.log(pc9.gray("Contact your administrator to enable this integration."));
|
|
1977
2003
|
process.exit(1);
|
|
1978
2004
|
}
|
|
1979
2005
|
const { connections } = await getConnections(accessToken);
|
|
@@ -1986,11 +2012,11 @@ async function connectCommand(provider, options = {}) {
|
|
|
1986
2012
|
initial: false
|
|
1987
2013
|
});
|
|
1988
2014
|
if (!reconnect) {
|
|
1989
|
-
console.log(
|
|
2015
|
+
console.log(pc9.gray("Keeping existing connection."));
|
|
1990
2016
|
return;
|
|
1991
2017
|
}
|
|
1992
2018
|
}
|
|
1993
|
-
console.log(
|
|
2019
|
+
console.log(pc9.blue(`
|
|
1994
2020
|
Connecting to ${providerInfo.displayName}...
|
|
1995
2021
|
`));
|
|
1996
2022
|
let connected = false;
|
|
@@ -2009,7 +2035,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
2009
2035
|
command: "connect",
|
|
2010
2036
|
error: truncateMessage(message)
|
|
2011
2037
|
});
|
|
2012
|
-
console.error(
|
|
2038
|
+
console.error(pc9.red(`
|
|
2013
2039
|
\u2717 ${message}`));
|
|
2014
2040
|
process.exit(1);
|
|
2015
2041
|
}
|
|
@@ -2019,24 +2045,24 @@ async function connectionsCommand(options = {}) {
|
|
|
2019
2045
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2020
2046
|
const { connections } = await getConnections(accessToken);
|
|
2021
2047
|
if (connections.length === 0) {
|
|
2022
|
-
console.log(
|
|
2023
|
-
console.log(
|
|
2024
|
-
console.log(
|
|
2048
|
+
console.log(pc9.gray("No provider connections found."));
|
|
2049
|
+
console.log(pc9.gray("\nConnect to a provider with: keyway connect <provider>"));
|
|
2050
|
+
console.log(pc9.gray("Available providers: vercel, railway"));
|
|
2025
2051
|
return;
|
|
2026
2052
|
}
|
|
2027
|
-
console.log(
|
|
2053
|
+
console.log(pc9.blue("\n\u{1F4E1} Provider Connections\n"));
|
|
2028
2054
|
for (const conn of connections) {
|
|
2029
2055
|
const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
|
|
2030
|
-
const teamInfo = conn.providerTeamId ?
|
|
2056
|
+
const teamInfo = conn.providerTeamId ? pc9.gray(` (Team: ${conn.providerTeamId})`) : "";
|
|
2031
2057
|
const date = new Date(conn.createdAt).toLocaleDateString();
|
|
2032
|
-
console.log(` ${
|
|
2033
|
-
console.log(
|
|
2034
|
-
console.log(
|
|
2058
|
+
console.log(` ${pc9.green("\u25CF")} ${pc9.bold(providerName)}${teamInfo}`);
|
|
2059
|
+
console.log(pc9.gray(` Connected: ${date}`));
|
|
2060
|
+
console.log(pc9.gray(` ID: ${conn.id}`));
|
|
2035
2061
|
console.log("");
|
|
2036
2062
|
}
|
|
2037
2063
|
} catch (error) {
|
|
2038
2064
|
const message = error instanceof Error ? error.message : "Failed to list connections";
|
|
2039
|
-
console.error(
|
|
2065
|
+
console.error(pc9.red(`
|
|
2040
2066
|
\u2717 ${message}`));
|
|
2041
2067
|
process.exit(1);
|
|
2042
2068
|
}
|
|
@@ -2047,7 +2073,7 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2047
2073
|
const { connections } = await getConnections(accessToken);
|
|
2048
2074
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2049
2075
|
if (!connection) {
|
|
2050
|
-
console.log(
|
|
2076
|
+
console.log(pc9.gray(`No connection found for provider: ${provider}`));
|
|
2051
2077
|
return;
|
|
2052
2078
|
}
|
|
2053
2079
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
@@ -2058,11 +2084,11 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2058
2084
|
initial: false
|
|
2059
2085
|
});
|
|
2060
2086
|
if (!confirm) {
|
|
2061
|
-
console.log(
|
|
2087
|
+
console.log(pc9.gray("Cancelled."));
|
|
2062
2088
|
return;
|
|
2063
2089
|
}
|
|
2064
2090
|
await deleteConnection(accessToken, connection.id);
|
|
2065
|
-
console.log(
|
|
2091
|
+
console.log(pc9.green(`
|
|
2066
2092
|
\u2713 Disconnected from ${providerName}`));
|
|
2067
2093
|
trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
|
|
2068
2094
|
provider: provider.toLowerCase()
|
|
@@ -2073,14 +2099,14 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2073
2099
|
command: "disconnect",
|
|
2074
2100
|
error: truncateMessage(message)
|
|
2075
2101
|
});
|
|
2076
|
-
console.error(
|
|
2102
|
+
console.error(pc9.red(`
|
|
2077
2103
|
\u2717 ${message}`));
|
|
2078
2104
|
process.exit(1);
|
|
2079
2105
|
}
|
|
2080
2106
|
}
|
|
2081
2107
|
|
|
2082
2108
|
// src/cmds/sync.ts
|
|
2083
|
-
import
|
|
2109
|
+
import pc10 from "picocolors";
|
|
2084
2110
|
import prompts7 from "prompts";
|
|
2085
2111
|
function mapToVercelEnvironment(keywayEnv) {
|
|
2086
2112
|
const mapping = {
|
|
@@ -2149,11 +2175,11 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2149
2175
|
let title = p.name;
|
|
2150
2176
|
const badges = [];
|
|
2151
2177
|
if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
|
|
2152
|
-
badges.push(
|
|
2178
|
+
badges.push(pc10.green("\u2190 linked"));
|
|
2153
2179
|
} else if (p.name.toLowerCase() === repoName) {
|
|
2154
|
-
badges.push(
|
|
2180
|
+
badges.push(pc10.green("\u2190 same name"));
|
|
2155
2181
|
} else if (p.linkedRepo) {
|
|
2156
|
-
badges.push(
|
|
2182
|
+
badges.push(pc10.gray(`\u2192 ${p.linkedRepo}`));
|
|
2157
2183
|
}
|
|
2158
2184
|
if (badges.length > 0) {
|
|
2159
2185
|
title = `${p.name} ${badges.join(" ")}`;
|
|
@@ -2167,7 +2193,7 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2167
2193
|
choices
|
|
2168
2194
|
});
|
|
2169
2195
|
if (!projectChoice) {
|
|
2170
|
-
console.log(
|
|
2196
|
+
console.log(pc10.gray("Cancelled."));
|
|
2171
2197
|
process.exit(0);
|
|
2172
2198
|
}
|
|
2173
2199
|
return projects.find((p) => p.id === projectChoice);
|
|
@@ -2175,23 +2201,23 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2175
2201
|
async function syncCommand(provider, options = {}) {
|
|
2176
2202
|
try {
|
|
2177
2203
|
if (options.pull && options.allowDelete) {
|
|
2178
|
-
console.error(
|
|
2179
|
-
console.log(
|
|
2204
|
+
console.error(pc10.red("Error: --allow-delete cannot be used with --pull"));
|
|
2205
|
+
console.log(pc10.gray("The --allow-delete flag is only for push operations."));
|
|
2180
2206
|
process.exit(1);
|
|
2181
2207
|
}
|
|
2182
2208
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2183
2209
|
const repoFullName = detectGitRepo();
|
|
2184
2210
|
if (!repoFullName) {
|
|
2185
|
-
console.error(
|
|
2186
|
-
console.log(
|
|
2211
|
+
console.error(pc10.red("Could not detect Git repository."));
|
|
2212
|
+
console.log(pc10.gray("Run this command from a Git repository directory."));
|
|
2187
2213
|
process.exit(1);
|
|
2188
2214
|
}
|
|
2189
|
-
console.log(
|
|
2215
|
+
console.log(pc10.gray(`Repository: ${repoFullName}`));
|
|
2190
2216
|
let { connections } = await getConnections(accessToken);
|
|
2191
2217
|
let connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2192
2218
|
if (!connection) {
|
|
2193
2219
|
const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2194
|
-
console.log(
|
|
2220
|
+
console.log(pc10.yellow(`
|
|
2195
2221
|
Not connected to ${providerDisplayName}.`));
|
|
2196
2222
|
const { shouldConnect } = await prompts7({
|
|
2197
2223
|
type: "confirm",
|
|
@@ -2200,7 +2226,7 @@ Not connected to ${providerDisplayName}.`));
|
|
|
2200
2226
|
initial: true
|
|
2201
2227
|
});
|
|
2202
2228
|
if (!shouldConnect) {
|
|
2203
|
-
console.log(
|
|
2229
|
+
console.log(pc10.gray("Cancelled."));
|
|
2204
2230
|
process.exit(0);
|
|
2205
2231
|
}
|
|
2206
2232
|
await connectCommand(provider, { loginPrompt: false });
|
|
@@ -2208,7 +2234,7 @@ Not connected to ${providerDisplayName}.`));
|
|
|
2208
2234
|
connections = refreshed.connections;
|
|
2209
2235
|
connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2210
2236
|
if (!connection) {
|
|
2211
|
-
console.error(
|
|
2237
|
+
console.error(pc10.red(`
|
|
2212
2238
|
Connection to ${providerDisplayName} failed.`));
|
|
2213
2239
|
process.exit(1);
|
|
2214
2240
|
}
|
|
@@ -2216,7 +2242,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2216
2242
|
}
|
|
2217
2243
|
const { projects } = await getConnectionProjects(accessToken, connection.id);
|
|
2218
2244
|
if (projects.length === 0) {
|
|
2219
|
-
console.error(
|
|
2245
|
+
console.error(pc10.red(`No projects found in your ${provider} account.`));
|
|
2220
2246
|
process.exit(1);
|
|
2221
2247
|
}
|
|
2222
2248
|
let selectedProject;
|
|
@@ -2225,21 +2251,21 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2225
2251
|
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase()
|
|
2226
2252
|
);
|
|
2227
2253
|
if (!found) {
|
|
2228
|
-
console.error(
|
|
2229
|
-
console.log(
|
|
2230
|
-
projects.forEach((p) => console.log(
|
|
2254
|
+
console.error(pc10.red(`Project not found: ${options.project}`));
|
|
2255
|
+
console.log(pc10.gray("Available projects:"));
|
|
2256
|
+
projects.forEach((p) => console.log(pc10.gray(` - ${p.name}`)));
|
|
2231
2257
|
process.exit(1);
|
|
2232
2258
|
}
|
|
2233
2259
|
selectedProject = found;
|
|
2234
2260
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2235
2261
|
console.log("");
|
|
2236
|
-
console.log(
|
|
2237
|
-
console.log(
|
|
2238
|
-
console.log(
|
|
2239
|
-
console.log(
|
|
2240
|
-
console.log(
|
|
2262
|
+
console.log(pc10.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"));
|
|
2263
|
+
console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2264
|
+
console.log(pc10.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"));
|
|
2265
|
+
console.log(pc10.yellow(` Current repo: ${repoFullName}`));
|
|
2266
|
+
console.log(pc10.yellow(` Selected project: ${selectedProject.name}`));
|
|
2241
2267
|
if (selectedProject.linkedRepo) {
|
|
2242
|
-
console.log(
|
|
2268
|
+
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2243
2269
|
}
|
|
2244
2270
|
console.log("");
|
|
2245
2271
|
}
|
|
@@ -2248,9 +2274,9 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2248
2274
|
if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
|
|
2249
2275
|
selectedProject = autoMatch.project;
|
|
2250
2276
|
const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
|
|
2251
|
-
console.log(
|
|
2277
|
+
console.log(pc10.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
|
|
2252
2278
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2253
|
-
console.log(
|
|
2279
|
+
console.log(pc10.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
|
|
2254
2280
|
const { useDetected } = await prompts7({
|
|
2255
2281
|
type: "confirm",
|
|
2256
2282
|
name: "useDetected",
|
|
@@ -2266,13 +2292,13 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2266
2292
|
selectedProject = projects[0];
|
|
2267
2293
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2268
2294
|
console.log("");
|
|
2269
|
-
console.log(
|
|
2270
|
-
console.log(
|
|
2271
|
-
console.log(
|
|
2272
|
-
console.log(
|
|
2273
|
-
console.log(
|
|
2295
|
+
console.log(pc10.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"));
|
|
2296
|
+
console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2297
|
+
console.log(pc10.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"));
|
|
2298
|
+
console.log(pc10.yellow(` Current repo: ${repoFullName}`));
|
|
2299
|
+
console.log(pc10.yellow(` Only project: ${selectedProject.name}`));
|
|
2274
2300
|
if (selectedProject.linkedRepo) {
|
|
2275
|
-
console.log(
|
|
2301
|
+
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2276
2302
|
}
|
|
2277
2303
|
console.log("");
|
|
2278
2304
|
const { continueAnyway } = await prompts7({
|
|
@@ -2282,14 +2308,14 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2282
2308
|
initial: false
|
|
2283
2309
|
});
|
|
2284
2310
|
if (!continueAnyway) {
|
|
2285
|
-
console.log(
|
|
2311
|
+
console.log(pc10.gray("Cancelled."));
|
|
2286
2312
|
process.exit(0);
|
|
2287
2313
|
}
|
|
2288
2314
|
}
|
|
2289
2315
|
} else {
|
|
2290
|
-
console.log(
|
|
2316
|
+
console.log(pc10.yellow(`
|
|
2291
2317
|
\u26A0\uFE0F No matching project found for ${repoFullName}`));
|
|
2292
|
-
console.log(
|
|
2318
|
+
console.log(pc10.gray("Select a project manually:\n"));
|
|
2293
2319
|
selectedProject = await promptProjectSelection(projects, repoFullName);
|
|
2294
2320
|
}
|
|
2295
2321
|
}
|
|
@@ -2297,13 +2323,13 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2297
2323
|
const autoMatch = findMatchingProject(projects, repoFullName);
|
|
2298
2324
|
if (autoMatch && autoMatch.project.id !== selectedProject.id) {
|
|
2299
2325
|
console.log("");
|
|
2300
|
-
console.log(
|
|
2301
|
-
console.log(
|
|
2302
|
-
console.log(
|
|
2303
|
-
console.log(
|
|
2304
|
-
console.log(
|
|
2326
|
+
console.log(pc10.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"));
|
|
2327
|
+
console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
|
|
2328
|
+
console.log(pc10.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"));
|
|
2329
|
+
console.log(pc10.yellow(` Current repo: ${repoFullName}`));
|
|
2330
|
+
console.log(pc10.yellow(` Selected project: ${selectedProject.name}`));
|
|
2305
2331
|
if (selectedProject.linkedRepo) {
|
|
2306
|
-
console.log(
|
|
2332
|
+
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2307
2333
|
}
|
|
2308
2334
|
console.log("");
|
|
2309
2335
|
const { continueAnyway } = await prompts7({
|
|
@@ -2313,7 +2339,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2313
2339
|
initial: false
|
|
2314
2340
|
});
|
|
2315
2341
|
if (!continueAnyway) {
|
|
2316
|
-
console.log(
|
|
2342
|
+
console.log(pc10.gray("Cancelled."));
|
|
2317
2343
|
process.exit(0);
|
|
2318
2344
|
}
|
|
2319
2345
|
}
|
|
@@ -2335,7 +2361,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2335
2361
|
initial: Math.max(0, vaultEnvs.indexOf("production"))
|
|
2336
2362
|
});
|
|
2337
2363
|
if (!selectedEnv) {
|
|
2338
|
-
console.log(
|
|
2364
|
+
console.log(pc10.gray("Cancelled."));
|
|
2339
2365
|
process.exit(0);
|
|
2340
2366
|
}
|
|
2341
2367
|
keywayEnv = selectedEnv;
|
|
@@ -2354,7 +2380,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2354
2380
|
]
|
|
2355
2381
|
});
|
|
2356
2382
|
if (!selectedDirection) {
|
|
2357
|
-
console.log(
|
|
2383
|
+
console.log(pc10.gray("Cancelled."));
|
|
2358
2384
|
process.exit(0);
|
|
2359
2385
|
}
|
|
2360
2386
|
direction = selectedDirection;
|
|
@@ -2371,9 +2397,9 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2371
2397
|
keywayEnv
|
|
2372
2398
|
);
|
|
2373
2399
|
if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
|
|
2374
|
-
console.log(
|
|
2400
|
+
console.log(pc10.yellow(`
|
|
2375
2401
|
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2376
|
-
console.log(
|
|
2402
|
+
console.log(pc10.gray(` (Use --environment to sync a different environment)`));
|
|
2377
2403
|
const { importFirst } = await prompts7({
|
|
2378
2404
|
type: "confirm",
|
|
2379
2405
|
name: "importFirst",
|
|
@@ -2415,7 +2441,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2415
2441
|
command: "sync",
|
|
2416
2442
|
error: truncateMessage(message)
|
|
2417
2443
|
});
|
|
2418
|
-
console.error(
|
|
2444
|
+
console.error(pc10.red(`
|
|
2419
2445
|
\u2717 ${message}`));
|
|
2420
2446
|
process.exit(1);
|
|
2421
2447
|
}
|
|
@@ -2432,33 +2458,33 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2432
2458
|
});
|
|
2433
2459
|
const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
|
|
2434
2460
|
if (totalChanges === 0) {
|
|
2435
|
-
console.log(
|
|
2461
|
+
console.log(pc10.green("\n\u2713 Already in sync. No changes needed."));
|
|
2436
2462
|
return;
|
|
2437
2463
|
}
|
|
2438
|
-
console.log(
|
|
2464
|
+
console.log(pc10.blue("\n\u{1F4CB} Sync Preview\n"));
|
|
2439
2465
|
if (preview.toCreate.length > 0) {
|
|
2440
|
-
console.log(
|
|
2441
|
-
preview.toCreate.slice(0, 5).forEach((key) => console.log(
|
|
2466
|
+
console.log(pc10.green(` + ${preview.toCreate.length} to create`));
|
|
2467
|
+
preview.toCreate.slice(0, 5).forEach((key) => console.log(pc10.gray(` ${key}`)));
|
|
2442
2468
|
if (preview.toCreate.length > 5) {
|
|
2443
|
-
console.log(
|
|
2469
|
+
console.log(pc10.gray(` ... and ${preview.toCreate.length - 5} more`));
|
|
2444
2470
|
}
|
|
2445
2471
|
}
|
|
2446
2472
|
if (preview.toUpdate.length > 0) {
|
|
2447
|
-
console.log(
|
|
2448
|
-
preview.toUpdate.slice(0, 5).forEach((key) => console.log(
|
|
2473
|
+
console.log(pc10.yellow(` ~ ${preview.toUpdate.length} to update`));
|
|
2474
|
+
preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc10.gray(` ${key}`)));
|
|
2449
2475
|
if (preview.toUpdate.length > 5) {
|
|
2450
|
-
console.log(
|
|
2476
|
+
console.log(pc10.gray(` ... and ${preview.toUpdate.length - 5} more`));
|
|
2451
2477
|
}
|
|
2452
2478
|
}
|
|
2453
2479
|
if (preview.toDelete.length > 0) {
|
|
2454
|
-
console.log(
|
|
2455
|
-
preview.toDelete.slice(0, 5).forEach((key) => console.log(
|
|
2480
|
+
console.log(pc10.red(` - ${preview.toDelete.length} to delete`));
|
|
2481
|
+
preview.toDelete.slice(0, 5).forEach((key) => console.log(pc10.gray(` ${key}`)));
|
|
2456
2482
|
if (preview.toDelete.length > 5) {
|
|
2457
|
-
console.log(
|
|
2483
|
+
console.log(pc10.gray(` ... and ${preview.toDelete.length - 5} more`));
|
|
2458
2484
|
}
|
|
2459
2485
|
}
|
|
2460
2486
|
if (preview.toSkip.length > 0) {
|
|
2461
|
-
console.log(
|
|
2487
|
+
console.log(pc10.gray(` \u25CB ${preview.toSkip.length} unchanged`));
|
|
2462
2488
|
}
|
|
2463
2489
|
console.log("");
|
|
2464
2490
|
if (!skipConfirm) {
|
|
@@ -2470,11 +2496,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2470
2496
|
initial: true
|
|
2471
2497
|
});
|
|
2472
2498
|
if (!confirm) {
|
|
2473
|
-
console.log(
|
|
2499
|
+
console.log(pc10.gray("Cancelled."));
|
|
2474
2500
|
return;
|
|
2475
2501
|
}
|
|
2476
2502
|
}
|
|
2477
|
-
console.log(
|
|
2503
|
+
console.log(pc10.blue("\n\u23F3 Syncing...\n"));
|
|
2478
2504
|
const result = await executeSync(accessToken, repoFullName, {
|
|
2479
2505
|
connectionId,
|
|
2480
2506
|
projectId: project.id,
|
|
@@ -2484,11 +2510,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2484
2510
|
allowDelete
|
|
2485
2511
|
});
|
|
2486
2512
|
if (result.success) {
|
|
2487
|
-
console.log(
|
|
2488
|
-
console.log(
|
|
2489
|
-
console.log(
|
|
2513
|
+
console.log(pc10.green("\u2713 Sync complete"));
|
|
2514
|
+
console.log(pc10.gray(` Created: ${result.stats.created}`));
|
|
2515
|
+
console.log(pc10.gray(` Updated: ${result.stats.updated}`));
|
|
2490
2516
|
if (result.stats.deleted > 0) {
|
|
2491
|
-
console.log(
|
|
2517
|
+
console.log(pc10.gray(` Deleted: ${result.stats.deleted}`));
|
|
2492
2518
|
}
|
|
2493
2519
|
trackEvent(AnalyticsEvents.CLI_SYNC, {
|
|
2494
2520
|
provider,
|
|
@@ -2498,20 +2524,24 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2498
2524
|
deleted: result.stats.deleted
|
|
2499
2525
|
});
|
|
2500
2526
|
} else {
|
|
2501
|
-
console.error(
|
|
2527
|
+
console.error(pc10.red(`
|
|
2502
2528
|
\u2717 ${result.error}`));
|
|
2503
2529
|
process.exit(1);
|
|
2504
2530
|
}
|
|
2505
2531
|
}
|
|
2506
2532
|
|
|
2507
2533
|
// src/cli.ts
|
|
2534
|
+
process.on("unhandledRejection", (reason) => {
|
|
2535
|
+
console.error(pc11.red("Unhandled error:"), reason);
|
|
2536
|
+
process.exit(1);
|
|
2537
|
+
});
|
|
2508
2538
|
var program = new Command();
|
|
2509
2539
|
var TAGLINE = "Sync secrets with your team and infra";
|
|
2510
2540
|
var showBanner = () => {
|
|
2511
|
-
const text =
|
|
2541
|
+
const text = pc11.bold(pc11.cyan("Keyway CLI"));
|
|
2512
2542
|
console.log(`
|
|
2513
2543
|
${text}
|
|
2514
|
-
${
|
|
2544
|
+
${pc11.gray(TAGLINE)}
|
|
2515
2545
|
`);
|
|
2516
2546
|
};
|
|
2517
2547
|
showBanner();
|
|
@@ -2548,6 +2578,6 @@ program.command("sync <provider>").description("Sync secrets with a provider (e.
|
|
|
2548
2578
|
await syncCommand(provider, options);
|
|
2549
2579
|
});
|
|
2550
2580
|
program.parseAsync().catch((error) => {
|
|
2551
|
-
console.error(
|
|
2581
|
+
console.error(pc11.red("Error:"), error.message || error);
|
|
2552
2582
|
process.exit(1);
|
|
2553
2583
|
});
|