@keywaysh/cli 0.1.4 → 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 +485 -293
- package/package.json +20 -13
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"
|
|
@@ -146,6 +146,7 @@ var package_default = {
|
|
|
146
146
|
node: ">=18.0.0"
|
|
147
147
|
},
|
|
148
148
|
dependencies: {
|
|
149
|
+
"balanced-match": "^3.0.1",
|
|
149
150
|
commander: "^14.0.0",
|
|
150
151
|
conf: "^15.0.2",
|
|
151
152
|
open: "^11.0.0",
|
|
@@ -154,8 +155,11 @@ var package_default = {
|
|
|
154
155
|
prompts: "^2.4.2"
|
|
155
156
|
},
|
|
156
157
|
devDependencies: {
|
|
158
|
+
"@types/balanced-match": "^3.0.2",
|
|
157
159
|
"@types/node": "^24.2.0",
|
|
158
160
|
"@types/prompts": "^2.4.9",
|
|
161
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
162
|
+
msw: "^2.12.4",
|
|
159
163
|
tsup: "^8.5.0",
|
|
160
164
|
tsx: "^4.20.3",
|
|
161
165
|
typescript: "^5.9.2",
|
|
@@ -491,6 +495,19 @@ async function executeSync(accessToken, repoFullName, options) {
|
|
|
491
495
|
const wrapped = await handleResponse(response);
|
|
492
496
|
return wrapped.data;
|
|
493
497
|
}
|
|
498
|
+
async function connectWithToken(accessToken, provider, providerToken) {
|
|
499
|
+
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/${provider}/connect`, {
|
|
500
|
+
method: "POST",
|
|
501
|
+
headers: {
|
|
502
|
+
"Content-Type": "application/json",
|
|
503
|
+
"User-Agent": USER_AGENT,
|
|
504
|
+
Authorization: `Bearer ${accessToken}`
|
|
505
|
+
},
|
|
506
|
+
body: JSON.stringify({ token: providerToken })
|
|
507
|
+
});
|
|
508
|
+
const wrapped = await handleResponse(response);
|
|
509
|
+
return wrapped.data;
|
|
510
|
+
}
|
|
494
511
|
async function checkVaultExists(accessToken, repoFullName) {
|
|
495
512
|
const [owner, repo] = repoFullName.split("/");
|
|
496
513
|
try {
|
|
@@ -673,23 +690,76 @@ import fs3 from "fs";
|
|
|
673
690
|
import path3 from "path";
|
|
674
691
|
import prompts from "prompts";
|
|
675
692
|
import pc2 from "picocolors";
|
|
693
|
+
import balanced from "balanced-match";
|
|
676
694
|
function generateBadge(repo) {
|
|
677
695
|
return `[](https://www.keyway.sh/vaults/${repo})`;
|
|
678
696
|
}
|
|
697
|
+
var BADGE_PREFIX = /\[!\[[^\]]*\]\([^)]*\)\]\(/g;
|
|
698
|
+
var H1_PATTERN = /^#\s+/;
|
|
699
|
+
var CODE_FENCE = /^```/;
|
|
700
|
+
function findLastBadgeEnd(line) {
|
|
701
|
+
let lastEnd = -1;
|
|
702
|
+
let match;
|
|
703
|
+
BADGE_PREFIX.lastIndex = 0;
|
|
704
|
+
while ((match = BADGE_PREFIX.exec(line)) !== null) {
|
|
705
|
+
const prefixEnd = match.index + match[0].length - 1;
|
|
706
|
+
const remainder = line.substring(prefixEnd);
|
|
707
|
+
const balancedMatch = balanced("(", ")", remainder);
|
|
708
|
+
if (balancedMatch) {
|
|
709
|
+
lastEnd = prefixEnd + balancedMatch.end + 1;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return lastEnd;
|
|
713
|
+
}
|
|
679
714
|
function insertBadgeIntoReadme(readmeContent, badge) {
|
|
680
715
|
if (readmeContent.includes("keyway.sh/badge.svg")) {
|
|
681
716
|
return readmeContent;
|
|
682
717
|
}
|
|
683
718
|
const lines = readmeContent.split(/\r?\n/);
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
719
|
+
let inCodeBlock = false;
|
|
720
|
+
let inHtmlComment = false;
|
|
721
|
+
let lastBadgeLine = -1;
|
|
722
|
+
let lastBadgeEndIndex = -1;
|
|
723
|
+
let firstH1Line = -1;
|
|
724
|
+
for (let i = 0; i < lines.length; i++) {
|
|
725
|
+
const line = lines[i];
|
|
726
|
+
const trimmed = line.trim();
|
|
727
|
+
if (CODE_FENCE.test(trimmed)) {
|
|
728
|
+
inCodeBlock = !inCodeBlock;
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (inCodeBlock) continue;
|
|
732
|
+
if (trimmed.includes("<!--")) inHtmlComment = true;
|
|
733
|
+
if (trimmed.includes("-->")) {
|
|
734
|
+
inHtmlComment = false;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (inHtmlComment) continue;
|
|
738
|
+
BADGE_PREFIX.lastIndex = 0;
|
|
739
|
+
if (BADGE_PREFIX.test(line)) {
|
|
740
|
+
lastBadgeLine = i;
|
|
741
|
+
lastBadgeEndIndex = findLastBadgeEnd(line);
|
|
742
|
+
}
|
|
743
|
+
if (firstH1Line === -1 && H1_PATTERN.test(line)) {
|
|
744
|
+
firstH1Line = i;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
if (lastBadgeLine >= 0 && lastBadgeEndIndex > 0) {
|
|
748
|
+
const line = lines[lastBadgeLine];
|
|
749
|
+
lines[lastBadgeLine] = line.slice(0, lastBadgeEndIndex) + " " + badge + line.slice(lastBadgeEndIndex);
|
|
750
|
+
return lines.join("\n");
|
|
751
|
+
}
|
|
752
|
+
if (firstH1Line >= 0) {
|
|
753
|
+
const before = lines.slice(0, firstH1Line + 1);
|
|
754
|
+
const after = lines.slice(firstH1Line + 1);
|
|
688
755
|
while (after.length > 0 && after[0].trim() === "") {
|
|
689
756
|
after.shift();
|
|
690
757
|
}
|
|
691
|
-
|
|
692
|
-
|
|
758
|
+
if (after.length > 0) {
|
|
759
|
+
return [...before, "", badge, "", ...after].join("\n");
|
|
760
|
+
} else {
|
|
761
|
+
return [...before, "", badge, ""].join("\n");
|
|
762
|
+
}
|
|
693
763
|
}
|
|
694
764
|
return `${badge}
|
|
695
765
|
|
|
@@ -708,8 +778,8 @@ function findReadmePath(cwd) {
|
|
|
708
778
|
async function ensureReadme(repoName, cwd) {
|
|
709
779
|
const existing = findReadmePath(cwd);
|
|
710
780
|
if (existing) return existing;
|
|
711
|
-
const
|
|
712
|
-
if (!
|
|
781
|
+
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
782
|
+
if (!isInteractive2) {
|
|
713
783
|
console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
714
784
|
return null;
|
|
715
785
|
}
|
|
@@ -760,22 +830,49 @@ async function addBadgeToReadme(silent = false) {
|
|
|
760
830
|
}
|
|
761
831
|
|
|
762
832
|
// src/cmds/push.ts
|
|
763
|
-
import
|
|
833
|
+
import pc5 from "picocolors";
|
|
764
834
|
import fs4 from "fs";
|
|
765
835
|
import path4 from "path";
|
|
766
836
|
import prompts3 from "prompts";
|
|
767
837
|
|
|
768
838
|
// src/cmds/login.ts
|
|
769
|
-
import
|
|
839
|
+
import pc4 from "picocolors";
|
|
770
840
|
import readline from "readline";
|
|
771
|
-
import open from "open";
|
|
772
841
|
import prompts2 from "prompts";
|
|
842
|
+
|
|
843
|
+
// src/utils/helpers.ts
|
|
844
|
+
import pc3 from "picocolors";
|
|
845
|
+
import open from "open";
|
|
773
846
|
function sleep(ms) {
|
|
774
847
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
775
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
|
+
}
|
|
776
857
|
function isInteractive() {
|
|
777
858
|
return Boolean(process.stdout.isTTY && process.stdin.isTTY && !process.env.CI);
|
|
778
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
|
|
779
876
|
async function promptYesNo(question, defaultYes = true) {
|
|
780
877
|
return new Promise((resolve) => {
|
|
781
878
|
const rl = readline.createInterface({
|
|
@@ -793,52 +890,60 @@ async function promptYesNo(question, defaultYes = true) {
|
|
|
793
890
|
});
|
|
794
891
|
}
|
|
795
892
|
async function runLoginFlow() {
|
|
796
|
-
console.log(
|
|
893
|
+
console.log(pc4.blue("\u{1F510} Starting Keyway login...\n"));
|
|
797
894
|
const repoName = detectGitRepo();
|
|
798
895
|
const start = await startDeviceLogin(repoName);
|
|
799
896
|
const verifyUrl = start.verificationUriComplete || start.verificationUri;
|
|
800
897
|
if (!verifyUrl) {
|
|
801
898
|
throw new Error("Missing verification URL from the auth server.");
|
|
802
899
|
}
|
|
803
|
-
console.log(`Code: ${
|
|
900
|
+
console.log(`Code: ${pc4.bold(pc4.green(start.userCode))}`);
|
|
901
|
+
await openUrl(verifyUrl);
|
|
804
902
|
console.log("Waiting for auth...");
|
|
805
|
-
open(verifyUrl).catch(() => {
|
|
806
|
-
console.log(pc3.gray(`Open this URL in your browser: ${verifyUrl}`));
|
|
807
|
-
});
|
|
808
903
|
const pollIntervalMs = (start.interval ?? 5) * 1e3;
|
|
809
904
|
const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
|
|
810
905
|
const startTime = Date.now();
|
|
906
|
+
let consecutiveErrors = 0;
|
|
811
907
|
while (true) {
|
|
812
908
|
if (Date.now() - startTime > maxTimeoutMs) {
|
|
813
909
|
throw new Error('Login timed out. Please run "keyway login" again.');
|
|
814
910
|
}
|
|
815
911
|
await sleep(pollIntervalMs);
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
identifyUser(result.githubLogin, {
|
|
831
|
-
github_username: result.githubLogin,
|
|
832
|
-
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
|
|
833
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;
|
|
834
938
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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}`);
|
|
838
945
|
}
|
|
839
|
-
return result.keywayToken;
|
|
840
946
|
}
|
|
841
|
-
throw new Error(result.message || "Authentication failed");
|
|
842
947
|
}
|
|
843
948
|
}
|
|
844
949
|
async function ensureLogin(options = {}) {
|
|
@@ -847,7 +952,7 @@ async function ensureLogin(options = {}) {
|
|
|
847
952
|
return envToken;
|
|
848
953
|
}
|
|
849
954
|
if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
|
|
850
|
-
console.warn(
|
|
955
|
+
console.warn(pc4.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
|
|
851
956
|
}
|
|
852
957
|
const stored = await getStoredAuth();
|
|
853
958
|
if (stored?.keywayToken) {
|
|
@@ -867,16 +972,13 @@ async function ensureLogin(options = {}) {
|
|
|
867
972
|
async function runTokenLogin() {
|
|
868
973
|
const repoName = detectGitRepo();
|
|
869
974
|
if (repoName) {
|
|
870
|
-
console.log(`\u{1F4C1} Detected: ${
|
|
975
|
+
console.log(`\u{1F4C1} Detected: ${pc4.cyan(repoName)}`);
|
|
871
976
|
}
|
|
872
977
|
const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
|
|
873
978
|
const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
});
|
|
878
|
-
console.log(pc3.gray("Select the detected repo (or scope manually)."));
|
|
879
|
-
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."));
|
|
880
982
|
const { token } = await prompts2(
|
|
881
983
|
{
|
|
882
984
|
type: "password",
|
|
@@ -913,7 +1015,7 @@ async function runTokenLogin() {
|
|
|
913
1015
|
github_username: validation.username,
|
|
914
1016
|
login_method: "pat"
|
|
915
1017
|
});
|
|
916
|
-
console.log(
|
|
1018
|
+
console.log(pc4.green("\u2705 Authenticated"), `as ${pc4.cyan(`@${validation.username}`)}`);
|
|
917
1019
|
return trimmedToken;
|
|
918
1020
|
}
|
|
919
1021
|
async function loginCommand(options = {}) {
|
|
@@ -929,15 +1031,15 @@ async function loginCommand(options = {}) {
|
|
|
929
1031
|
command: "login",
|
|
930
1032
|
error: truncateMessage(message)
|
|
931
1033
|
});
|
|
932
|
-
console.error(
|
|
1034
|
+
console.error(pc4.red(`
|
|
933
1035
|
\u2717 ${message}`));
|
|
934
1036
|
process.exit(1);
|
|
935
1037
|
}
|
|
936
1038
|
}
|
|
937
1039
|
async function logoutCommand() {
|
|
938
1040
|
clearAuth();
|
|
939
|
-
console.log(
|
|
940
|
-
console.log(
|
|
1041
|
+
console.log(pc4.green("\u2713 Logged out of Keyway"));
|
|
1042
|
+
console.log(pc4.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
941
1043
|
}
|
|
942
1044
|
|
|
943
1045
|
// src/cmds/push.ts
|
|
@@ -954,7 +1056,7 @@ function discoverEnvCandidates(cwd) {
|
|
|
954
1056
|
const entries = fs4.readdirSync(cwd);
|
|
955
1057
|
const hasEnvLocal = entries.includes(".env.local");
|
|
956
1058
|
if (hasEnvLocal) {
|
|
957
|
-
console.log(
|
|
1059
|
+
console.log(pc5.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
|
|
958
1060
|
}
|
|
959
1061
|
const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
|
|
960
1062
|
const fullPath = path4.join(cwd, name);
|
|
@@ -980,8 +1082,8 @@ function discoverEnvCandidates(cwd) {
|
|
|
980
1082
|
}
|
|
981
1083
|
async function pushCommand(options) {
|
|
982
1084
|
try {
|
|
983
|
-
console.log(
|
|
984
|
-
const
|
|
1085
|
+
console.log(pc5.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
1086
|
+
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
985
1087
|
let environment = options.env;
|
|
986
1088
|
let envFile = options.file;
|
|
987
1089
|
const candidates = discoverEnvCandidates(process.cwd());
|
|
@@ -991,7 +1093,7 @@ async function pushCommand(options) {
|
|
|
991
1093
|
envFile = match.file;
|
|
992
1094
|
}
|
|
993
1095
|
}
|
|
994
|
-
if (!environment && !envFile &&
|
|
1096
|
+
if (!environment && !envFile && isInteractive2 && candidates.length > 0) {
|
|
995
1097
|
const { choice } = await prompts3(
|
|
996
1098
|
{
|
|
997
1099
|
type: "select",
|
|
@@ -1045,7 +1147,7 @@ async function pushCommand(options) {
|
|
|
1045
1147
|
}
|
|
1046
1148
|
let envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1047
1149
|
if (!fs4.existsSync(envFilePath)) {
|
|
1048
|
-
if (!
|
|
1150
|
+
if (!isInteractive2) {
|
|
1049
1151
|
throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
|
|
1050
1152
|
}
|
|
1051
1153
|
const { newPath } = await prompts3(
|
|
@@ -1080,14 +1182,14 @@ async function pushCommand(options) {
|
|
|
1080
1182
|
const trimmed = line.trim();
|
|
1081
1183
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1082
1184
|
});
|
|
1083
|
-
console.log(`File: ${
|
|
1084
|
-
console.log(`Environment: ${
|
|
1085
|
-
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())}`);
|
|
1086
1188
|
const repoFullName = getCurrentRepoFullName();
|
|
1087
|
-
console.log(`Repository: ${
|
|
1189
|
+
console.log(`Repository: ${pc5.cyan(repoFullName)}`);
|
|
1088
1190
|
if (!options.yes) {
|
|
1089
|
-
const
|
|
1090
|
-
if (!
|
|
1191
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1192
|
+
if (!isInteractive3) {
|
|
1091
1193
|
throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
1092
1194
|
}
|
|
1093
1195
|
const { confirm } = await prompts3(
|
|
@@ -1104,7 +1206,7 @@ async function pushCommand(options) {
|
|
|
1104
1206
|
}
|
|
1105
1207
|
);
|
|
1106
1208
|
if (!confirm) {
|
|
1107
|
-
console.log(
|
|
1209
|
+
console.log(pc5.yellow("Push aborted."));
|
|
1108
1210
|
return;
|
|
1109
1211
|
}
|
|
1110
1212
|
}
|
|
@@ -1116,20 +1218,20 @@ async function pushCommand(options) {
|
|
|
1116
1218
|
});
|
|
1117
1219
|
console.log("\nUploading secrets...");
|
|
1118
1220
|
const response = await pushSecrets(repoFullName, environment, content, accessToken);
|
|
1119
|
-
console.log(
|
|
1221
|
+
console.log(pc5.green("\n\u2713 " + response.message));
|
|
1120
1222
|
if (response.stats) {
|
|
1121
1223
|
const { created, updated, deleted } = response.stats;
|
|
1122
1224
|
const parts = [];
|
|
1123
|
-
if (created > 0) parts.push(
|
|
1124
|
-
if (updated > 0) parts.push(
|
|
1125
|
-
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`));
|
|
1126
1228
|
if (parts.length > 0) {
|
|
1127
1229
|
console.log(`Stats: ${parts.join(", ")}`);
|
|
1128
1230
|
}
|
|
1129
1231
|
}
|
|
1130
1232
|
console.log(`
|
|
1131
1233
|
Your secrets are now encrypted and stored securely.`);
|
|
1132
|
-
console.log(`To retrieve them, run: ${
|
|
1234
|
+
console.log(`To retrieve them, run: ${pc5.cyan(`keyway pull --env ${environment}`)}`);
|
|
1133
1235
|
await shutdownAnalytics();
|
|
1134
1236
|
} catch (error) {
|
|
1135
1237
|
let message;
|
|
@@ -1142,13 +1244,18 @@ Your secrets are now encrypted and stored securely.`);
|
|
|
1142
1244
|
const availableEnvs = envNotFoundMatch[2];
|
|
1143
1245
|
message = `Environment '${requestedEnv}' does not exist in this vault.`;
|
|
1144
1246
|
hint = `Available environments: ${availableEnvs}
|
|
1145
|
-
Use ${
|
|
1247
|
+
Use ${pc5.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1146
1248
|
}
|
|
1147
|
-
if (error.statusCode === 403 && error.upgradeUrl) {
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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);
|
|
1152
1259
|
}
|
|
1153
1260
|
} else if (error instanceof Error) {
|
|
1154
1261
|
message = truncateMessage(error.message);
|
|
@@ -1160,10 +1267,10 @@ Use ${pc4.cyan(`keyway push --env <environment>`)} to specify one, or create '${
|
|
|
1160
1267
|
error: message
|
|
1161
1268
|
});
|
|
1162
1269
|
await shutdownAnalytics();
|
|
1163
|
-
console.error(
|
|
1270
|
+
console.error(pc5.red(`
|
|
1164
1271
|
\u2717 ${message}`));
|
|
1165
1272
|
if (hint) {
|
|
1166
|
-
console.error(
|
|
1273
|
+
console.error(pc5.gray(`
|
|
1167
1274
|
${hint}`));
|
|
1168
1275
|
}
|
|
1169
1276
|
process.exit(1);
|
|
@@ -1174,12 +1281,6 @@ ${hint}`));
|
|
|
1174
1281
|
var DASHBOARD_URL = "https://www.keyway.sh/dashboard/vaults";
|
|
1175
1282
|
var POLL_INTERVAL_MS = 3e3;
|
|
1176
1283
|
var POLL_TIMEOUT_MS = 12e4;
|
|
1177
|
-
function sleep2(ms) {
|
|
1178
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1179
|
-
}
|
|
1180
|
-
function isInteractive2() {
|
|
1181
|
-
return Boolean(process.stdout.isTTY && process.stdin.isTTY && !process.env.CI);
|
|
1182
|
-
}
|
|
1183
1284
|
async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
1184
1285
|
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1185
1286
|
const envToken = process.env.KEYWAY_TOKEN;
|
|
@@ -1198,12 +1299,12 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1198
1299
|
}
|
|
1199
1300
|
}
|
|
1200
1301
|
const allowPrompt = options.allowPrompt !== false;
|
|
1201
|
-
if (!allowPrompt || !
|
|
1302
|
+
if (!allowPrompt || !isInteractive()) {
|
|
1202
1303
|
throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
|
|
1203
1304
|
}
|
|
1204
1305
|
console.log("");
|
|
1205
|
-
console.log(
|
|
1206
|
-
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."));
|
|
1207
1308
|
console.log("");
|
|
1208
1309
|
const { shouldProceed } = await prompts4({
|
|
1209
1310
|
type: "confirm",
|
|
@@ -1216,16 +1317,15 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1216
1317
|
}
|
|
1217
1318
|
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1218
1319
|
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
console.log("");
|
|
1222
|
-
console.log(pc5.blue("\u23F3 Waiting for installation & authorization..."));
|
|
1223
|
-
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"));
|
|
1224
1323
|
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1225
1324
|
const startTime = Date.now();
|
|
1226
1325
|
let accessToken = null;
|
|
1326
|
+
let consecutiveErrors = 0;
|
|
1227
1327
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1228
|
-
await
|
|
1328
|
+
await sleep(pollIntervalMs);
|
|
1229
1329
|
try {
|
|
1230
1330
|
if (!accessToken) {
|
|
1231
1331
|
const result = await pollDeviceLogin(deviceStart.deviceCode);
|
|
@@ -1235,7 +1335,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1235
1335
|
githubLogin: result.githubLogin,
|
|
1236
1336
|
expiresAt: result.expiresAt
|
|
1237
1337
|
});
|
|
1238
|
-
console.log(
|
|
1338
|
+
console.log(pc6.green("\u2713 Signed in!"));
|
|
1239
1339
|
if (result.githubLogin) {
|
|
1240
1340
|
identifyUser(result.githubLogin, {
|
|
1241
1341
|
github_username: result.githubLogin,
|
|
@@ -1247,18 +1347,24 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1247
1347
|
if (accessToken) {
|
|
1248
1348
|
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1249
1349
|
if (installStatus.installed) {
|
|
1250
|
-
console.log(
|
|
1350
|
+
console.log(pc6.green("\u2713 GitHub App installed!"));
|
|
1251
1351
|
console.log("");
|
|
1252
1352
|
return accessToken;
|
|
1253
1353
|
}
|
|
1254
1354
|
}
|
|
1255
|
-
|
|
1256
|
-
|
|
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
|
+
}
|
|
1257
1363
|
}
|
|
1258
1364
|
}
|
|
1259
1365
|
console.log("");
|
|
1260
|
-
console.log(
|
|
1261
|
-
console.log(
|
|
1366
|
+
console.log(pc6.yellow("\u26A0 Timed out waiting for setup."));
|
|
1367
|
+
console.log(pc6.gray(` Install the GitHub App: ${installUrl}`));
|
|
1262
1368
|
throw new Error("Setup timed out. Please try again.");
|
|
1263
1369
|
}
|
|
1264
1370
|
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
@@ -1268,7 +1374,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1268
1374
|
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1269
1375
|
} catch (error) {
|
|
1270
1376
|
if (error instanceof APIError && error.statusCode === 401) {
|
|
1271
|
-
console.log(
|
|
1377
|
+
console.log(pc6.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1272
1378
|
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1273
1379
|
clearAuth2();
|
|
1274
1380
|
return null;
|
|
@@ -1279,13 +1385,13 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1279
1385
|
return accessToken;
|
|
1280
1386
|
}
|
|
1281
1387
|
console.log("");
|
|
1282
|
-
console.log(
|
|
1388
|
+
console.log(pc6.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1283
1389
|
console.log("");
|
|
1284
|
-
console.log(
|
|
1285
|
-
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)."));
|
|
1286
1392
|
console.log("");
|
|
1287
|
-
if (!
|
|
1288
|
-
console.log(
|
|
1393
|
+
if (!isInteractive()) {
|
|
1394
|
+
console.log(pc6.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1289
1395
|
throw new Error("GitHub App installation required.");
|
|
1290
1396
|
}
|
|
1291
1397
|
const { shouldInstall } = await prompts4({
|
|
@@ -1295,67 +1401,72 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1295
1401
|
initial: true
|
|
1296
1402
|
});
|
|
1297
1403
|
if (!shouldInstall) {
|
|
1298
|
-
console.log(
|
|
1404
|
+
console.log(pc6.gray(`
|
|
1299
1405
|
You can install later: ${status.installUrl}`));
|
|
1300
1406
|
throw new Error("GitHub App installation required.");
|
|
1301
1407
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
console.log("");
|
|
1305
|
-
console.log(pc5.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1306
|
-
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"));
|
|
1307
1411
|
const startTime = Date.now();
|
|
1412
|
+
let consecutiveErrors = 0;
|
|
1308
1413
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1309
|
-
await
|
|
1414
|
+
await sleep(POLL_INTERVAL_MS);
|
|
1310
1415
|
try {
|
|
1311
1416
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1312
1417
|
if (pollStatus.installed) {
|
|
1313
|
-
console.log(
|
|
1418
|
+
console.log(pc6.green("\u2713 GitHub App installed!"));
|
|
1314
1419
|
console.log("");
|
|
1315
1420
|
return accessToken;
|
|
1316
1421
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
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
|
+
}
|
|
1319
1430
|
}
|
|
1320
1431
|
}
|
|
1321
1432
|
console.log("");
|
|
1322
|
-
console.log(
|
|
1323
|
-
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}`));
|
|
1324
1435
|
throw new Error("GitHub App installation timed out.");
|
|
1325
1436
|
}
|
|
1326
1437
|
async function initCommand(options = {}) {
|
|
1327
1438
|
try {
|
|
1328
1439
|
const repoFullName = getCurrentRepoFullName();
|
|
1329
1440
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1330
|
-
console.log(
|
|
1331
|
-
console.log(` ${
|
|
1441
|
+
console.log(pc6.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1442
|
+
console.log(` ${pc6.gray("Repository:")} ${pc6.white(repoFullName)}`);
|
|
1332
1443
|
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1333
1444
|
allowPrompt: options.loginPrompt !== false
|
|
1334
1445
|
});
|
|
1335
1446
|
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1336
1447
|
const vaultExists = await checkVaultExists(accessToken, repoFullName);
|
|
1337
1448
|
if (vaultExists) {
|
|
1338
|
-
console.log(
|
|
1339
|
-
console.log(` ${
|
|
1340
|
-
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)}`);
|
|
1341
1452
|
console.log("");
|
|
1342
1453
|
await shutdownAnalytics();
|
|
1343
1454
|
return;
|
|
1344
1455
|
}
|
|
1345
1456
|
await initVault(repoFullName, accessToken);
|
|
1346
|
-
console.log(
|
|
1457
|
+
console.log(pc6.green("\u2713 Vault created!"));
|
|
1347
1458
|
try {
|
|
1348
1459
|
const badgeAdded = await addBadgeToReadme(true);
|
|
1349
1460
|
if (badgeAdded) {
|
|
1350
|
-
console.log(
|
|
1461
|
+
console.log(pc6.green("\u2713 Badge added to README.md"));
|
|
1351
1462
|
}
|
|
1352
1463
|
} catch {
|
|
1353
1464
|
}
|
|
1354
1465
|
console.log("");
|
|
1355
1466
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1356
|
-
const
|
|
1357
|
-
if (envCandidates.length > 0 &&
|
|
1358
|
-
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(", ")}
|
|
1359
1470
|
`));
|
|
1360
1471
|
const { shouldPush } = await prompts4({
|
|
1361
1472
|
type: "confirm",
|
|
@@ -1369,42 +1480,32 @@ async function initCommand(options = {}) {
|
|
|
1369
1480
|
return;
|
|
1370
1481
|
}
|
|
1371
1482
|
}
|
|
1372
|
-
console.log(
|
|
1483
|
+
console.log(pc6.dim("\u2500".repeat(50)));
|
|
1373
1484
|
console.log("");
|
|
1374
1485
|
if (envCandidates.length === 0) {
|
|
1375
|
-
console.log(
|
|
1376
|
-
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")}
|
|
1377
1488
|
`);
|
|
1378
1489
|
} else {
|
|
1379
|
-
console.log(` ${
|
|
1490
|
+
console.log(` ${pc6.yellow("\u2192")} Run ${pc6.cyan("keyway push")} to sync your secrets
|
|
1380
1491
|
`);
|
|
1381
1492
|
}
|
|
1382
|
-
console.log(` ${
|
|
1493
|
+
console.log(` ${pc6.blue("\u2394")} Dashboard: ${pc6.underline(dashboardLink)}`);
|
|
1383
1494
|
console.log("");
|
|
1384
1495
|
await shutdownAnalytics();
|
|
1385
1496
|
} catch (error) {
|
|
1386
1497
|
if (error instanceof APIError) {
|
|
1387
1498
|
if (error.statusCode === 409) {
|
|
1388
|
-
console.log(
|
|
1389
|
-
console.log(` ${
|
|
1390
|
-
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()}`)}`);
|
|
1391
1502
|
console.log("");
|
|
1392
1503
|
await shutdownAnalytics();
|
|
1393
1504
|
return;
|
|
1394
1505
|
}
|
|
1395
1506
|
if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
|
|
1396
1507
|
const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
|
|
1397
|
-
|
|
1398
|
-
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1399
|
-
console.log("");
|
|
1400
|
-
console.log(` ${pc5.yellow("\u26A1")} ${pc5.bold("Plan Limit Reached")}`);
|
|
1401
|
-
console.log("");
|
|
1402
|
-
console.log(pc5.white(` ${error.message}`));
|
|
1403
|
-
console.log("");
|
|
1404
|
-
console.log(` ${pc5.cyan("Upgrade now \u2192")} ${pc5.underline(upgradeUrl)}`);
|
|
1405
|
-
console.log("");
|
|
1406
|
-
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1407
|
-
console.log("");
|
|
1508
|
+
showUpgradePrompt(error.message, upgradeUrl);
|
|
1408
1509
|
await shutdownAnalytics();
|
|
1409
1510
|
process.exit(1);
|
|
1410
1511
|
}
|
|
@@ -1415,14 +1516,14 @@ async function initCommand(options = {}) {
|
|
|
1415
1516
|
error: message
|
|
1416
1517
|
});
|
|
1417
1518
|
await shutdownAnalytics();
|
|
1418
|
-
console.error(
|
|
1519
|
+
console.error(pc6.red(`
|
|
1419
1520
|
\u2717 ${message}`));
|
|
1420
1521
|
process.exit(1);
|
|
1421
1522
|
}
|
|
1422
1523
|
}
|
|
1423
1524
|
|
|
1424
1525
|
// src/cmds/pull.ts
|
|
1425
|
-
import
|
|
1526
|
+
import pc7 from "picocolors";
|
|
1426
1527
|
import fs5 from "fs";
|
|
1427
1528
|
import path5 from "path";
|
|
1428
1529
|
import prompts5 from "prompts";
|
|
@@ -1430,10 +1531,10 @@ async function pullCommand(options) {
|
|
|
1430
1531
|
try {
|
|
1431
1532
|
const environment = options.env || "development";
|
|
1432
1533
|
const envFile = options.file || ".env";
|
|
1433
|
-
console.log(
|
|
1434
|
-
console.log(`Environment: ${
|
|
1534
|
+
console.log(pc7.blue("\u{1F510} Pulling secrets from Keyway...\n"));
|
|
1535
|
+
console.log(`Environment: ${pc7.cyan(environment)}`);
|
|
1435
1536
|
const repoFullName = getCurrentRepoFullName();
|
|
1436
|
-
console.log(`Repository: ${
|
|
1537
|
+
console.log(`Repository: ${pc7.cyan(repoFullName)}`);
|
|
1437
1538
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1438
1539
|
trackEvent(AnalyticsEvents.CLI_PULL, {
|
|
1439
1540
|
repoFullName,
|
|
@@ -1443,11 +1544,11 @@ async function pullCommand(options) {
|
|
|
1443
1544
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1444
1545
|
const envFilePath = path5.resolve(process.cwd(), envFile);
|
|
1445
1546
|
if (fs5.existsSync(envFilePath)) {
|
|
1446
|
-
const
|
|
1547
|
+
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1447
1548
|
if (options.yes) {
|
|
1448
|
-
console.log(
|
|
1549
|
+
console.log(pc7.yellow(`
|
|
1449
1550
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1450
|
-
} else if (!
|
|
1551
|
+
} else if (!isInteractive2) {
|
|
1451
1552
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
1452
1553
|
} else {
|
|
1453
1554
|
const { confirm } = await prompts5(
|
|
@@ -1464,7 +1565,7 @@ async function pullCommand(options) {
|
|
|
1464
1565
|
}
|
|
1465
1566
|
);
|
|
1466
1567
|
if (!confirm) {
|
|
1467
|
-
console.log(
|
|
1568
|
+
console.log(pc7.yellow("Pull aborted."));
|
|
1468
1569
|
return;
|
|
1469
1570
|
}
|
|
1470
1571
|
}
|
|
@@ -1474,11 +1575,11 @@ async function pullCommand(options) {
|
|
|
1474
1575
|
const trimmed = line.trim();
|
|
1475
1576
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1476
1577
|
});
|
|
1477
|
-
console.log(
|
|
1578
|
+
console.log(pc7.green(`
|
|
1478
1579
|
\u2713 Secrets downloaded successfully`));
|
|
1479
1580
|
console.log(`
|
|
1480
|
-
File: ${
|
|
1481
|
-
console.log(`Variables: ${
|
|
1581
|
+
File: ${pc7.cyan(envFile)}`);
|
|
1582
|
+
console.log(`Variables: ${pc7.cyan(lines.length.toString())}`);
|
|
1482
1583
|
await shutdownAnalytics();
|
|
1483
1584
|
} catch (error) {
|
|
1484
1585
|
const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
|
|
@@ -1487,14 +1588,14 @@ File: ${pc6.cyan(envFile)}`);
|
|
|
1487
1588
|
error: message
|
|
1488
1589
|
});
|
|
1489
1590
|
await shutdownAnalytics();
|
|
1490
|
-
console.error(
|
|
1591
|
+
console.error(pc7.red(`
|
|
1491
1592
|
\u2717 ${message}`));
|
|
1492
1593
|
process.exit(1);
|
|
1493
1594
|
}
|
|
1494
1595
|
}
|
|
1495
1596
|
|
|
1496
1597
|
// src/cmds/doctor.ts
|
|
1497
|
-
import
|
|
1598
|
+
import pc8 from "picocolors";
|
|
1498
1599
|
|
|
1499
1600
|
// src/core/doctor.ts
|
|
1500
1601
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -1748,9 +1849,9 @@ async function runAllChecks(options = {}) {
|
|
|
1748
1849
|
// src/cmds/doctor.ts
|
|
1749
1850
|
function formatSummary(results) {
|
|
1750
1851
|
const parts = [
|
|
1751
|
-
|
|
1752
|
-
results.summary.warn > 0 ?
|
|
1753
|
-
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
|
|
1754
1855
|
].filter(Boolean);
|
|
1755
1856
|
return parts.join(", ");
|
|
1756
1857
|
}
|
|
@@ -1767,20 +1868,20 @@ async function doctorCommand(options = {}) {
|
|
|
1767
1868
|
process.stdout.write(JSON.stringify(results, null, 0) + "\n");
|
|
1768
1869
|
process.exit(results.exitCode);
|
|
1769
1870
|
}
|
|
1770
|
-
console.log(
|
|
1871
|
+
console.log(pc8.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
|
|
1771
1872
|
results.checks.forEach((check) => {
|
|
1772
|
-
const icon = check.status === "pass" ?
|
|
1773
|
-
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}`) : "";
|
|
1774
1875
|
console.log(` ${icon} ${check.name}${detail}`);
|
|
1775
1876
|
});
|
|
1776
1877
|
console.log(`
|
|
1777
1878
|
Summary: ${formatSummary(results)}`);
|
|
1778
1879
|
if (results.summary.fail > 0) {
|
|
1779
|
-
console.log(
|
|
1880
|
+
console.log(pc8.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
|
|
1780
1881
|
} else if (results.summary.warn > 0) {
|
|
1781
|
-
console.log(
|
|
1882
|
+
console.log(pc8.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
|
|
1782
1883
|
} else {
|
|
1783
|
-
console.log(
|
|
1884
|
+
console.log(pc8.green("\u2728 All checks passed! Your environment is ready for Keyway."));
|
|
1784
1885
|
}
|
|
1785
1886
|
process.exit(results.exitCode);
|
|
1786
1887
|
} catch (error) {
|
|
@@ -1801,7 +1902,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1801
1902
|
};
|
|
1802
1903
|
process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
|
|
1803
1904
|
} else {
|
|
1804
|
-
console.error(
|
|
1905
|
+
console.error(pc8.red(`
|
|
1805
1906
|
\u2717 ${message}`));
|
|
1806
1907
|
}
|
|
1807
1908
|
process.exit(1);
|
|
@@ -1809,9 +1910,82 @@ Summary: ${formatSummary(results)}`);
|
|
|
1809
1910
|
}
|
|
1810
1911
|
|
|
1811
1912
|
// src/cmds/connect.ts
|
|
1812
|
-
import
|
|
1813
|
-
import open3 from "open";
|
|
1913
|
+
import pc9 from "picocolors";
|
|
1814
1914
|
import prompts6 from "prompts";
|
|
1915
|
+
var TOKEN_AUTH_PROVIDERS = ["railway"];
|
|
1916
|
+
function getTokenCreationUrl(provider) {
|
|
1917
|
+
switch (provider) {
|
|
1918
|
+
case "railway":
|
|
1919
|
+
return "https://railway.com/account/tokens";
|
|
1920
|
+
default:
|
|
1921
|
+
return "";
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
async function connectWithTokenFlow(accessToken, provider, displayName) {
|
|
1925
|
+
const tokenUrl = getTokenCreationUrl(provider);
|
|
1926
|
+
if (provider === "railway") {
|
|
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.`));
|
|
1929
|
+
}
|
|
1930
|
+
await openUrl(tokenUrl);
|
|
1931
|
+
const { token } = await prompts6({
|
|
1932
|
+
type: "password",
|
|
1933
|
+
name: "token",
|
|
1934
|
+
message: `${displayName} API Token:`
|
|
1935
|
+
});
|
|
1936
|
+
if (!token) {
|
|
1937
|
+
console.log(pc9.gray("Cancelled."));
|
|
1938
|
+
return false;
|
|
1939
|
+
}
|
|
1940
|
+
console.log(pc9.gray("\nValidating token..."));
|
|
1941
|
+
try {
|
|
1942
|
+
const result = await connectWithToken(accessToken, provider, token);
|
|
1943
|
+
if (result.success) {
|
|
1944
|
+
console.log(pc9.green(`
|
|
1945
|
+
\u2713 Connected to ${displayName}!`));
|
|
1946
|
+
console.log(pc9.gray(` Account: ${result.user.username}`));
|
|
1947
|
+
if (result.user.teamName) {
|
|
1948
|
+
console.log(pc9.gray(` Team: ${result.user.teamName}`));
|
|
1949
|
+
}
|
|
1950
|
+
return true;
|
|
1951
|
+
} else {
|
|
1952
|
+
console.log(pc9.red("\n\u2717 Connection failed."));
|
|
1953
|
+
return false;
|
|
1954
|
+
}
|
|
1955
|
+
} catch (error) {
|
|
1956
|
+
const message = error instanceof Error ? error.message : "Token validation failed";
|
|
1957
|
+
console.log(pc9.red(`
|
|
1958
|
+
\u2717 ${message}`));
|
|
1959
|
+
return false;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
async function connectWithOAuthFlow(accessToken, provider, displayName) {
|
|
1963
|
+
const authUrl = getProviderAuthUrl(provider);
|
|
1964
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
1965
|
+
await openUrl(authUrl);
|
|
1966
|
+
console.log(pc9.gray("Waiting for authorization..."));
|
|
1967
|
+
const maxAttempts = 60;
|
|
1968
|
+
let attempts = 0;
|
|
1969
|
+
while (attempts < maxAttempts) {
|
|
1970
|
+
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
1971
|
+
attempts++;
|
|
1972
|
+
try {
|
|
1973
|
+
const { connections } = await getConnections(accessToken);
|
|
1974
|
+
const newConn = connections.find(
|
|
1975
|
+
(c) => c.provider === provider && new Date(c.createdAt) > startTime
|
|
1976
|
+
);
|
|
1977
|
+
if (newConn) {
|
|
1978
|
+
console.log(pc9.green(`
|
|
1979
|
+
\u2713 Connected to ${displayName}!`));
|
|
1980
|
+
return true;
|
|
1981
|
+
}
|
|
1982
|
+
} catch {
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
console.log(pc9.red("\n\u2717 Authorization timeout."));
|
|
1986
|
+
console.log(pc9.gray("Run `keyway connections` to check if the connection was established."));
|
|
1987
|
+
return false;
|
|
1988
|
+
}
|
|
1815
1989
|
async function connectCommand(provider, options = {}) {
|
|
1816
1990
|
try {
|
|
1817
1991
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
@@ -1819,13 +1993,13 @@ async function connectCommand(provider, options = {}) {
|
|
|
1819
1993
|
const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
|
|
1820
1994
|
if (!providerInfo) {
|
|
1821
1995
|
const available = providers.map((p) => p.name).join(", ");
|
|
1822
|
-
console.error(
|
|
1823
|
-
console.log(
|
|
1996
|
+
console.error(pc9.red(`Unknown provider: ${provider}`));
|
|
1997
|
+
console.log(pc9.gray(`Available providers: ${available || "none"}`));
|
|
1824
1998
|
process.exit(1);
|
|
1825
1999
|
}
|
|
1826
2000
|
if (!providerInfo.configured) {
|
|
1827
|
-
console.error(
|
|
1828
|
-
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."));
|
|
1829
2003
|
process.exit(1);
|
|
1830
2004
|
}
|
|
1831
2005
|
const { connections } = await getConnections(accessToken);
|
|
@@ -1838,43 +2012,18 @@ async function connectCommand(provider, options = {}) {
|
|
|
1838
2012
|
initial: false
|
|
1839
2013
|
});
|
|
1840
2014
|
if (!reconnect) {
|
|
1841
|
-
console.log(
|
|
2015
|
+
console.log(pc9.gray("Keeping existing connection."));
|
|
1842
2016
|
return;
|
|
1843
2017
|
}
|
|
1844
2018
|
}
|
|
1845
|
-
console.log(
|
|
2019
|
+
console.log(pc9.blue(`
|
|
1846
2020
|
Connecting to ${providerInfo.displayName}...
|
|
1847
2021
|
`));
|
|
1848
|
-
const authUrl = getProviderAuthUrl(provider.toLowerCase());
|
|
1849
|
-
const startTime = /* @__PURE__ */ new Date();
|
|
1850
|
-
console.log(pc8.gray("Opening browser for authorization..."));
|
|
1851
|
-
console.log(pc8.gray(`If the browser doesn't open, visit: ${authUrl}`));
|
|
1852
|
-
await open3(authUrl).catch(() => {
|
|
1853
|
-
});
|
|
1854
|
-
console.log(pc8.gray("Waiting for authorization..."));
|
|
1855
|
-
const maxAttempts = 60;
|
|
1856
|
-
let attempts = 0;
|
|
1857
2022
|
let connected = false;
|
|
1858
|
-
|
|
1859
|
-
await
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
const { connections: connections2 } = await getConnections(accessToken);
|
|
1863
|
-
const newConn = connections2.find(
|
|
1864
|
-
(c) => c.provider === provider.toLowerCase() && new Date(c.createdAt) > startTime
|
|
1865
|
-
);
|
|
1866
|
-
if (newConn) {
|
|
1867
|
-
connected = true;
|
|
1868
|
-
console.log(pc8.green(`
|
|
1869
|
-
\u2713 Connected to ${providerInfo.displayName}!`));
|
|
1870
|
-
break;
|
|
1871
|
-
}
|
|
1872
|
-
} catch {
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
if (!connected) {
|
|
1876
|
-
console.log(pc8.red("\n\u2717 Authorization timeout."));
|
|
1877
|
-
console.log(pc8.gray("Run `keyway connections` to check if the connection was established."));
|
|
2023
|
+
if (TOKEN_AUTH_PROVIDERS.includes(provider.toLowerCase())) {
|
|
2024
|
+
connected = await connectWithTokenFlow(accessToken, provider.toLowerCase(), providerInfo.displayName);
|
|
2025
|
+
} else {
|
|
2026
|
+
connected = await connectWithOAuthFlow(accessToken, provider.toLowerCase(), providerInfo.displayName);
|
|
1878
2027
|
}
|
|
1879
2028
|
trackEvent(AnalyticsEvents.CLI_CONNECT, {
|
|
1880
2029
|
provider: provider.toLowerCase(),
|
|
@@ -1886,7 +2035,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1886
2035
|
command: "connect",
|
|
1887
2036
|
error: truncateMessage(message)
|
|
1888
2037
|
});
|
|
1889
|
-
console.error(
|
|
2038
|
+
console.error(pc9.red(`
|
|
1890
2039
|
\u2717 ${message}`));
|
|
1891
2040
|
process.exit(1);
|
|
1892
2041
|
}
|
|
@@ -1896,24 +2045,24 @@ async function connectionsCommand(options = {}) {
|
|
|
1896
2045
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1897
2046
|
const { connections } = await getConnections(accessToken);
|
|
1898
2047
|
if (connections.length === 0) {
|
|
1899
|
-
console.log(
|
|
1900
|
-
console.log(
|
|
1901
|
-
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"));
|
|
1902
2051
|
return;
|
|
1903
2052
|
}
|
|
1904
|
-
console.log(
|
|
2053
|
+
console.log(pc9.blue("\n\u{1F4E1} Provider Connections\n"));
|
|
1905
2054
|
for (const conn of connections) {
|
|
1906
2055
|
const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
|
|
1907
|
-
const teamInfo = conn.providerTeamId ?
|
|
2056
|
+
const teamInfo = conn.providerTeamId ? pc9.gray(` (Team: ${conn.providerTeamId})`) : "";
|
|
1908
2057
|
const date = new Date(conn.createdAt).toLocaleDateString();
|
|
1909
|
-
console.log(` ${
|
|
1910
|
-
console.log(
|
|
1911
|
-
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}`));
|
|
1912
2061
|
console.log("");
|
|
1913
2062
|
}
|
|
1914
2063
|
} catch (error) {
|
|
1915
2064
|
const message = error instanceof Error ? error.message : "Failed to list connections";
|
|
1916
|
-
console.error(
|
|
2065
|
+
console.error(pc9.red(`
|
|
1917
2066
|
\u2717 ${message}`));
|
|
1918
2067
|
process.exit(1);
|
|
1919
2068
|
}
|
|
@@ -1924,7 +2073,7 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1924
2073
|
const { connections } = await getConnections(accessToken);
|
|
1925
2074
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
1926
2075
|
if (!connection) {
|
|
1927
|
-
console.log(
|
|
2076
|
+
console.log(pc9.gray(`No connection found for provider: ${provider}`));
|
|
1928
2077
|
return;
|
|
1929
2078
|
}
|
|
1930
2079
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
@@ -1935,11 +2084,11 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1935
2084
|
initial: false
|
|
1936
2085
|
});
|
|
1937
2086
|
if (!confirm) {
|
|
1938
|
-
console.log(
|
|
2087
|
+
console.log(pc9.gray("Cancelled."));
|
|
1939
2088
|
return;
|
|
1940
2089
|
}
|
|
1941
2090
|
await deleteConnection(accessToken, connection.id);
|
|
1942
|
-
console.log(
|
|
2091
|
+
console.log(pc9.green(`
|
|
1943
2092
|
\u2713 Disconnected from ${providerName}`));
|
|
1944
2093
|
trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
|
|
1945
2094
|
provider: provider.toLowerCase()
|
|
@@ -1950,14 +2099,14 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1950
2099
|
command: "disconnect",
|
|
1951
2100
|
error: truncateMessage(message)
|
|
1952
2101
|
});
|
|
1953
|
-
console.error(
|
|
2102
|
+
console.error(pc9.red(`
|
|
1954
2103
|
\u2717 ${message}`));
|
|
1955
2104
|
process.exit(1);
|
|
1956
2105
|
}
|
|
1957
2106
|
}
|
|
1958
2107
|
|
|
1959
2108
|
// src/cmds/sync.ts
|
|
1960
|
-
import
|
|
2109
|
+
import pc10 from "picocolors";
|
|
1961
2110
|
import prompts7 from "prompts";
|
|
1962
2111
|
function mapToVercelEnvironment(keywayEnv) {
|
|
1963
2112
|
const mapping = {
|
|
@@ -1968,6 +2117,25 @@ function mapToVercelEnvironment(keywayEnv) {
|
|
|
1968
2117
|
};
|
|
1969
2118
|
return mapping[keywayEnv.toLowerCase()] || "production";
|
|
1970
2119
|
}
|
|
2120
|
+
function mapToRailwayEnvironment(keywayEnv) {
|
|
2121
|
+
const mapping = {
|
|
2122
|
+
production: "production",
|
|
2123
|
+
staging: "staging",
|
|
2124
|
+
dev: "development",
|
|
2125
|
+
development: "development"
|
|
2126
|
+
};
|
|
2127
|
+
return mapping[keywayEnv.toLowerCase()] || "production";
|
|
2128
|
+
}
|
|
2129
|
+
function mapToProviderEnvironment(provider, keywayEnv) {
|
|
2130
|
+
switch (provider.toLowerCase()) {
|
|
2131
|
+
case "vercel":
|
|
2132
|
+
return mapToVercelEnvironment(keywayEnv);
|
|
2133
|
+
case "railway":
|
|
2134
|
+
return mapToRailwayEnvironment(keywayEnv);
|
|
2135
|
+
default:
|
|
2136
|
+
return keywayEnv;
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
1971
2139
|
function findMatchingProject(projects, repoFullName) {
|
|
1972
2140
|
const repoFullNameLower = repoFullName.toLowerCase();
|
|
1973
2141
|
const repoName = repoFullName.split("/")[1]?.toLowerCase();
|
|
@@ -2007,11 +2175,11 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2007
2175
|
let title = p.name;
|
|
2008
2176
|
const badges = [];
|
|
2009
2177
|
if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
|
|
2010
|
-
badges.push(
|
|
2178
|
+
badges.push(pc10.green("\u2190 linked"));
|
|
2011
2179
|
} else if (p.name.toLowerCase() === repoName) {
|
|
2012
|
-
badges.push(
|
|
2180
|
+
badges.push(pc10.green("\u2190 same name"));
|
|
2013
2181
|
} else if (p.linkedRepo) {
|
|
2014
|
-
badges.push(
|
|
2182
|
+
badges.push(pc10.gray(`\u2192 ${p.linkedRepo}`));
|
|
2015
2183
|
}
|
|
2016
2184
|
if (badges.length > 0) {
|
|
2017
2185
|
title = `${p.name} ${badges.join(" ")}`;
|
|
@@ -2025,7 +2193,7 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2025
2193
|
choices
|
|
2026
2194
|
});
|
|
2027
2195
|
if (!projectChoice) {
|
|
2028
|
-
console.log(
|
|
2196
|
+
console.log(pc10.gray("Cancelled."));
|
|
2029
2197
|
process.exit(0);
|
|
2030
2198
|
}
|
|
2031
2199
|
return projects.find((p) => p.id === projectChoice);
|
|
@@ -2033,28 +2201,48 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2033
2201
|
async function syncCommand(provider, options = {}) {
|
|
2034
2202
|
try {
|
|
2035
2203
|
if (options.pull && options.allowDelete) {
|
|
2036
|
-
console.error(
|
|
2037
|
-
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."));
|
|
2038
2206
|
process.exit(1);
|
|
2039
2207
|
}
|
|
2040
2208
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2041
2209
|
const repoFullName = detectGitRepo();
|
|
2042
2210
|
if (!repoFullName) {
|
|
2043
|
-
console.error(
|
|
2044
|
-
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."));
|
|
2045
2213
|
process.exit(1);
|
|
2046
2214
|
}
|
|
2047
|
-
console.log(
|
|
2048
|
-
|
|
2049
|
-
|
|
2215
|
+
console.log(pc10.gray(`Repository: ${repoFullName}`));
|
|
2216
|
+
let { connections } = await getConnections(accessToken);
|
|
2217
|
+
let connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2050
2218
|
if (!connection) {
|
|
2051
|
-
|
|
2052
|
-
console.log(
|
|
2053
|
-
|
|
2219
|
+
const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2220
|
+
console.log(pc10.yellow(`
|
|
2221
|
+
Not connected to ${providerDisplayName}.`));
|
|
2222
|
+
const { shouldConnect } = await prompts7({
|
|
2223
|
+
type: "confirm",
|
|
2224
|
+
name: "shouldConnect",
|
|
2225
|
+
message: `Connect to ${providerDisplayName} now?`,
|
|
2226
|
+
initial: true
|
|
2227
|
+
});
|
|
2228
|
+
if (!shouldConnect) {
|
|
2229
|
+
console.log(pc10.gray("Cancelled."));
|
|
2230
|
+
process.exit(0);
|
|
2231
|
+
}
|
|
2232
|
+
await connectCommand(provider, { loginPrompt: false });
|
|
2233
|
+
const refreshed = await getConnections(accessToken);
|
|
2234
|
+
connections = refreshed.connections;
|
|
2235
|
+
connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2236
|
+
if (!connection) {
|
|
2237
|
+
console.error(pc10.red(`
|
|
2238
|
+
Connection to ${providerDisplayName} failed.`));
|
|
2239
|
+
process.exit(1);
|
|
2240
|
+
}
|
|
2241
|
+
console.log("");
|
|
2054
2242
|
}
|
|
2055
2243
|
const { projects } = await getConnectionProjects(accessToken, connection.id);
|
|
2056
2244
|
if (projects.length === 0) {
|
|
2057
|
-
console.error(
|
|
2245
|
+
console.error(pc10.red(`No projects found in your ${provider} account.`));
|
|
2058
2246
|
process.exit(1);
|
|
2059
2247
|
}
|
|
2060
2248
|
let selectedProject;
|
|
@@ -2063,21 +2251,21 @@ async function syncCommand(provider, options = {}) {
|
|
|
2063
2251
|
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase()
|
|
2064
2252
|
);
|
|
2065
2253
|
if (!found) {
|
|
2066
|
-
console.error(
|
|
2067
|
-
console.log(
|
|
2068
|
-
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}`)));
|
|
2069
2257
|
process.exit(1);
|
|
2070
2258
|
}
|
|
2071
2259
|
selectedProject = found;
|
|
2072
2260
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2073
2261
|
console.log("");
|
|
2074
|
-
console.log(
|
|
2075
|
-
console.log(
|
|
2076
|
-
console.log(
|
|
2077
|
-
console.log(
|
|
2078
|
-
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}`));
|
|
2079
2267
|
if (selectedProject.linkedRepo) {
|
|
2080
|
-
console.log(
|
|
2268
|
+
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2081
2269
|
}
|
|
2082
2270
|
console.log("");
|
|
2083
2271
|
}
|
|
@@ -2086,9 +2274,9 @@ async function syncCommand(provider, options = {}) {
|
|
|
2086
2274
|
if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
|
|
2087
2275
|
selectedProject = autoMatch.project;
|
|
2088
2276
|
const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
|
|
2089
|
-
console.log(
|
|
2277
|
+
console.log(pc10.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
|
|
2090
2278
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2091
|
-
console.log(
|
|
2279
|
+
console.log(pc10.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
|
|
2092
2280
|
const { useDetected } = await prompts7({
|
|
2093
2281
|
type: "confirm",
|
|
2094
2282
|
name: "useDetected",
|
|
@@ -2104,13 +2292,13 @@ async function syncCommand(provider, options = {}) {
|
|
|
2104
2292
|
selectedProject = projects[0];
|
|
2105
2293
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2106
2294
|
console.log("");
|
|
2107
|
-
console.log(
|
|
2108
|
-
console.log(
|
|
2109
|
-
console.log(
|
|
2110
|
-
console.log(
|
|
2111
|
-
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}`));
|
|
2112
2300
|
if (selectedProject.linkedRepo) {
|
|
2113
|
-
console.log(
|
|
2301
|
+
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2114
2302
|
}
|
|
2115
2303
|
console.log("");
|
|
2116
2304
|
const { continueAnyway } = await prompts7({
|
|
@@ -2120,14 +2308,14 @@ async function syncCommand(provider, options = {}) {
|
|
|
2120
2308
|
initial: false
|
|
2121
2309
|
});
|
|
2122
2310
|
if (!continueAnyway) {
|
|
2123
|
-
console.log(
|
|
2311
|
+
console.log(pc10.gray("Cancelled."));
|
|
2124
2312
|
process.exit(0);
|
|
2125
2313
|
}
|
|
2126
2314
|
}
|
|
2127
2315
|
} else {
|
|
2128
|
-
console.log(
|
|
2316
|
+
console.log(pc10.yellow(`
|
|
2129
2317
|
\u26A0\uFE0F No matching project found for ${repoFullName}`));
|
|
2130
|
-
console.log(
|
|
2318
|
+
console.log(pc10.gray("Select a project manually:\n"));
|
|
2131
2319
|
selectedProject = await promptProjectSelection(projects, repoFullName);
|
|
2132
2320
|
}
|
|
2133
2321
|
}
|
|
@@ -2135,13 +2323,13 @@ async function syncCommand(provider, options = {}) {
|
|
|
2135
2323
|
const autoMatch = findMatchingProject(projects, repoFullName);
|
|
2136
2324
|
if (autoMatch && autoMatch.project.id !== selectedProject.id) {
|
|
2137
2325
|
console.log("");
|
|
2138
|
-
console.log(
|
|
2139
|
-
console.log(
|
|
2140
|
-
console.log(
|
|
2141
|
-
console.log(
|
|
2142
|
-
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}`));
|
|
2143
2331
|
if (selectedProject.linkedRepo) {
|
|
2144
|
-
console.log(
|
|
2332
|
+
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2145
2333
|
}
|
|
2146
2334
|
console.log("");
|
|
2147
2335
|
const { continueAnyway } = await prompts7({
|
|
@@ -2151,7 +2339,7 @@ async function syncCommand(provider, options = {}) {
|
|
|
2151
2339
|
initial: false
|
|
2152
2340
|
});
|
|
2153
2341
|
if (!continueAnyway) {
|
|
2154
|
-
console.log(
|
|
2342
|
+
console.log(pc10.gray("Cancelled."));
|
|
2155
2343
|
process.exit(0);
|
|
2156
2344
|
}
|
|
2157
2345
|
}
|
|
@@ -2173,12 +2361,12 @@ async function syncCommand(provider, options = {}) {
|
|
|
2173
2361
|
initial: Math.max(0, vaultEnvs.indexOf("production"))
|
|
2174
2362
|
});
|
|
2175
2363
|
if (!selectedEnv) {
|
|
2176
|
-
console.log(
|
|
2364
|
+
console.log(pc10.gray("Cancelled."));
|
|
2177
2365
|
process.exit(0);
|
|
2178
2366
|
}
|
|
2179
2367
|
keywayEnv = selectedEnv;
|
|
2180
2368
|
if (!options.providerEnv) {
|
|
2181
|
-
providerEnv =
|
|
2369
|
+
providerEnv = mapToProviderEnvironment(provider, keywayEnv);
|
|
2182
2370
|
}
|
|
2183
2371
|
}
|
|
2184
2372
|
if (needsDirectionPrompt) {
|
|
@@ -2192,7 +2380,7 @@ async function syncCommand(provider, options = {}) {
|
|
|
2192
2380
|
]
|
|
2193
2381
|
});
|
|
2194
2382
|
if (!selectedDirection) {
|
|
2195
|
-
console.log(
|
|
2383
|
+
console.log(pc10.gray("Cancelled."));
|
|
2196
2384
|
process.exit(0);
|
|
2197
2385
|
}
|
|
2198
2386
|
direction = selectedDirection;
|
|
@@ -2209,9 +2397,9 @@ async function syncCommand(provider, options = {}) {
|
|
|
2209
2397
|
keywayEnv
|
|
2210
2398
|
);
|
|
2211
2399
|
if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
|
|
2212
|
-
console.log(
|
|
2400
|
+
console.log(pc10.yellow(`
|
|
2213
2401
|
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2214
|
-
console.log(
|
|
2402
|
+
console.log(pc10.gray(` (Use --environment to sync a different environment)`));
|
|
2215
2403
|
const { importFirst } = await prompts7({
|
|
2216
2404
|
type: "confirm",
|
|
2217
2405
|
name: "importFirst",
|
|
@@ -2253,7 +2441,7 @@ async function syncCommand(provider, options = {}) {
|
|
|
2253
2441
|
command: "sync",
|
|
2254
2442
|
error: truncateMessage(message)
|
|
2255
2443
|
});
|
|
2256
|
-
console.error(
|
|
2444
|
+
console.error(pc10.red(`
|
|
2257
2445
|
\u2717 ${message}`));
|
|
2258
2446
|
process.exit(1);
|
|
2259
2447
|
}
|
|
@@ -2270,33 +2458,33 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2270
2458
|
});
|
|
2271
2459
|
const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
|
|
2272
2460
|
if (totalChanges === 0) {
|
|
2273
|
-
console.log(
|
|
2461
|
+
console.log(pc10.green("\n\u2713 Already in sync. No changes needed."));
|
|
2274
2462
|
return;
|
|
2275
2463
|
}
|
|
2276
|
-
console.log(
|
|
2464
|
+
console.log(pc10.blue("\n\u{1F4CB} Sync Preview\n"));
|
|
2277
2465
|
if (preview.toCreate.length > 0) {
|
|
2278
|
-
console.log(
|
|
2279
|
-
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}`)));
|
|
2280
2468
|
if (preview.toCreate.length > 5) {
|
|
2281
|
-
console.log(
|
|
2469
|
+
console.log(pc10.gray(` ... and ${preview.toCreate.length - 5} more`));
|
|
2282
2470
|
}
|
|
2283
2471
|
}
|
|
2284
2472
|
if (preview.toUpdate.length > 0) {
|
|
2285
|
-
console.log(
|
|
2286
|
-
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}`)));
|
|
2287
2475
|
if (preview.toUpdate.length > 5) {
|
|
2288
|
-
console.log(
|
|
2476
|
+
console.log(pc10.gray(` ... and ${preview.toUpdate.length - 5} more`));
|
|
2289
2477
|
}
|
|
2290
2478
|
}
|
|
2291
2479
|
if (preview.toDelete.length > 0) {
|
|
2292
|
-
console.log(
|
|
2293
|
-
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}`)));
|
|
2294
2482
|
if (preview.toDelete.length > 5) {
|
|
2295
|
-
console.log(
|
|
2483
|
+
console.log(pc10.gray(` ... and ${preview.toDelete.length - 5} more`));
|
|
2296
2484
|
}
|
|
2297
2485
|
}
|
|
2298
2486
|
if (preview.toSkip.length > 0) {
|
|
2299
|
-
console.log(
|
|
2487
|
+
console.log(pc10.gray(` \u25CB ${preview.toSkip.length} unchanged`));
|
|
2300
2488
|
}
|
|
2301
2489
|
console.log("");
|
|
2302
2490
|
if (!skipConfirm) {
|
|
@@ -2308,11 +2496,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2308
2496
|
initial: true
|
|
2309
2497
|
});
|
|
2310
2498
|
if (!confirm) {
|
|
2311
|
-
console.log(
|
|
2499
|
+
console.log(pc10.gray("Cancelled."));
|
|
2312
2500
|
return;
|
|
2313
2501
|
}
|
|
2314
2502
|
}
|
|
2315
|
-
console.log(
|
|
2503
|
+
console.log(pc10.blue("\n\u23F3 Syncing...\n"));
|
|
2316
2504
|
const result = await executeSync(accessToken, repoFullName, {
|
|
2317
2505
|
connectionId,
|
|
2318
2506
|
projectId: project.id,
|
|
@@ -2322,11 +2510,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2322
2510
|
allowDelete
|
|
2323
2511
|
});
|
|
2324
2512
|
if (result.success) {
|
|
2325
|
-
console.log(
|
|
2326
|
-
console.log(
|
|
2327
|
-
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}`));
|
|
2328
2516
|
if (result.stats.deleted > 0) {
|
|
2329
|
-
console.log(
|
|
2517
|
+
console.log(pc10.gray(` Deleted: ${result.stats.deleted}`));
|
|
2330
2518
|
}
|
|
2331
2519
|
trackEvent(AnalyticsEvents.CLI_SYNC, {
|
|
2332
2520
|
provider,
|
|
@@ -2336,20 +2524,24 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2336
2524
|
deleted: result.stats.deleted
|
|
2337
2525
|
});
|
|
2338
2526
|
} else {
|
|
2339
|
-
console.error(
|
|
2527
|
+
console.error(pc10.red(`
|
|
2340
2528
|
\u2717 ${result.error}`));
|
|
2341
2529
|
process.exit(1);
|
|
2342
2530
|
}
|
|
2343
2531
|
}
|
|
2344
2532
|
|
|
2345
2533
|
// src/cli.ts
|
|
2534
|
+
process.on("unhandledRejection", (reason) => {
|
|
2535
|
+
console.error(pc11.red("Unhandled error:"), reason);
|
|
2536
|
+
process.exit(1);
|
|
2537
|
+
});
|
|
2346
2538
|
var program = new Command();
|
|
2347
2539
|
var TAGLINE = "Sync secrets with your team and infra";
|
|
2348
2540
|
var showBanner = () => {
|
|
2349
|
-
const text =
|
|
2541
|
+
const text = pc11.bold(pc11.cyan("Keyway CLI"));
|
|
2350
2542
|
console.log(`
|
|
2351
2543
|
${text}
|
|
2352
|
-
${
|
|
2544
|
+
${pc11.gray(TAGLINE)}
|
|
2353
2545
|
`);
|
|
2354
2546
|
};
|
|
2355
2547
|
showBanner();
|
|
@@ -2386,6 +2578,6 @@ program.command("sync <provider>").description("Sync secrets with a provider (e.
|
|
|
2386
2578
|
await syncCommand(provider, options);
|
|
2387
2579
|
});
|
|
2388
2580
|
program.parseAsync().catch((error) => {
|
|
2389
|
-
console.error(
|
|
2581
|
+
console.error(pc11.red("Error:"), error.message || error);
|
|
2390
2582
|
process.exit(1);
|
|
2391
2583
|
});
|