@keywaysh/cli 0.1.1 → 0.1.3
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 +334 -241
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,15 +8,18 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/cli.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
-
import
|
|
11
|
+
import pc10 from "picocolors";
|
|
12
12
|
|
|
13
13
|
// src/cmds/init.ts
|
|
14
|
-
import
|
|
14
|
+
import pc5 from "picocolors";
|
|
15
15
|
import prompts4 from "prompts";
|
|
16
16
|
import open2 from "open";
|
|
17
17
|
|
|
18
18
|
// src/utils/git.ts
|
|
19
19
|
import { execSync } from "child_process";
|
|
20
|
+
import fs from "fs";
|
|
21
|
+
import path from "path";
|
|
22
|
+
import pc from "picocolors";
|
|
20
23
|
function getCurrentRepoFullName() {
|
|
21
24
|
try {
|
|
22
25
|
if (!isGitRepository()) {
|
|
@@ -67,6 +70,29 @@ function parseGitHubUrl(url) {
|
|
|
67
70
|
}
|
|
68
71
|
throw new Error(`Invalid GitHub URL: ${url}`);
|
|
69
72
|
}
|
|
73
|
+
function checkEnvGitignore() {
|
|
74
|
+
try {
|
|
75
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
76
|
+
encoding: "utf-8",
|
|
77
|
+
stdio: "pipe"
|
|
78
|
+
}).trim();
|
|
79
|
+
const gitignorePath = path.join(gitRoot, ".gitignore");
|
|
80
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
84
|
+
const lines = content.split("\n").map((l) => l.trim());
|
|
85
|
+
const envPatterns = [".env", ".env*", ".env.*", "*.env"];
|
|
86
|
+
return envPatterns.some((pattern) => lines.includes(pattern));
|
|
87
|
+
} catch {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function warnIfEnvNotGitignored() {
|
|
92
|
+
if (!checkEnvGitignore()) {
|
|
93
|
+
console.log(pc.yellow("\u26A0\uFE0F .env files are not in .gitignore - secrets may be committed"));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
70
96
|
|
|
71
97
|
// src/config/internal.ts
|
|
72
98
|
var INTERNAL_API_URL = "https://api.keyway.sh";
|
|
@@ -76,7 +102,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
76
102
|
// package.json
|
|
77
103
|
var package_default = {
|
|
78
104
|
name: "@keywaysh/cli",
|
|
79
|
-
version: "0.1.
|
|
105
|
+
version: "0.1.3",
|
|
80
106
|
description: "One link to all your secrets",
|
|
81
107
|
type: "module",
|
|
82
108
|
bin: {
|
|
@@ -465,6 +491,25 @@ async function executeSync(accessToken, repoFullName, options) {
|
|
|
465
491
|
const wrapped = await handleResponse(response);
|
|
466
492
|
return wrapped.data;
|
|
467
493
|
}
|
|
494
|
+
async function getVaultEnvironments(accessToken, repoFullName) {
|
|
495
|
+
const [owner, repo] = repoFullName.split("/");
|
|
496
|
+
try {
|
|
497
|
+
const response = await fetchWithTimeout(
|
|
498
|
+
`${API_BASE_URL}/v1/vaults/${owner}/${repo}`,
|
|
499
|
+
{
|
|
500
|
+
method: "GET",
|
|
501
|
+
headers: {
|
|
502
|
+
"User-Agent": USER_AGENT,
|
|
503
|
+
Authorization: `Bearer ${accessToken}`
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
const wrapped = await handleResponse(response);
|
|
508
|
+
return wrapped.data.environments || ["production"];
|
|
509
|
+
} catch {
|
|
510
|
+
return ["production"];
|
|
511
|
+
}
|
|
512
|
+
}
|
|
468
513
|
async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
|
|
469
514
|
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/github/check-installation`, {
|
|
470
515
|
method: "POST",
|
|
@@ -482,32 +527,32 @@ async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
|
|
|
482
527
|
// src/utils/analytics.ts
|
|
483
528
|
import { PostHog } from "posthog-node";
|
|
484
529
|
import crypto from "crypto";
|
|
485
|
-
import
|
|
530
|
+
import path2 from "path";
|
|
486
531
|
import os from "os";
|
|
487
|
-
import
|
|
532
|
+
import fs2 from "fs";
|
|
488
533
|
var posthog = null;
|
|
489
534
|
var distinctId = null;
|
|
490
|
-
var CONFIG_DIR =
|
|
491
|
-
var ID_FILE =
|
|
535
|
+
var CONFIG_DIR = path2.join(os.homedir(), ".config", "keyway");
|
|
536
|
+
var ID_FILE = path2.join(CONFIG_DIR, "id.json");
|
|
492
537
|
var TELEMETRY_DISABLED = process.env.KEYWAY_DISABLE_TELEMETRY === "1";
|
|
493
538
|
var CI = process.env.CI === "true" || process.env.CI === "1";
|
|
494
539
|
function getDistinctId() {
|
|
495
540
|
if (distinctId) return distinctId;
|
|
496
541
|
try {
|
|
497
|
-
if (!
|
|
498
|
-
|
|
542
|
+
if (!fs2.existsSync(CONFIG_DIR)) {
|
|
543
|
+
fs2.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
499
544
|
}
|
|
500
|
-
if (
|
|
501
|
-
const content =
|
|
545
|
+
if (fs2.existsSync(ID_FILE)) {
|
|
546
|
+
const content = fs2.readFileSync(ID_FILE, "utf-8");
|
|
502
547
|
const config2 = JSON.parse(content);
|
|
503
548
|
distinctId = config2.distinctId;
|
|
504
549
|
return distinctId;
|
|
505
550
|
}
|
|
506
551
|
distinctId = crypto.randomUUID();
|
|
507
552
|
const config = { distinctId };
|
|
508
|
-
|
|
553
|
+
fs2.writeFileSync(ID_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
509
554
|
try {
|
|
510
|
-
|
|
555
|
+
fs2.chmodSync(ID_FILE, 384);
|
|
511
556
|
} catch {
|
|
512
557
|
}
|
|
513
558
|
return distinctId;
|
|
@@ -606,10 +651,10 @@ var AnalyticsEvents = {
|
|
|
606
651
|
};
|
|
607
652
|
|
|
608
653
|
// src/cmds/readme.ts
|
|
609
|
-
import
|
|
610
|
-
import
|
|
654
|
+
import fs3 from "fs";
|
|
655
|
+
import path3 from "path";
|
|
611
656
|
import prompts from "prompts";
|
|
612
|
-
import
|
|
657
|
+
import pc2 from "picocolors";
|
|
613
658
|
function generateBadge(repo) {
|
|
614
659
|
return `[](https://www.keyway.sh/vaults/${repo})`;
|
|
615
660
|
}
|
|
@@ -635,8 +680,8 @@ ${readmeContent}`;
|
|
|
635
680
|
function findReadmePath(cwd) {
|
|
636
681
|
const candidates = ["README.md", "readme.md", "Readme.md"];
|
|
637
682
|
for (const candidate of candidates) {
|
|
638
|
-
const candidatePath =
|
|
639
|
-
if (
|
|
683
|
+
const candidatePath = path3.join(cwd, candidate);
|
|
684
|
+
if (fs3.existsSync(candidatePath)) {
|
|
640
685
|
return candidatePath;
|
|
641
686
|
}
|
|
642
687
|
}
|
|
@@ -647,7 +692,7 @@ async function ensureReadme(repoName, cwd) {
|
|
|
647
692
|
if (existing) return existing;
|
|
648
693
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
649
694
|
if (!isInteractive3) {
|
|
650
|
-
console.log(
|
|
695
|
+
console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
651
696
|
return null;
|
|
652
697
|
}
|
|
653
698
|
const { confirm } = await prompts(
|
|
@@ -662,14 +707,14 @@ async function ensureReadme(repoName, cwd) {
|
|
|
662
707
|
}
|
|
663
708
|
);
|
|
664
709
|
if (!confirm) {
|
|
665
|
-
console.log(
|
|
710
|
+
console.log(pc2.yellow("Skipping badge insertion (no README)."));
|
|
666
711
|
return null;
|
|
667
712
|
}
|
|
668
|
-
const defaultPath =
|
|
713
|
+
const defaultPath = path3.join(cwd, "README.md");
|
|
669
714
|
const content = `# ${repoName}
|
|
670
715
|
|
|
671
716
|
`;
|
|
672
|
-
|
|
717
|
+
fs3.writeFileSync(defaultPath, content, "utf-8");
|
|
673
718
|
return defaultPath;
|
|
674
719
|
}
|
|
675
720
|
async function addBadgeToReadme(silent = false) {
|
|
@@ -681,29 +726,29 @@ async function addBadgeToReadme(silent = false) {
|
|
|
681
726
|
const readmePath = await ensureReadme(repo, cwd);
|
|
682
727
|
if (!readmePath) return false;
|
|
683
728
|
const badge = generateBadge(repo);
|
|
684
|
-
const content =
|
|
729
|
+
const content = fs3.readFileSync(readmePath, "utf-8");
|
|
685
730
|
const updated = insertBadgeIntoReadme(content, badge);
|
|
686
731
|
if (updated === content) {
|
|
687
732
|
if (!silent) {
|
|
688
|
-
console.log(
|
|
733
|
+
console.log(pc2.gray("Keyway badge already present in README."));
|
|
689
734
|
}
|
|
690
735
|
return false;
|
|
691
736
|
}
|
|
692
|
-
|
|
737
|
+
fs3.writeFileSync(readmePath, updated, "utf-8");
|
|
693
738
|
if (!silent) {
|
|
694
|
-
console.log(
|
|
739
|
+
console.log(pc2.green(`\u2713 Keyway badge added to ${path3.basename(readmePath)}`));
|
|
695
740
|
}
|
|
696
741
|
return true;
|
|
697
742
|
}
|
|
698
743
|
|
|
699
744
|
// src/cmds/push.ts
|
|
700
|
-
import
|
|
701
|
-
import
|
|
702
|
-
import
|
|
745
|
+
import pc4 from "picocolors";
|
|
746
|
+
import fs4 from "fs";
|
|
747
|
+
import path4 from "path";
|
|
703
748
|
import prompts3 from "prompts";
|
|
704
749
|
|
|
705
750
|
// src/cmds/login.ts
|
|
706
|
-
import
|
|
751
|
+
import pc3 from "picocolors";
|
|
707
752
|
import readline from "readline";
|
|
708
753
|
import open from "open";
|
|
709
754
|
import prompts2 from "prompts";
|
|
@@ -730,17 +775,17 @@ async function promptYesNo(question, defaultYes = true) {
|
|
|
730
775
|
});
|
|
731
776
|
}
|
|
732
777
|
async function runLoginFlow() {
|
|
733
|
-
console.log(
|
|
778
|
+
console.log(pc3.blue("\u{1F510} Starting Keyway login...\n"));
|
|
734
779
|
const repoName = detectGitRepo();
|
|
735
780
|
const start = await startDeviceLogin(repoName);
|
|
736
781
|
const verifyUrl = start.verificationUriComplete || start.verificationUri;
|
|
737
782
|
if (!verifyUrl) {
|
|
738
783
|
throw new Error("Missing verification URL from the auth server.");
|
|
739
784
|
}
|
|
740
|
-
console.log(`Code: ${
|
|
785
|
+
console.log(`Code: ${pc3.bold(pc3.green(start.userCode))}`);
|
|
741
786
|
console.log("Waiting for auth...");
|
|
742
787
|
open(verifyUrl).catch(() => {
|
|
743
|
-
console.log(
|
|
788
|
+
console.log(pc3.gray(`Open this URL in your browser: ${verifyUrl}`));
|
|
744
789
|
});
|
|
745
790
|
const pollIntervalMs = (start.interval ?? 5) * 1e3;
|
|
746
791
|
const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
|
|
@@ -769,9 +814,9 @@ async function runLoginFlow() {
|
|
|
769
814
|
login_method: "device"
|
|
770
815
|
});
|
|
771
816
|
}
|
|
772
|
-
console.log(
|
|
817
|
+
console.log(pc3.green("\n\u2713 Login successful"));
|
|
773
818
|
if (result.githubLogin) {
|
|
774
|
-
console.log(`Authenticated GitHub user: ${
|
|
819
|
+
console.log(`Authenticated GitHub user: ${pc3.cyan(result.githubLogin)}`);
|
|
775
820
|
}
|
|
776
821
|
return result.keywayToken;
|
|
777
822
|
}
|
|
@@ -784,7 +829,7 @@ async function ensureLogin(options = {}) {
|
|
|
784
829
|
return envToken;
|
|
785
830
|
}
|
|
786
831
|
if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
|
|
787
|
-
console.warn(
|
|
832
|
+
console.warn(pc3.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
|
|
788
833
|
}
|
|
789
834
|
const stored = await getStoredAuth();
|
|
790
835
|
if (stored?.keywayToken) {
|
|
@@ -804,16 +849,16 @@ async function ensureLogin(options = {}) {
|
|
|
804
849
|
async function runTokenLogin() {
|
|
805
850
|
const repoName = detectGitRepo();
|
|
806
851
|
if (repoName) {
|
|
807
|
-
console.log(`\u{1F4C1} Detected: ${
|
|
852
|
+
console.log(`\u{1F4C1} Detected: ${pc3.cyan(repoName)}`);
|
|
808
853
|
}
|
|
809
854
|
const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
|
|
810
855
|
const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
|
|
811
856
|
console.log("Opening GitHub...");
|
|
812
857
|
open(url).catch(() => {
|
|
813
|
-
console.log(
|
|
858
|
+
console.log(pc3.gray(`Open this URL in your browser: ${url}`));
|
|
814
859
|
});
|
|
815
|
-
console.log(
|
|
816
|
-
console.log(
|
|
860
|
+
console.log(pc3.gray("Select the detected repo (or scope manually)."));
|
|
861
|
+
console.log(pc3.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
|
|
817
862
|
const { token } = await prompts2(
|
|
818
863
|
{
|
|
819
864
|
type: "password",
|
|
@@ -850,7 +895,7 @@ async function runTokenLogin() {
|
|
|
850
895
|
github_username: validation.username,
|
|
851
896
|
login_method: "pat"
|
|
852
897
|
});
|
|
853
|
-
console.log(
|
|
898
|
+
console.log(pc3.green("\u2705 Authenticated"), `as ${pc3.cyan(`@${validation.username}`)}`);
|
|
854
899
|
return trimmedToken;
|
|
855
900
|
}
|
|
856
901
|
async function loginCommand(options = {}) {
|
|
@@ -866,20 +911,20 @@ async function loginCommand(options = {}) {
|
|
|
866
911
|
command: "login",
|
|
867
912
|
error: truncateMessage(message)
|
|
868
913
|
});
|
|
869
|
-
console.error(
|
|
914
|
+
console.error(pc3.red(`
|
|
870
915
|
\u2717 ${message}`));
|
|
871
916
|
process.exit(1);
|
|
872
917
|
}
|
|
873
918
|
}
|
|
874
919
|
async function logoutCommand() {
|
|
875
920
|
clearAuth();
|
|
876
|
-
console.log(
|
|
877
|
-
console.log(
|
|
921
|
+
console.log(pc3.green("\u2713 Logged out of Keyway"));
|
|
922
|
+
console.log(pc3.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
878
923
|
}
|
|
879
924
|
|
|
880
925
|
// src/cmds/push.ts
|
|
881
926
|
function deriveEnvFromFile(file) {
|
|
882
|
-
const base =
|
|
927
|
+
const base = path4.basename(file);
|
|
883
928
|
const match = base.match(/\.env(?:\.(.+))?$/);
|
|
884
929
|
if (match) {
|
|
885
930
|
return match[1] || "development";
|
|
@@ -888,15 +933,15 @@ function deriveEnvFromFile(file) {
|
|
|
888
933
|
}
|
|
889
934
|
function discoverEnvCandidates(cwd) {
|
|
890
935
|
try {
|
|
891
|
-
const entries =
|
|
936
|
+
const entries = fs4.readdirSync(cwd);
|
|
892
937
|
const hasEnvLocal = entries.includes(".env.local");
|
|
893
938
|
if (hasEnvLocal) {
|
|
894
|
-
console.log(
|
|
939
|
+
console.log(pc4.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
|
|
895
940
|
}
|
|
896
941
|
const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
|
|
897
|
-
const fullPath =
|
|
942
|
+
const fullPath = path4.join(cwd, name);
|
|
898
943
|
try {
|
|
899
|
-
const stat =
|
|
944
|
+
const stat = fs4.statSync(fullPath);
|
|
900
945
|
if (!stat.isFile()) return null;
|
|
901
946
|
return { file: name, env: deriveEnvFromFile(name) };
|
|
902
947
|
} catch {
|
|
@@ -917,7 +962,7 @@ function discoverEnvCandidates(cwd) {
|
|
|
917
962
|
}
|
|
918
963
|
async function pushCommand(options) {
|
|
919
964
|
try {
|
|
920
|
-
console.log(
|
|
965
|
+
console.log(pc4.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
921
966
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
922
967
|
let environment = options.env;
|
|
923
968
|
let envFile = options.file;
|
|
@@ -959,8 +1004,8 @@ async function pushCommand(options) {
|
|
|
959
1004
|
message: "Path to env file:",
|
|
960
1005
|
validate: (value) => {
|
|
961
1006
|
if (!value) return "Path is required";
|
|
962
|
-
const resolved =
|
|
963
|
-
if (!
|
|
1007
|
+
const resolved = path4.resolve(process.cwd(), value);
|
|
1008
|
+
if (!fs4.existsSync(resolved)) return `File not found: ${value}`;
|
|
964
1009
|
return true;
|
|
965
1010
|
}
|
|
966
1011
|
},
|
|
@@ -980,8 +1025,8 @@ async function pushCommand(options) {
|
|
|
980
1025
|
if (!envFile) {
|
|
981
1026
|
envFile = ".env";
|
|
982
1027
|
}
|
|
983
|
-
let envFilePath =
|
|
984
|
-
if (!
|
|
1028
|
+
let envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1029
|
+
if (!fs4.existsSync(envFilePath)) {
|
|
985
1030
|
if (!isInteractive3) {
|
|
986
1031
|
throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
|
|
987
1032
|
}
|
|
@@ -992,8 +1037,8 @@ async function pushCommand(options) {
|
|
|
992
1037
|
message: `File not found: ${envFile}. Enter an env file path to use:`,
|
|
993
1038
|
validate: (value) => {
|
|
994
1039
|
if (!value || typeof value !== "string") return "Path is required";
|
|
995
|
-
const resolved =
|
|
996
|
-
if (!
|
|
1040
|
+
const resolved = path4.resolve(process.cwd(), value);
|
|
1041
|
+
if (!fs4.existsSync(resolved)) return `File not found: ${value}`;
|
|
997
1042
|
return true;
|
|
998
1043
|
}
|
|
999
1044
|
},
|
|
@@ -1007,9 +1052,9 @@ async function pushCommand(options) {
|
|
|
1007
1052
|
throw new Error("Push cancelled (no env file provided).");
|
|
1008
1053
|
}
|
|
1009
1054
|
envFile = newPath.trim();
|
|
1010
|
-
envFilePath =
|
|
1055
|
+
envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1011
1056
|
}
|
|
1012
|
-
const content =
|
|
1057
|
+
const content = fs4.readFileSync(envFilePath, "utf-8");
|
|
1013
1058
|
if (content.trim().length === 0) {
|
|
1014
1059
|
throw new Error(`File is empty: ${envFile}`);
|
|
1015
1060
|
}
|
|
@@ -1017,11 +1062,11 @@ async function pushCommand(options) {
|
|
|
1017
1062
|
const trimmed = line.trim();
|
|
1018
1063
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1019
1064
|
});
|
|
1020
|
-
console.log(`File: ${
|
|
1021
|
-
console.log(`Environment: ${
|
|
1022
|
-
console.log(`Variables: ${
|
|
1065
|
+
console.log(`File: ${pc4.cyan(envFile)}`);
|
|
1066
|
+
console.log(`Environment: ${pc4.cyan(environment)}`);
|
|
1067
|
+
console.log(`Variables: ${pc4.cyan(lines.length.toString())}`);
|
|
1023
1068
|
const repoFullName = getCurrentRepoFullName();
|
|
1024
|
-
console.log(`Repository: ${
|
|
1069
|
+
console.log(`Repository: ${pc4.cyan(repoFullName)}`);
|
|
1025
1070
|
if (!options.yes) {
|
|
1026
1071
|
const isInteractive4 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1027
1072
|
if (!isInteractive4) {
|
|
@@ -1041,7 +1086,7 @@ async function pushCommand(options) {
|
|
|
1041
1086
|
}
|
|
1042
1087
|
);
|
|
1043
1088
|
if (!confirm) {
|
|
1044
|
-
console.log(
|
|
1089
|
+
console.log(pc4.yellow("Push aborted."));
|
|
1045
1090
|
return;
|
|
1046
1091
|
}
|
|
1047
1092
|
}
|
|
@@ -1053,20 +1098,20 @@ async function pushCommand(options) {
|
|
|
1053
1098
|
});
|
|
1054
1099
|
console.log("\nUploading secrets...");
|
|
1055
1100
|
const response = await pushSecrets(repoFullName, environment, content, accessToken);
|
|
1056
|
-
console.log(
|
|
1101
|
+
console.log(pc4.green("\n\u2713 " + response.message));
|
|
1057
1102
|
if (response.stats) {
|
|
1058
1103
|
const { created, updated, deleted } = response.stats;
|
|
1059
1104
|
const parts = [];
|
|
1060
|
-
if (created > 0) parts.push(
|
|
1061
|
-
if (updated > 0) parts.push(
|
|
1062
|
-
if (deleted > 0) parts.push(
|
|
1105
|
+
if (created > 0) parts.push(pc4.green(`+${created} created`));
|
|
1106
|
+
if (updated > 0) parts.push(pc4.yellow(`~${updated} updated`));
|
|
1107
|
+
if (deleted > 0) parts.push(pc4.red(`-${deleted} deleted`));
|
|
1063
1108
|
if (parts.length > 0) {
|
|
1064
1109
|
console.log(`Stats: ${parts.join(", ")}`);
|
|
1065
1110
|
}
|
|
1066
1111
|
}
|
|
1067
1112
|
console.log(`
|
|
1068
1113
|
Your secrets are now encrypted and stored securely.`);
|
|
1069
|
-
console.log(`To retrieve them, run: ${
|
|
1114
|
+
console.log(`To retrieve them, run: ${pc4.cyan(`keyway pull --env ${environment}`)}`);
|
|
1070
1115
|
await shutdownAnalytics();
|
|
1071
1116
|
} catch (error) {
|
|
1072
1117
|
let message;
|
|
@@ -1079,13 +1124,13 @@ Your secrets are now encrypted and stored securely.`);
|
|
|
1079
1124
|
const availableEnvs = envNotFoundMatch[2];
|
|
1080
1125
|
message = `Environment '${requestedEnv}' does not exist in this vault.`;
|
|
1081
1126
|
hint = `Available environments: ${availableEnvs}
|
|
1082
|
-
Use ${
|
|
1127
|
+
Use ${pc4.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1083
1128
|
}
|
|
1084
1129
|
if (error.statusCode === 403 && error.upgradeUrl) {
|
|
1085
|
-
hint = `${
|
|
1130
|
+
hint = `${pc4.yellow("\u26A1")} Upgrade to Pro: ${pc4.cyan(error.upgradeUrl)}`;
|
|
1086
1131
|
} else if (error.statusCode === 403 && message.toLowerCase().includes("read-only")) {
|
|
1087
1132
|
message = "This vault is read-only on your current plan.";
|
|
1088
|
-
hint = `Upgrade to Pro to unlock editing: ${
|
|
1133
|
+
hint = `Upgrade to Pro to unlock editing: ${pc4.cyan("https://keyway.sh/settings")}`;
|
|
1089
1134
|
}
|
|
1090
1135
|
} else if (error instanceof Error) {
|
|
1091
1136
|
message = truncateMessage(error.message);
|
|
@@ -1097,10 +1142,10 @@ Use ${pc3.cyan(`keyway push --env <environment>`)} to specify one, or create '${
|
|
|
1097
1142
|
error: message
|
|
1098
1143
|
});
|
|
1099
1144
|
await shutdownAnalytics();
|
|
1100
|
-
console.error(
|
|
1145
|
+
console.error(pc4.red(`
|
|
1101
1146
|
\u2717 ${message}`));
|
|
1102
1147
|
if (hint) {
|
|
1103
|
-
console.error(
|
|
1148
|
+
console.error(pc4.gray(`
|
|
1104
1149
|
${hint}`));
|
|
1105
1150
|
}
|
|
1106
1151
|
process.exit(1);
|
|
@@ -1139,8 +1184,8 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1139
1184
|
throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
|
|
1140
1185
|
}
|
|
1141
1186
|
console.log("");
|
|
1142
|
-
console.log(
|
|
1143
|
-
console.log(
|
|
1187
|
+
console.log(pc5.gray(" Keyway uses a GitHub App for secure access."));
|
|
1188
|
+
console.log(pc5.gray(" Installing the app will also log you in."));
|
|
1144
1189
|
console.log("");
|
|
1145
1190
|
const { shouldProceed } = await prompts4({
|
|
1146
1191
|
type: "confirm",
|
|
@@ -1153,11 +1198,11 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1153
1198
|
}
|
|
1154
1199
|
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1155
1200
|
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1156
|
-
console.log(
|
|
1201
|
+
console.log(pc5.gray("\n Opening browser..."));
|
|
1157
1202
|
await open2(installUrl);
|
|
1158
1203
|
console.log("");
|
|
1159
|
-
console.log(
|
|
1160
|
-
console.log(
|
|
1204
|
+
console.log(pc5.blue("\u23F3 Waiting for installation & authorization..."));
|
|
1205
|
+
console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1161
1206
|
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1162
1207
|
const startTime = Date.now();
|
|
1163
1208
|
let accessToken = null;
|
|
@@ -1172,7 +1217,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1172
1217
|
githubLogin: result.githubLogin,
|
|
1173
1218
|
expiresAt: result.expiresAt
|
|
1174
1219
|
});
|
|
1175
|
-
console.log(
|
|
1220
|
+
console.log(pc5.green("\u2713 Signed in!"));
|
|
1176
1221
|
if (result.githubLogin) {
|
|
1177
1222
|
identifyUser(result.githubLogin, {
|
|
1178
1223
|
github_username: result.githubLogin,
|
|
@@ -1184,18 +1229,18 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1184
1229
|
if (accessToken) {
|
|
1185
1230
|
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1186
1231
|
if (installStatus.installed) {
|
|
1187
|
-
console.log(
|
|
1232
|
+
console.log(pc5.green("\u2713 GitHub App installed!"));
|
|
1188
1233
|
console.log("");
|
|
1189
1234
|
return accessToken;
|
|
1190
1235
|
}
|
|
1191
1236
|
}
|
|
1192
|
-
process.stdout.write(
|
|
1237
|
+
process.stdout.write(pc5.gray("."));
|
|
1193
1238
|
} catch {
|
|
1194
1239
|
}
|
|
1195
1240
|
}
|
|
1196
1241
|
console.log("");
|
|
1197
|
-
console.log(
|
|
1198
|
-
console.log(
|
|
1242
|
+
console.log(pc5.yellow("\u26A0 Timed out waiting for setup."));
|
|
1243
|
+
console.log(pc5.gray(` Install the GitHub App: ${installUrl}`));
|
|
1199
1244
|
throw new Error("Setup timed out. Please try again.");
|
|
1200
1245
|
}
|
|
1201
1246
|
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
@@ -1205,7 +1250,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1205
1250
|
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1206
1251
|
} catch (error) {
|
|
1207
1252
|
if (error instanceof APIError && error.statusCode === 401) {
|
|
1208
|
-
console.log(
|
|
1253
|
+
console.log(pc5.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1209
1254
|
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1210
1255
|
clearAuth2();
|
|
1211
1256
|
return null;
|
|
@@ -1216,13 +1261,13 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1216
1261
|
return accessToken;
|
|
1217
1262
|
}
|
|
1218
1263
|
console.log("");
|
|
1219
|
-
console.log(
|
|
1264
|
+
console.log(pc5.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1220
1265
|
console.log("");
|
|
1221
|
-
console.log(
|
|
1222
|
-
console.log(
|
|
1266
|
+
console.log(pc5.gray(" The Keyway GitHub App is required to securely manage secrets."));
|
|
1267
|
+
console.log(pc5.gray(" It only requests minimal permissions (repository metadata)."));
|
|
1223
1268
|
console.log("");
|
|
1224
1269
|
if (!isInteractive2()) {
|
|
1225
|
-
console.log(
|
|
1270
|
+
console.log(pc5.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1226
1271
|
throw new Error("GitHub App installation required.");
|
|
1227
1272
|
}
|
|
1228
1273
|
const { shouldInstall } = await prompts4({
|
|
@@ -1232,50 +1277,50 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1232
1277
|
initial: true
|
|
1233
1278
|
});
|
|
1234
1279
|
if (!shouldInstall) {
|
|
1235
|
-
console.log(
|
|
1280
|
+
console.log(pc5.gray(`
|
|
1236
1281
|
You can install later: ${status.installUrl}`));
|
|
1237
1282
|
throw new Error("GitHub App installation required.");
|
|
1238
1283
|
}
|
|
1239
|
-
console.log(
|
|
1284
|
+
console.log(pc5.gray("\n Opening browser..."));
|
|
1240
1285
|
await open2(status.installUrl);
|
|
1241
1286
|
console.log("");
|
|
1242
|
-
console.log(
|
|
1243
|
-
console.log(
|
|
1287
|
+
console.log(pc5.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1288
|
+
console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1244
1289
|
const startTime = Date.now();
|
|
1245
1290
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1246
1291
|
await sleep2(POLL_INTERVAL_MS);
|
|
1247
1292
|
try {
|
|
1248
1293
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1249
1294
|
if (pollStatus.installed) {
|
|
1250
|
-
console.log(
|
|
1295
|
+
console.log(pc5.green("\u2713 GitHub App installed!"));
|
|
1251
1296
|
console.log("");
|
|
1252
1297
|
return accessToken;
|
|
1253
1298
|
}
|
|
1254
|
-
process.stdout.write(
|
|
1299
|
+
process.stdout.write(pc5.gray("."));
|
|
1255
1300
|
} catch {
|
|
1256
1301
|
}
|
|
1257
1302
|
}
|
|
1258
1303
|
console.log("");
|
|
1259
|
-
console.log(
|
|
1260
|
-
console.log(
|
|
1304
|
+
console.log(pc5.yellow("\u26A0 Timed out waiting for installation."));
|
|
1305
|
+
console.log(pc5.gray(` You can install the GitHub App later: ${status.installUrl}`));
|
|
1261
1306
|
throw new Error("GitHub App installation timed out.");
|
|
1262
1307
|
}
|
|
1263
1308
|
async function initCommand(options = {}) {
|
|
1264
1309
|
try {
|
|
1265
1310
|
const repoFullName = getCurrentRepoFullName();
|
|
1266
1311
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1267
|
-
console.log(
|
|
1268
|
-
console.log(` ${
|
|
1312
|
+
console.log(pc5.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1313
|
+
console.log(` ${pc5.gray("Repository:")} ${pc5.white(repoFullName)}`);
|
|
1269
1314
|
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1270
1315
|
allowPrompt: options.loginPrompt !== false
|
|
1271
1316
|
});
|
|
1272
1317
|
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1273
1318
|
await initVault(repoFullName, accessToken);
|
|
1274
|
-
console.log(
|
|
1319
|
+
console.log(pc5.green("\u2713 Vault created!"));
|
|
1275
1320
|
try {
|
|
1276
1321
|
const badgeAdded = await addBadgeToReadme(true);
|
|
1277
1322
|
if (badgeAdded) {
|
|
1278
|
-
console.log(
|
|
1323
|
+
console.log(pc5.green("\u2713 Badge added to README.md"));
|
|
1279
1324
|
}
|
|
1280
1325
|
} catch {
|
|
1281
1326
|
}
|
|
@@ -1283,7 +1328,7 @@ async function initCommand(options = {}) {
|
|
|
1283
1328
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1284
1329
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1285
1330
|
if (envCandidates.length > 0 && isInteractive3) {
|
|
1286
|
-
console.log(
|
|
1331
|
+
console.log(pc5.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1287
1332
|
`));
|
|
1288
1333
|
const { shouldPush } = await prompts4({
|
|
1289
1334
|
type: "confirm",
|
|
@@ -1297,25 +1342,25 @@ async function initCommand(options = {}) {
|
|
|
1297
1342
|
return;
|
|
1298
1343
|
}
|
|
1299
1344
|
}
|
|
1300
|
-
console.log(
|
|
1345
|
+
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1301
1346
|
console.log("");
|
|
1302
1347
|
if (envCandidates.length === 0) {
|
|
1303
|
-
console.log(` ${
|
|
1304
|
-
console.log(` ${
|
|
1348
|
+
console.log(` ${pc5.yellow("\u2192")} Create a ${pc5.cyan(".env")} file with your secrets`);
|
|
1349
|
+
console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync them
|
|
1305
1350
|
`);
|
|
1306
1351
|
} else {
|
|
1307
|
-
console.log(` ${
|
|
1352
|
+
console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync your secrets
|
|
1308
1353
|
`);
|
|
1309
1354
|
}
|
|
1310
|
-
console.log(` ${
|
|
1355
|
+
console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
|
|
1311
1356
|
console.log("");
|
|
1312
1357
|
await shutdownAnalytics();
|
|
1313
1358
|
} catch (error) {
|
|
1314
1359
|
if (error instanceof APIError) {
|
|
1315
1360
|
if (error.statusCode === 409) {
|
|
1316
|
-
console.log(
|
|
1317
|
-
console.log(` ${
|
|
1318
|
-
console.log(` ${
|
|
1361
|
+
console.log(pc5.green("\n\u2713 Already initialized!\n"));
|
|
1362
|
+
console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync your secrets`);
|
|
1363
|
+
console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(`${DASHBOARD_URL}/${getCurrentRepoFullName()}`)}`);
|
|
1319
1364
|
console.log("");
|
|
1320
1365
|
await shutdownAnalytics();
|
|
1321
1366
|
return;
|
|
@@ -1323,15 +1368,15 @@ async function initCommand(options = {}) {
|
|
|
1323
1368
|
if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
|
|
1324
1369
|
const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
|
|
1325
1370
|
console.log("");
|
|
1326
|
-
console.log(
|
|
1371
|
+
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1327
1372
|
console.log("");
|
|
1328
|
-
console.log(` ${
|
|
1373
|
+
console.log(` ${pc5.yellow("\u26A1")} ${pc5.bold("Plan Limit Reached")}`);
|
|
1329
1374
|
console.log("");
|
|
1330
|
-
console.log(
|
|
1375
|
+
console.log(pc5.white(` ${error.message}`));
|
|
1331
1376
|
console.log("");
|
|
1332
|
-
console.log(` ${
|
|
1377
|
+
console.log(` ${pc5.cyan("Upgrade now \u2192")} ${pc5.underline(upgradeUrl)}`);
|
|
1333
1378
|
console.log("");
|
|
1334
|
-
console.log(
|
|
1379
|
+
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1335
1380
|
console.log("");
|
|
1336
1381
|
await shutdownAnalytics();
|
|
1337
1382
|
process.exit(1);
|
|
@@ -1343,25 +1388,25 @@ async function initCommand(options = {}) {
|
|
|
1343
1388
|
error: message
|
|
1344
1389
|
});
|
|
1345
1390
|
await shutdownAnalytics();
|
|
1346
|
-
console.error(
|
|
1391
|
+
console.error(pc5.red(`
|
|
1347
1392
|
\u2717 ${message}`));
|
|
1348
1393
|
process.exit(1);
|
|
1349
1394
|
}
|
|
1350
1395
|
}
|
|
1351
1396
|
|
|
1352
1397
|
// src/cmds/pull.ts
|
|
1353
|
-
import
|
|
1354
|
-
import
|
|
1355
|
-
import
|
|
1398
|
+
import pc6 from "picocolors";
|
|
1399
|
+
import fs5 from "fs";
|
|
1400
|
+
import path5 from "path";
|
|
1356
1401
|
import prompts5 from "prompts";
|
|
1357
1402
|
async function pullCommand(options) {
|
|
1358
1403
|
try {
|
|
1359
1404
|
const environment = options.env || "development";
|
|
1360
1405
|
const envFile = options.file || ".env";
|
|
1361
|
-
console.log(
|
|
1362
|
-
console.log(`Environment: ${
|
|
1406
|
+
console.log(pc6.blue("\u{1F510} Pulling secrets from Keyway...\n"));
|
|
1407
|
+
console.log(`Environment: ${pc6.cyan(environment)}`);
|
|
1363
1408
|
const repoFullName = getCurrentRepoFullName();
|
|
1364
|
-
console.log(`Repository: ${
|
|
1409
|
+
console.log(`Repository: ${pc6.cyan(repoFullName)}`);
|
|
1365
1410
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1366
1411
|
trackEvent(AnalyticsEvents.CLI_PULL, {
|
|
1367
1412
|
repoFullName,
|
|
@@ -1369,11 +1414,11 @@ async function pullCommand(options) {
|
|
|
1369
1414
|
});
|
|
1370
1415
|
console.log("\nDownloading secrets...");
|
|
1371
1416
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1372
|
-
const envFilePath =
|
|
1373
|
-
if (
|
|
1417
|
+
const envFilePath = path5.resolve(process.cwd(), envFile);
|
|
1418
|
+
if (fs5.existsSync(envFilePath)) {
|
|
1374
1419
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1375
1420
|
if (options.yes) {
|
|
1376
|
-
console.log(
|
|
1421
|
+
console.log(pc6.yellow(`
|
|
1377
1422
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1378
1423
|
} else if (!isInteractive3) {
|
|
1379
1424
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
@@ -1392,21 +1437,21 @@ async function pullCommand(options) {
|
|
|
1392
1437
|
}
|
|
1393
1438
|
);
|
|
1394
1439
|
if (!confirm) {
|
|
1395
|
-
console.log(
|
|
1440
|
+
console.log(pc6.yellow("Pull aborted."));
|
|
1396
1441
|
return;
|
|
1397
1442
|
}
|
|
1398
1443
|
}
|
|
1399
1444
|
}
|
|
1400
|
-
|
|
1445
|
+
fs5.writeFileSync(envFilePath, response.content, "utf-8");
|
|
1401
1446
|
const lines = response.content.split("\n").filter((line) => {
|
|
1402
1447
|
const trimmed = line.trim();
|
|
1403
1448
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1404
1449
|
});
|
|
1405
|
-
console.log(
|
|
1450
|
+
console.log(pc6.green(`
|
|
1406
1451
|
\u2713 Secrets downloaded successfully`));
|
|
1407
1452
|
console.log(`
|
|
1408
|
-
File: ${
|
|
1409
|
-
console.log(`Variables: ${
|
|
1453
|
+
File: ${pc6.cyan(envFile)}`);
|
|
1454
|
+
console.log(`Variables: ${pc6.cyan(lines.length.toString())}`);
|
|
1410
1455
|
await shutdownAnalytics();
|
|
1411
1456
|
} catch (error) {
|
|
1412
1457
|
const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
|
|
@@ -1415,14 +1460,14 @@ File: ${pc5.cyan(envFile)}`);
|
|
|
1415
1460
|
error: message
|
|
1416
1461
|
});
|
|
1417
1462
|
await shutdownAnalytics();
|
|
1418
|
-
console.error(
|
|
1463
|
+
console.error(pc6.red(`
|
|
1419
1464
|
\u2717 ${message}`));
|
|
1420
1465
|
process.exit(1);
|
|
1421
1466
|
}
|
|
1422
1467
|
}
|
|
1423
1468
|
|
|
1424
1469
|
// src/cmds/doctor.ts
|
|
1425
|
-
import
|
|
1470
|
+
import pc7 from "picocolors";
|
|
1426
1471
|
|
|
1427
1472
|
// src/core/doctor.ts
|
|
1428
1473
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -1676,9 +1721,9 @@ async function runAllChecks(options = {}) {
|
|
|
1676
1721
|
// src/cmds/doctor.ts
|
|
1677
1722
|
function formatSummary(results) {
|
|
1678
1723
|
const parts = [
|
|
1679
|
-
|
|
1680
|
-
results.summary.warn > 0 ?
|
|
1681
|
-
results.summary.fail > 0 ?
|
|
1724
|
+
pc7.green(`${results.summary.pass} passed`),
|
|
1725
|
+
results.summary.warn > 0 ? pc7.yellow(`${results.summary.warn} warnings`) : null,
|
|
1726
|
+
results.summary.fail > 0 ? pc7.red(`${results.summary.fail} failed`) : null
|
|
1682
1727
|
].filter(Boolean);
|
|
1683
1728
|
return parts.join(", ");
|
|
1684
1729
|
}
|
|
@@ -1695,20 +1740,20 @@ async function doctorCommand(options = {}) {
|
|
|
1695
1740
|
process.stdout.write(JSON.stringify(results, null, 0) + "\n");
|
|
1696
1741
|
process.exit(results.exitCode);
|
|
1697
1742
|
}
|
|
1698
|
-
console.log(
|
|
1743
|
+
console.log(pc7.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
|
|
1699
1744
|
results.checks.forEach((check) => {
|
|
1700
|
-
const icon = check.status === "pass" ?
|
|
1701
|
-
const detail = check.detail ?
|
|
1745
|
+
const icon = check.status === "pass" ? pc7.green("\u2713") : check.status === "warn" ? pc7.yellow("!") : pc7.red("\u2717");
|
|
1746
|
+
const detail = check.detail ? pc7.dim(` \u2014 ${check.detail}`) : "";
|
|
1702
1747
|
console.log(` ${icon} ${check.name}${detail}`);
|
|
1703
1748
|
});
|
|
1704
1749
|
console.log(`
|
|
1705
1750
|
Summary: ${formatSummary(results)}`);
|
|
1706
1751
|
if (results.summary.fail > 0) {
|
|
1707
|
-
console.log(
|
|
1752
|
+
console.log(pc7.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
|
|
1708
1753
|
} else if (results.summary.warn > 0) {
|
|
1709
|
-
console.log(
|
|
1754
|
+
console.log(pc7.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
|
|
1710
1755
|
} else {
|
|
1711
|
-
console.log(
|
|
1756
|
+
console.log(pc7.green("\u2728 All checks passed! Your environment is ready for Keyway."));
|
|
1712
1757
|
}
|
|
1713
1758
|
process.exit(results.exitCode);
|
|
1714
1759
|
} catch (error) {
|
|
@@ -1729,7 +1774,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1729
1774
|
};
|
|
1730
1775
|
process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
|
|
1731
1776
|
} else {
|
|
1732
|
-
console.error(
|
|
1777
|
+
console.error(pc7.red(`
|
|
1733
1778
|
\u2717 ${message}`));
|
|
1734
1779
|
}
|
|
1735
1780
|
process.exit(1);
|
|
@@ -1737,7 +1782,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1737
1782
|
}
|
|
1738
1783
|
|
|
1739
1784
|
// src/cmds/connect.ts
|
|
1740
|
-
import
|
|
1785
|
+
import pc8 from "picocolors";
|
|
1741
1786
|
import open3 from "open";
|
|
1742
1787
|
import prompts6 from "prompts";
|
|
1743
1788
|
async function connectCommand(provider, options = {}) {
|
|
@@ -1747,13 +1792,13 @@ async function connectCommand(provider, options = {}) {
|
|
|
1747
1792
|
const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
|
|
1748
1793
|
if (!providerInfo) {
|
|
1749
1794
|
const available = providers.map((p) => p.name).join(", ");
|
|
1750
|
-
console.error(
|
|
1751
|
-
console.log(
|
|
1795
|
+
console.error(pc8.red(`Unknown provider: ${provider}`));
|
|
1796
|
+
console.log(pc8.gray(`Available providers: ${available || "none"}`));
|
|
1752
1797
|
process.exit(1);
|
|
1753
1798
|
}
|
|
1754
1799
|
if (!providerInfo.configured) {
|
|
1755
|
-
console.error(
|
|
1756
|
-
console.log(
|
|
1800
|
+
console.error(pc8.red(`Provider ${providerInfo.displayName} is not configured on the server.`));
|
|
1801
|
+
console.log(pc8.gray("Contact your administrator to enable this integration."));
|
|
1757
1802
|
process.exit(1);
|
|
1758
1803
|
}
|
|
1759
1804
|
const { connections } = await getConnections(accessToken);
|
|
@@ -1766,20 +1811,20 @@ async function connectCommand(provider, options = {}) {
|
|
|
1766
1811
|
initial: false
|
|
1767
1812
|
});
|
|
1768
1813
|
if (!reconnect) {
|
|
1769
|
-
console.log(
|
|
1814
|
+
console.log(pc8.gray("Keeping existing connection."));
|
|
1770
1815
|
return;
|
|
1771
1816
|
}
|
|
1772
1817
|
}
|
|
1773
|
-
console.log(
|
|
1818
|
+
console.log(pc8.blue(`
|
|
1774
1819
|
Connecting to ${providerInfo.displayName}...
|
|
1775
1820
|
`));
|
|
1776
1821
|
const authUrl = getProviderAuthUrl(provider.toLowerCase());
|
|
1777
1822
|
const startTime = /* @__PURE__ */ new Date();
|
|
1778
|
-
console.log(
|
|
1779
|
-
console.log(
|
|
1823
|
+
console.log(pc8.gray("Opening browser for authorization..."));
|
|
1824
|
+
console.log(pc8.gray(`If the browser doesn't open, visit: ${authUrl}`));
|
|
1780
1825
|
await open3(authUrl).catch(() => {
|
|
1781
1826
|
});
|
|
1782
|
-
console.log(
|
|
1827
|
+
console.log(pc8.gray("Waiting for authorization..."));
|
|
1783
1828
|
const maxAttempts = 60;
|
|
1784
1829
|
let attempts = 0;
|
|
1785
1830
|
let connected = false;
|
|
@@ -1793,7 +1838,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1793
1838
|
);
|
|
1794
1839
|
if (newConn) {
|
|
1795
1840
|
connected = true;
|
|
1796
|
-
console.log(
|
|
1841
|
+
console.log(pc8.green(`
|
|
1797
1842
|
\u2713 Connected to ${providerInfo.displayName}!`));
|
|
1798
1843
|
break;
|
|
1799
1844
|
}
|
|
@@ -1801,8 +1846,8 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1801
1846
|
}
|
|
1802
1847
|
}
|
|
1803
1848
|
if (!connected) {
|
|
1804
|
-
console.log(
|
|
1805
|
-
console.log(
|
|
1849
|
+
console.log(pc8.red("\n\u2717 Authorization timeout."));
|
|
1850
|
+
console.log(pc8.gray("Run `keyway connections` to check if the connection was established."));
|
|
1806
1851
|
}
|
|
1807
1852
|
trackEvent(AnalyticsEvents.CLI_CONNECT, {
|
|
1808
1853
|
provider: provider.toLowerCase(),
|
|
@@ -1814,7 +1859,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1814
1859
|
command: "connect",
|
|
1815
1860
|
error: truncateMessage(message)
|
|
1816
1861
|
});
|
|
1817
|
-
console.error(
|
|
1862
|
+
console.error(pc8.red(`
|
|
1818
1863
|
\u2717 ${message}`));
|
|
1819
1864
|
process.exit(1);
|
|
1820
1865
|
}
|
|
@@ -1824,24 +1869,24 @@ async function connectionsCommand(options = {}) {
|
|
|
1824
1869
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1825
1870
|
const { connections } = await getConnections(accessToken);
|
|
1826
1871
|
if (connections.length === 0) {
|
|
1827
|
-
console.log(
|
|
1828
|
-
console.log(
|
|
1829
|
-
console.log(
|
|
1872
|
+
console.log(pc8.gray("No provider connections found."));
|
|
1873
|
+
console.log(pc8.gray("\nConnect to a provider with: keyway connect <provider>"));
|
|
1874
|
+
console.log(pc8.gray("Available providers: vercel"));
|
|
1830
1875
|
return;
|
|
1831
1876
|
}
|
|
1832
|
-
console.log(
|
|
1877
|
+
console.log(pc8.blue("\n\u{1F4E1} Provider Connections\n"));
|
|
1833
1878
|
for (const conn of connections) {
|
|
1834
1879
|
const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
|
|
1835
|
-
const teamInfo = conn.providerTeamId ?
|
|
1880
|
+
const teamInfo = conn.providerTeamId ? pc8.gray(` (Team: ${conn.providerTeamId})`) : "";
|
|
1836
1881
|
const date = new Date(conn.createdAt).toLocaleDateString();
|
|
1837
|
-
console.log(` ${
|
|
1838
|
-
console.log(
|
|
1839
|
-
console.log(
|
|
1882
|
+
console.log(` ${pc8.green("\u25CF")} ${pc8.bold(providerName)}${teamInfo}`);
|
|
1883
|
+
console.log(pc8.gray(` Connected: ${date}`));
|
|
1884
|
+
console.log(pc8.gray(` ID: ${conn.id}`));
|
|
1840
1885
|
console.log("");
|
|
1841
1886
|
}
|
|
1842
1887
|
} catch (error) {
|
|
1843
1888
|
const message = error instanceof Error ? error.message : "Failed to list connections";
|
|
1844
|
-
console.error(
|
|
1889
|
+
console.error(pc8.red(`
|
|
1845
1890
|
\u2717 ${message}`));
|
|
1846
1891
|
process.exit(1);
|
|
1847
1892
|
}
|
|
@@ -1852,7 +1897,7 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1852
1897
|
const { connections } = await getConnections(accessToken);
|
|
1853
1898
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
1854
1899
|
if (!connection) {
|
|
1855
|
-
console.log(
|
|
1900
|
+
console.log(pc8.gray(`No connection found for provider: ${provider}`));
|
|
1856
1901
|
return;
|
|
1857
1902
|
}
|
|
1858
1903
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
@@ -1863,11 +1908,11 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1863
1908
|
initial: false
|
|
1864
1909
|
});
|
|
1865
1910
|
if (!confirm) {
|
|
1866
|
-
console.log(
|
|
1911
|
+
console.log(pc8.gray("Cancelled."));
|
|
1867
1912
|
return;
|
|
1868
1913
|
}
|
|
1869
1914
|
await deleteConnection(accessToken, connection.id);
|
|
1870
|
-
console.log(
|
|
1915
|
+
console.log(pc8.green(`
|
|
1871
1916
|
\u2713 Disconnected from ${providerName}`));
|
|
1872
1917
|
trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
|
|
1873
1918
|
provider: provider.toLowerCase()
|
|
@@ -1878,15 +1923,24 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1878
1923
|
command: "disconnect",
|
|
1879
1924
|
error: truncateMessage(message)
|
|
1880
1925
|
});
|
|
1881
|
-
console.error(
|
|
1926
|
+
console.error(pc8.red(`
|
|
1882
1927
|
\u2717 ${message}`));
|
|
1883
1928
|
process.exit(1);
|
|
1884
1929
|
}
|
|
1885
1930
|
}
|
|
1886
1931
|
|
|
1887
1932
|
// src/cmds/sync.ts
|
|
1888
|
-
import
|
|
1933
|
+
import pc9 from "picocolors";
|
|
1889
1934
|
import prompts7 from "prompts";
|
|
1935
|
+
function mapToVercelEnvironment(keywayEnv) {
|
|
1936
|
+
const mapping = {
|
|
1937
|
+
production: "production",
|
|
1938
|
+
staging: "preview",
|
|
1939
|
+
dev: "development",
|
|
1940
|
+
development: "development"
|
|
1941
|
+
};
|
|
1942
|
+
return mapping[keywayEnv.toLowerCase()] || "production";
|
|
1943
|
+
}
|
|
1890
1944
|
function findMatchingProject(projects, repoFullName) {
|
|
1891
1945
|
const repoFullNameLower = repoFullName.toLowerCase();
|
|
1892
1946
|
const repoName = repoFullName.split("/")[1]?.toLowerCase();
|
|
@@ -1926,11 +1980,11 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
1926
1980
|
let title = p.name;
|
|
1927
1981
|
const badges = [];
|
|
1928
1982
|
if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
|
|
1929
|
-
badges.push(
|
|
1983
|
+
badges.push(pc9.green("\u2190 linked"));
|
|
1930
1984
|
} else if (p.name.toLowerCase() === repoName) {
|
|
1931
|
-
badges.push(
|
|
1985
|
+
badges.push(pc9.green("\u2190 same name"));
|
|
1932
1986
|
} else if (p.linkedRepo) {
|
|
1933
|
-
badges.push(
|
|
1987
|
+
badges.push(pc9.gray(`\u2192 ${p.linkedRepo}`));
|
|
1934
1988
|
}
|
|
1935
1989
|
if (badges.length > 0) {
|
|
1936
1990
|
title = `${p.name} ${badges.join(" ")}`;
|
|
@@ -1944,7 +1998,7 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
1944
1998
|
choices
|
|
1945
1999
|
});
|
|
1946
2000
|
if (!projectChoice) {
|
|
1947
|
-
console.log(
|
|
2001
|
+
console.log(pc9.gray("Cancelled."));
|
|
1948
2002
|
process.exit(0);
|
|
1949
2003
|
}
|
|
1950
2004
|
return projects.find((p) => p.id === projectChoice);
|
|
@@ -1952,28 +2006,28 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
1952
2006
|
async function syncCommand(provider, options = {}) {
|
|
1953
2007
|
try {
|
|
1954
2008
|
if (options.pull && options.allowDelete) {
|
|
1955
|
-
console.error(
|
|
1956
|
-
console.log(
|
|
2009
|
+
console.error(pc9.red("Error: --allow-delete cannot be used with --pull"));
|
|
2010
|
+
console.log(pc9.gray("The --allow-delete flag is only for push operations."));
|
|
1957
2011
|
process.exit(1);
|
|
1958
2012
|
}
|
|
1959
2013
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1960
2014
|
const repoFullName = detectGitRepo();
|
|
1961
2015
|
if (!repoFullName) {
|
|
1962
|
-
console.error(
|
|
1963
|
-
console.log(
|
|
2016
|
+
console.error(pc9.red("Could not detect Git repository."));
|
|
2017
|
+
console.log(pc9.gray("Run this command from a Git repository directory."));
|
|
1964
2018
|
process.exit(1);
|
|
1965
2019
|
}
|
|
1966
|
-
console.log(
|
|
2020
|
+
console.log(pc9.gray(`Repository: ${repoFullName}`));
|
|
1967
2021
|
const { connections } = await getConnections(accessToken);
|
|
1968
2022
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
1969
2023
|
if (!connection) {
|
|
1970
|
-
console.error(
|
|
1971
|
-
console.log(
|
|
2024
|
+
console.error(pc9.red(`Not connected to ${provider}.`));
|
|
2025
|
+
console.log(pc9.gray(`Run: keyway connect ${provider}`));
|
|
1972
2026
|
process.exit(1);
|
|
1973
2027
|
}
|
|
1974
2028
|
const { projects } = await getConnectionProjects(accessToken, connection.id);
|
|
1975
2029
|
if (projects.length === 0) {
|
|
1976
|
-
console.error(
|
|
2030
|
+
console.error(pc9.red(`No projects found in your ${provider} account.`));
|
|
1977
2031
|
process.exit(1);
|
|
1978
2032
|
}
|
|
1979
2033
|
let selectedProject;
|
|
@@ -1982,21 +2036,21 @@ async function syncCommand(provider, options = {}) {
|
|
|
1982
2036
|
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase()
|
|
1983
2037
|
);
|
|
1984
2038
|
if (!found) {
|
|
1985
|
-
console.error(
|
|
1986
|
-
console.log(
|
|
1987
|
-
projects.forEach((p) => console.log(
|
|
2039
|
+
console.error(pc9.red(`Project not found: ${options.project}`));
|
|
2040
|
+
console.log(pc9.gray("Available projects:"));
|
|
2041
|
+
projects.forEach((p) => console.log(pc9.gray(` - ${p.name}`)));
|
|
1988
2042
|
process.exit(1);
|
|
1989
2043
|
}
|
|
1990
2044
|
selectedProject = found;
|
|
1991
2045
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
1992
2046
|
console.log("");
|
|
1993
|
-
console.log(
|
|
1994
|
-
console.log(
|
|
1995
|
-
console.log(
|
|
1996
|
-
console.log(
|
|
1997
|
-
console.log(
|
|
2047
|
+
console.log(pc9.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"));
|
|
2048
|
+
console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2049
|
+
console.log(pc9.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"));
|
|
2050
|
+
console.log(pc9.yellow(` Current repo: ${repoFullName}`));
|
|
2051
|
+
console.log(pc9.yellow(` Selected project: ${selectedProject.name}`));
|
|
1998
2052
|
if (selectedProject.linkedRepo) {
|
|
1999
|
-
console.log(
|
|
2053
|
+
console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2000
2054
|
}
|
|
2001
2055
|
console.log("");
|
|
2002
2056
|
}
|
|
@@ -2005,9 +2059,9 @@ async function syncCommand(provider, options = {}) {
|
|
|
2005
2059
|
if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
|
|
2006
2060
|
selectedProject = autoMatch.project;
|
|
2007
2061
|
const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
|
|
2008
|
-
console.log(
|
|
2062
|
+
console.log(pc9.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
|
|
2009
2063
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2010
|
-
console.log(
|
|
2064
|
+
console.log(pc9.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
|
|
2011
2065
|
const { useDetected } = await prompts7({
|
|
2012
2066
|
type: "confirm",
|
|
2013
2067
|
name: "useDetected",
|
|
@@ -2023,13 +2077,13 @@ async function syncCommand(provider, options = {}) {
|
|
|
2023
2077
|
selectedProject = projects[0];
|
|
2024
2078
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2025
2079
|
console.log("");
|
|
2026
|
-
console.log(
|
|
2027
|
-
console.log(
|
|
2028
|
-
console.log(
|
|
2029
|
-
console.log(
|
|
2030
|
-
console.log(
|
|
2080
|
+
console.log(pc9.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"));
|
|
2081
|
+
console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2082
|
+
console.log(pc9.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"));
|
|
2083
|
+
console.log(pc9.yellow(` Current repo: ${repoFullName}`));
|
|
2084
|
+
console.log(pc9.yellow(` Only project: ${selectedProject.name}`));
|
|
2031
2085
|
if (selectedProject.linkedRepo) {
|
|
2032
|
-
console.log(
|
|
2086
|
+
console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2033
2087
|
}
|
|
2034
2088
|
console.log("");
|
|
2035
2089
|
const { continueAnyway } = await prompts7({
|
|
@@ -2039,14 +2093,14 @@ async function syncCommand(provider, options = {}) {
|
|
|
2039
2093
|
initial: false
|
|
2040
2094
|
});
|
|
2041
2095
|
if (!continueAnyway) {
|
|
2042
|
-
console.log(
|
|
2096
|
+
console.log(pc9.gray("Cancelled."));
|
|
2043
2097
|
process.exit(0);
|
|
2044
2098
|
}
|
|
2045
2099
|
}
|
|
2046
2100
|
} else {
|
|
2047
|
-
console.log(
|
|
2101
|
+
console.log(pc9.yellow(`
|
|
2048
2102
|
\u26A0\uFE0F No matching project found for ${repoFullName}`));
|
|
2049
|
-
console.log(
|
|
2103
|
+
console.log(pc9.gray("Select a project manually:\n"));
|
|
2050
2104
|
selectedProject = await promptProjectSelection(projects, repoFullName);
|
|
2051
2105
|
}
|
|
2052
2106
|
}
|
|
@@ -2054,13 +2108,13 @@ async function syncCommand(provider, options = {}) {
|
|
|
2054
2108
|
const autoMatch = findMatchingProject(projects, repoFullName);
|
|
2055
2109
|
if (autoMatch && autoMatch.project.id !== selectedProject.id) {
|
|
2056
2110
|
console.log("");
|
|
2057
|
-
console.log(
|
|
2058
|
-
console.log(
|
|
2059
|
-
console.log(
|
|
2060
|
-
console.log(
|
|
2061
|
-
console.log(
|
|
2111
|
+
console.log(pc9.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"));
|
|
2112
|
+
console.log(pc9.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
|
|
2113
|
+
console.log(pc9.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"));
|
|
2114
|
+
console.log(pc9.yellow(` Current repo: ${repoFullName}`));
|
|
2115
|
+
console.log(pc9.yellow(` Selected project: ${selectedProject.name}`));
|
|
2062
2116
|
if (selectedProject.linkedRepo) {
|
|
2063
|
-
console.log(
|
|
2117
|
+
console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2064
2118
|
}
|
|
2065
2119
|
console.log("");
|
|
2066
2120
|
const { continueAnyway } = await prompts7({
|
|
@@ -2070,18 +2124,56 @@ async function syncCommand(provider, options = {}) {
|
|
|
2070
2124
|
initial: false
|
|
2071
2125
|
});
|
|
2072
2126
|
if (!continueAnyway) {
|
|
2073
|
-
console.log(
|
|
2127
|
+
console.log(pc9.gray("Cancelled."));
|
|
2074
2128
|
process.exit(0);
|
|
2075
2129
|
}
|
|
2076
2130
|
}
|
|
2077
2131
|
}
|
|
2078
|
-
const keywayEnv = options.environment || "production";
|
|
2079
|
-
const providerEnv = options.providerEnv || "production";
|
|
2080
|
-
const direction = options.pull ? "pull" : "push";
|
|
2081
2132
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2133
|
+
let keywayEnv = options.environment;
|
|
2134
|
+
let providerEnv = options.providerEnv;
|
|
2135
|
+
let direction = options.push ? "push" : options.pull ? "pull" : void 0;
|
|
2136
|
+
const needsEnvPrompt = !options.environment;
|
|
2137
|
+
const needsDirectionPrompt = !direction;
|
|
2138
|
+
if (needsEnvPrompt || needsDirectionPrompt) {
|
|
2139
|
+
if (needsEnvPrompt) {
|
|
2140
|
+
const vaultEnvs = await getVaultEnvironments(accessToken, repoFullName);
|
|
2141
|
+
const { selectedEnv } = await prompts7({
|
|
2142
|
+
type: "select",
|
|
2143
|
+
name: "selectedEnv",
|
|
2144
|
+
message: "Keyway environment:",
|
|
2145
|
+
choices: vaultEnvs.map((e) => ({ title: e, value: e })),
|
|
2146
|
+
initial: Math.max(0, vaultEnvs.indexOf("production"))
|
|
2147
|
+
});
|
|
2148
|
+
if (!selectedEnv) {
|
|
2149
|
+
console.log(pc9.gray("Cancelled."));
|
|
2150
|
+
process.exit(0);
|
|
2151
|
+
}
|
|
2152
|
+
keywayEnv = selectedEnv;
|
|
2153
|
+
if (!options.providerEnv) {
|
|
2154
|
+
providerEnv = mapToVercelEnvironment(keywayEnv);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
if (needsDirectionPrompt) {
|
|
2158
|
+
const { selectedDirection } = await prompts7({
|
|
2159
|
+
type: "select",
|
|
2160
|
+
name: "selectedDirection",
|
|
2161
|
+
message: "Sync direction:",
|
|
2162
|
+
choices: [
|
|
2163
|
+
{ title: `Keyway \u2192 ${providerName}`, value: "push" },
|
|
2164
|
+
{ title: `${providerName} \u2192 Keyway`, value: "pull" }
|
|
2165
|
+
]
|
|
2166
|
+
});
|
|
2167
|
+
if (!selectedDirection) {
|
|
2168
|
+
console.log(pc9.gray("Cancelled."));
|
|
2169
|
+
process.exit(0);
|
|
2170
|
+
}
|
|
2171
|
+
direction = selectedDirection;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
keywayEnv = keywayEnv || "production";
|
|
2175
|
+
providerEnv = providerEnv || "production";
|
|
2176
|
+
direction = direction || "push";
|
|
2085
2177
|
const status = await getSyncStatus(
|
|
2086
2178
|
accessToken,
|
|
2087
2179
|
repoFullName,
|
|
@@ -2089,10 +2181,10 @@ async function syncCommand(provider, options = {}) {
|
|
|
2089
2181
|
selectedProject.id,
|
|
2090
2182
|
keywayEnv
|
|
2091
2183
|
);
|
|
2092
|
-
if (status.isFirstSync &&
|
|
2093
|
-
console.log(
|
|
2184
|
+
if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
|
|
2185
|
+
console.log(pc9.yellow(`
|
|
2094
2186
|
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2095
|
-
console.log(
|
|
2187
|
+
console.log(pc9.gray(` (Use --environment to sync a different environment)`));
|
|
2096
2188
|
const { importFirst } = await prompts7({
|
|
2097
2189
|
type: "confirm",
|
|
2098
2190
|
name: "importFirst",
|
|
@@ -2134,7 +2226,7 @@ async function syncCommand(provider, options = {}) {
|
|
|
2134
2226
|
command: "sync",
|
|
2135
2227
|
error: truncateMessage(message)
|
|
2136
2228
|
});
|
|
2137
|
-
console.error(
|
|
2229
|
+
console.error(pc9.red(`
|
|
2138
2230
|
\u2717 ${message}`));
|
|
2139
2231
|
process.exit(1);
|
|
2140
2232
|
}
|
|
@@ -2151,33 +2243,33 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2151
2243
|
});
|
|
2152
2244
|
const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
|
|
2153
2245
|
if (totalChanges === 0) {
|
|
2154
|
-
console.log(
|
|
2246
|
+
console.log(pc9.green("\n\u2713 Already in sync. No changes needed."));
|
|
2155
2247
|
return;
|
|
2156
2248
|
}
|
|
2157
|
-
console.log(
|
|
2249
|
+
console.log(pc9.blue("\n\u{1F4CB} Sync Preview\n"));
|
|
2158
2250
|
if (preview.toCreate.length > 0) {
|
|
2159
|
-
console.log(
|
|
2160
|
-
preview.toCreate.slice(0, 5).forEach((key) => console.log(
|
|
2251
|
+
console.log(pc9.green(` + ${preview.toCreate.length} to create`));
|
|
2252
|
+
preview.toCreate.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
|
|
2161
2253
|
if (preview.toCreate.length > 5) {
|
|
2162
|
-
console.log(
|
|
2254
|
+
console.log(pc9.gray(` ... and ${preview.toCreate.length - 5} more`));
|
|
2163
2255
|
}
|
|
2164
2256
|
}
|
|
2165
2257
|
if (preview.toUpdate.length > 0) {
|
|
2166
|
-
console.log(
|
|
2167
|
-
preview.toUpdate.slice(0, 5).forEach((key) => console.log(
|
|
2258
|
+
console.log(pc9.yellow(` ~ ${preview.toUpdate.length} to update`));
|
|
2259
|
+
preview.toUpdate.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
|
|
2168
2260
|
if (preview.toUpdate.length > 5) {
|
|
2169
|
-
console.log(
|
|
2261
|
+
console.log(pc9.gray(` ... and ${preview.toUpdate.length - 5} more`));
|
|
2170
2262
|
}
|
|
2171
2263
|
}
|
|
2172
2264
|
if (preview.toDelete.length > 0) {
|
|
2173
|
-
console.log(
|
|
2174
|
-
preview.toDelete.slice(0, 5).forEach((key) => console.log(
|
|
2265
|
+
console.log(pc9.red(` - ${preview.toDelete.length} to delete`));
|
|
2266
|
+
preview.toDelete.slice(0, 5).forEach((key) => console.log(pc9.gray(` ${key}`)));
|
|
2175
2267
|
if (preview.toDelete.length > 5) {
|
|
2176
|
-
console.log(
|
|
2268
|
+
console.log(pc9.gray(` ... and ${preview.toDelete.length - 5} more`));
|
|
2177
2269
|
}
|
|
2178
2270
|
}
|
|
2179
2271
|
if (preview.toSkip.length > 0) {
|
|
2180
|
-
console.log(
|
|
2272
|
+
console.log(pc9.gray(` \u25CB ${preview.toSkip.length} unchanged`));
|
|
2181
2273
|
}
|
|
2182
2274
|
console.log("");
|
|
2183
2275
|
if (!skipConfirm) {
|
|
@@ -2189,11 +2281,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2189
2281
|
initial: true
|
|
2190
2282
|
});
|
|
2191
2283
|
if (!confirm) {
|
|
2192
|
-
console.log(
|
|
2284
|
+
console.log(pc9.gray("Cancelled."));
|
|
2193
2285
|
return;
|
|
2194
2286
|
}
|
|
2195
2287
|
}
|
|
2196
|
-
console.log(
|
|
2288
|
+
console.log(pc9.blue("\n\u23F3 Syncing...\n"));
|
|
2197
2289
|
const result = await executeSync(accessToken, repoFullName, {
|
|
2198
2290
|
connectionId,
|
|
2199
2291
|
projectId: project.id,
|
|
@@ -2203,11 +2295,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2203
2295
|
allowDelete
|
|
2204
2296
|
});
|
|
2205
2297
|
if (result.success) {
|
|
2206
|
-
console.log(
|
|
2207
|
-
console.log(
|
|
2208
|
-
console.log(
|
|
2298
|
+
console.log(pc9.green("\u2713 Sync complete"));
|
|
2299
|
+
console.log(pc9.gray(` Created: ${result.stats.created}`));
|
|
2300
|
+
console.log(pc9.gray(` Updated: ${result.stats.updated}`));
|
|
2209
2301
|
if (result.stats.deleted > 0) {
|
|
2210
|
-
console.log(
|
|
2302
|
+
console.log(pc9.gray(` Deleted: ${result.stats.deleted}`));
|
|
2211
2303
|
}
|
|
2212
2304
|
trackEvent(AnalyticsEvents.CLI_SYNC, {
|
|
2213
2305
|
provider,
|
|
@@ -2217,7 +2309,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2217
2309
|
deleted: result.stats.deleted
|
|
2218
2310
|
});
|
|
2219
2311
|
} else {
|
|
2220
|
-
console.error(
|
|
2312
|
+
console.error(pc9.red(`
|
|
2221
2313
|
\u2717 ${result.error}`));
|
|
2222
2314
|
process.exit(1);
|
|
2223
2315
|
}
|
|
@@ -2227,13 +2319,14 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2227
2319
|
var program = new Command();
|
|
2228
2320
|
var TAGLINE = "Sync secrets with your team and infra";
|
|
2229
2321
|
var showBanner = () => {
|
|
2230
|
-
const text =
|
|
2322
|
+
const text = pc10.bold(pc10.cyan("Keyway CLI"));
|
|
2231
2323
|
console.log(`
|
|
2232
2324
|
${text}
|
|
2233
|
-
${
|
|
2325
|
+
${pc10.gray(TAGLINE)}
|
|
2234
2326
|
`);
|
|
2235
2327
|
};
|
|
2236
2328
|
showBanner();
|
|
2329
|
+
warnIfEnvNotGitignored();
|
|
2237
2330
|
program.name("keyway").description(TAGLINE).version(package_default.version);
|
|
2238
2331
|
program.command("init").description("Initialize a vault for the current repository").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (options) => {
|
|
2239
2332
|
await initCommand(options);
|
|
@@ -2262,10 +2355,10 @@ program.command("connections").description("List your provider connections").opt
|
|
|
2262
2355
|
program.command("disconnect <provider>").description("Disconnect from a provider").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
|
|
2263
2356
|
await disconnectCommand(provider, options);
|
|
2264
2357
|
});
|
|
2265
|
-
program.command("sync <provider>").description("Sync secrets with a provider (e.g., vercel)").option("--pull", "Import secrets from provider to Keyway").option("-e, --environment <env>", "Keyway environment (default: production)").option("--provider-env <env>", "Provider environment (default: production)").option("--project <project>", "Provider project name or ID").option("--allow-delete", "Allow deleting secrets not in source").option("-y, --yes", "Skip confirmation prompt").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
|
|
2358
|
+
program.command("sync <provider>").description("Sync secrets with a provider (e.g., vercel)").option("--push", "Export secrets from Keyway to provider").option("--pull", "Import secrets from provider to Keyway").option("-e, --environment <env>", "Keyway environment (default: production)").option("--provider-env <env>", "Provider environment (default: production)").option("--project <project>", "Provider project name or ID").option("--allow-delete", "Allow deleting secrets not in source").option("-y, --yes", "Skip confirmation prompt").option("--no-login-prompt", "Fail instead of prompting to login if unauthenticated").action(async (provider, options) => {
|
|
2266
2359
|
await syncCommand(provider, options);
|
|
2267
2360
|
});
|
|
2268
2361
|
program.parseAsync().catch((error) => {
|
|
2269
|
-
console.error(
|
|
2362
|
+
console.error(pc10.red("Error:"), error.message || error);
|
|
2270
2363
|
process.exit(1);
|
|
2271
2364
|
});
|