@keywaysh/cli 0.1.0 → 0.1.2
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/auth-QLPQ24HZ.js +12 -0
- package/dist/chunk-F4C46224.js +102 -0
- package/dist/cli.js +366 -347
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
clearAuth,
|
|
4
|
+
getAuthFilePath,
|
|
5
|
+
getStoredAuth,
|
|
6
|
+
saveAuthToken
|
|
7
|
+
} from "./chunk-F4C46224.js";
|
|
2
8
|
|
|
3
9
|
// src/cli.ts
|
|
4
10
|
import { Command } from "commander";
|
|
5
|
-
import
|
|
11
|
+
import pc10 from "picocolors";
|
|
6
12
|
|
|
7
13
|
// src/cmds/init.ts
|
|
8
|
-
import
|
|
14
|
+
import pc5 from "picocolors";
|
|
9
15
|
import prompts4 from "prompts";
|
|
10
16
|
import open2 from "open";
|
|
11
17
|
|
|
12
18
|
// src/utils/git.ts
|
|
13
19
|
import { execSync } from "child_process";
|
|
20
|
+
import fs from "fs";
|
|
21
|
+
import path from "path";
|
|
22
|
+
import pc from "picocolors";
|
|
14
23
|
function getCurrentRepoFullName() {
|
|
15
24
|
try {
|
|
16
25
|
if (!isGitRepository()) {
|
|
@@ -61,6 +70,29 @@ function parseGitHubUrl(url) {
|
|
|
61
70
|
}
|
|
62
71
|
throw new Error(`Invalid GitHub URL: ${url}`);
|
|
63
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
|
+
}
|
|
64
96
|
|
|
65
97
|
// src/config/internal.ts
|
|
66
98
|
var INTERNAL_API_URL = "https://api.keyway.sh";
|
|
@@ -70,7 +102,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
70
102
|
// package.json
|
|
71
103
|
var package_default = {
|
|
72
104
|
name: "@keywaysh/cli",
|
|
73
|
-
version: "0.1.
|
|
105
|
+
version: "0.1.2",
|
|
74
106
|
description: "One link to all your secrets",
|
|
75
107
|
type: "module",
|
|
76
108
|
bin: {
|
|
@@ -459,6 +491,25 @@ async function executeSync(accessToken, repoFullName, options) {
|
|
|
459
491
|
const wrapped = await handleResponse(response);
|
|
460
492
|
return wrapped.data;
|
|
461
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
|
+
}
|
|
462
513
|
async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
|
|
463
514
|
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/github/check-installation`, {
|
|
464
515
|
method: "POST",
|
|
@@ -476,32 +527,32 @@ async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
|
|
|
476
527
|
// src/utils/analytics.ts
|
|
477
528
|
import { PostHog } from "posthog-node";
|
|
478
529
|
import crypto from "crypto";
|
|
479
|
-
import
|
|
530
|
+
import path2 from "path";
|
|
480
531
|
import os from "os";
|
|
481
|
-
import
|
|
532
|
+
import fs2 from "fs";
|
|
482
533
|
var posthog = null;
|
|
483
534
|
var distinctId = null;
|
|
484
|
-
var CONFIG_DIR =
|
|
485
|
-
var ID_FILE =
|
|
535
|
+
var CONFIG_DIR = path2.join(os.homedir(), ".config", "keyway");
|
|
536
|
+
var ID_FILE = path2.join(CONFIG_DIR, "id.json");
|
|
486
537
|
var TELEMETRY_DISABLED = process.env.KEYWAY_DISABLE_TELEMETRY === "1";
|
|
487
538
|
var CI = process.env.CI === "true" || process.env.CI === "1";
|
|
488
539
|
function getDistinctId() {
|
|
489
540
|
if (distinctId) return distinctId;
|
|
490
541
|
try {
|
|
491
|
-
if (!
|
|
492
|
-
|
|
542
|
+
if (!fs2.existsSync(CONFIG_DIR)) {
|
|
543
|
+
fs2.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
493
544
|
}
|
|
494
|
-
if (
|
|
495
|
-
const content =
|
|
545
|
+
if (fs2.existsSync(ID_FILE)) {
|
|
546
|
+
const content = fs2.readFileSync(ID_FILE, "utf-8");
|
|
496
547
|
const config2 = JSON.parse(content);
|
|
497
548
|
distinctId = config2.distinctId;
|
|
498
549
|
return distinctId;
|
|
499
550
|
}
|
|
500
551
|
distinctId = crypto.randomUUID();
|
|
501
552
|
const config = { distinctId };
|
|
502
|
-
|
|
553
|
+
fs2.writeFileSync(ID_FILE, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
|
|
503
554
|
try {
|
|
504
|
-
|
|
555
|
+
fs2.chmodSync(ID_FILE, 384);
|
|
505
556
|
} catch {
|
|
506
557
|
}
|
|
507
558
|
return distinctId;
|
|
@@ -600,10 +651,10 @@ var AnalyticsEvents = {
|
|
|
600
651
|
};
|
|
601
652
|
|
|
602
653
|
// src/cmds/readme.ts
|
|
603
|
-
import
|
|
604
|
-
import
|
|
654
|
+
import fs3 from "fs";
|
|
655
|
+
import path3 from "path";
|
|
605
656
|
import prompts from "prompts";
|
|
606
|
-
import
|
|
657
|
+
import pc2 from "picocolors";
|
|
607
658
|
function generateBadge(repo) {
|
|
608
659
|
return `[](https://www.keyway.sh/vaults/${repo})`;
|
|
609
660
|
}
|
|
@@ -629,8 +680,8 @@ ${readmeContent}`;
|
|
|
629
680
|
function findReadmePath(cwd) {
|
|
630
681
|
const candidates = ["README.md", "readme.md", "Readme.md"];
|
|
631
682
|
for (const candidate of candidates) {
|
|
632
|
-
const candidatePath =
|
|
633
|
-
if (
|
|
683
|
+
const candidatePath = path3.join(cwd, candidate);
|
|
684
|
+
if (fs3.existsSync(candidatePath)) {
|
|
634
685
|
return candidatePath;
|
|
635
686
|
}
|
|
636
687
|
}
|
|
@@ -641,7 +692,7 @@ async function ensureReadme(repoName, cwd) {
|
|
|
641
692
|
if (existing) return existing;
|
|
642
693
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
643
694
|
if (!isInteractive3) {
|
|
644
|
-
console.log(
|
|
695
|
+
console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
645
696
|
return null;
|
|
646
697
|
}
|
|
647
698
|
const { confirm } = await prompts(
|
|
@@ -656,14 +707,14 @@ async function ensureReadme(repoName, cwd) {
|
|
|
656
707
|
}
|
|
657
708
|
);
|
|
658
709
|
if (!confirm) {
|
|
659
|
-
console.log(
|
|
710
|
+
console.log(pc2.yellow("Skipping badge insertion (no README)."));
|
|
660
711
|
return null;
|
|
661
712
|
}
|
|
662
|
-
const defaultPath =
|
|
713
|
+
const defaultPath = path3.join(cwd, "README.md");
|
|
663
714
|
const content = `# ${repoName}
|
|
664
715
|
|
|
665
716
|
`;
|
|
666
|
-
|
|
717
|
+
fs3.writeFileSync(defaultPath, content, "utf-8");
|
|
667
718
|
return defaultPath;
|
|
668
719
|
}
|
|
669
720
|
async function addBadgeToReadme(silent = false) {
|
|
@@ -675,130 +726,32 @@ async function addBadgeToReadme(silent = false) {
|
|
|
675
726
|
const readmePath = await ensureReadme(repo, cwd);
|
|
676
727
|
if (!readmePath) return false;
|
|
677
728
|
const badge = generateBadge(repo);
|
|
678
|
-
const content =
|
|
729
|
+
const content = fs3.readFileSync(readmePath, "utf-8");
|
|
679
730
|
const updated = insertBadgeIntoReadme(content, badge);
|
|
680
731
|
if (updated === content) {
|
|
681
732
|
if (!silent) {
|
|
682
|
-
console.log(
|
|
733
|
+
console.log(pc2.gray("Keyway badge already present in README."));
|
|
683
734
|
}
|
|
684
735
|
return false;
|
|
685
736
|
}
|
|
686
|
-
|
|
737
|
+
fs3.writeFileSync(readmePath, updated, "utf-8");
|
|
687
738
|
if (!silent) {
|
|
688
|
-
console.log(
|
|
739
|
+
console.log(pc2.green(`\u2713 Keyway badge added to ${path3.basename(readmePath)}`));
|
|
689
740
|
}
|
|
690
741
|
return true;
|
|
691
742
|
}
|
|
692
743
|
|
|
693
744
|
// src/cmds/push.ts
|
|
694
|
-
import
|
|
695
|
-
import
|
|
696
|
-
import
|
|
745
|
+
import pc4 from "picocolors";
|
|
746
|
+
import fs4 from "fs";
|
|
747
|
+
import path4 from "path";
|
|
697
748
|
import prompts3 from "prompts";
|
|
698
749
|
|
|
699
750
|
// src/cmds/login.ts
|
|
700
|
-
import
|
|
751
|
+
import pc3 from "picocolors";
|
|
701
752
|
import readline from "readline";
|
|
702
753
|
import open from "open";
|
|
703
754
|
import prompts2 from "prompts";
|
|
704
|
-
|
|
705
|
-
// src/utils/auth.ts
|
|
706
|
-
import Conf from "conf";
|
|
707
|
-
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
708
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "fs";
|
|
709
|
-
import { join } from "path";
|
|
710
|
-
import { homedir } from "os";
|
|
711
|
-
var store = new Conf({
|
|
712
|
-
projectName: "keyway",
|
|
713
|
-
configName: "config",
|
|
714
|
-
fileMode: 384
|
|
715
|
-
});
|
|
716
|
-
var KEY_DIR = join(homedir(), ".keyway");
|
|
717
|
-
var KEY_FILE = join(KEY_DIR, ".key");
|
|
718
|
-
function getOrCreateEncryptionKey() {
|
|
719
|
-
if (!existsSync(KEY_DIR)) {
|
|
720
|
-
mkdirSync(KEY_DIR, { recursive: true, mode: 448 });
|
|
721
|
-
}
|
|
722
|
-
if (existsSync(KEY_FILE)) {
|
|
723
|
-
const keyHex2 = readFileSync(KEY_FILE, "utf-8").trim();
|
|
724
|
-
if (keyHex2.length === 64) {
|
|
725
|
-
return Buffer.from(keyHex2, "hex");
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
const key = randomBytes(32);
|
|
729
|
-
const keyHex = key.toString("hex");
|
|
730
|
-
writeFileSync(KEY_FILE, keyHex, { mode: 384 });
|
|
731
|
-
try {
|
|
732
|
-
chmodSync(KEY_FILE, 384);
|
|
733
|
-
} catch {
|
|
734
|
-
}
|
|
735
|
-
return key;
|
|
736
|
-
}
|
|
737
|
-
function encryptToken(token) {
|
|
738
|
-
const key = getOrCreateEncryptionKey();
|
|
739
|
-
const iv = randomBytes(16);
|
|
740
|
-
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
741
|
-
const encrypted = Buffer.concat([cipher.update(token, "utf8"), cipher.final()]);
|
|
742
|
-
const authTag = cipher.getAuthTag();
|
|
743
|
-
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
|
|
744
|
-
}
|
|
745
|
-
function decryptToken(encryptedData) {
|
|
746
|
-
const key = getOrCreateEncryptionKey();
|
|
747
|
-
const parts = encryptedData.split(":");
|
|
748
|
-
if (parts.length !== 3) {
|
|
749
|
-
throw new Error("Invalid encrypted token format");
|
|
750
|
-
}
|
|
751
|
-
const iv = Buffer.from(parts[0], "hex");
|
|
752
|
-
const authTag = Buffer.from(parts[1], "hex");
|
|
753
|
-
const encrypted = Buffer.from(parts[2], "hex");
|
|
754
|
-
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
755
|
-
decipher.setAuthTag(authTag);
|
|
756
|
-
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
757
|
-
return decrypted.toString("utf8");
|
|
758
|
-
}
|
|
759
|
-
function isExpired(auth) {
|
|
760
|
-
if (!auth.expiresAt) return false;
|
|
761
|
-
const expires = Date.parse(auth.expiresAt);
|
|
762
|
-
if (Number.isNaN(expires)) return false;
|
|
763
|
-
return expires <= Date.now();
|
|
764
|
-
}
|
|
765
|
-
async function getStoredAuth() {
|
|
766
|
-
const encryptedData = store.get("auth");
|
|
767
|
-
if (!encryptedData) {
|
|
768
|
-
return null;
|
|
769
|
-
}
|
|
770
|
-
try {
|
|
771
|
-
const decrypted = decryptToken(encryptedData);
|
|
772
|
-
const auth = JSON.parse(decrypted);
|
|
773
|
-
if (isExpired(auth)) {
|
|
774
|
-
clearAuth();
|
|
775
|
-
return null;
|
|
776
|
-
}
|
|
777
|
-
return auth;
|
|
778
|
-
} catch {
|
|
779
|
-
console.error("Failed to decrypt stored auth, clearing...");
|
|
780
|
-
clearAuth();
|
|
781
|
-
return null;
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
async function saveAuthToken(token, meta) {
|
|
785
|
-
const auth = {
|
|
786
|
-
keywayToken: token,
|
|
787
|
-
githubLogin: meta?.githubLogin,
|
|
788
|
-
expiresAt: meta?.expiresAt,
|
|
789
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
790
|
-
};
|
|
791
|
-
const encrypted = encryptToken(JSON.stringify(auth));
|
|
792
|
-
store.set("auth", encrypted);
|
|
793
|
-
}
|
|
794
|
-
function clearAuth() {
|
|
795
|
-
store.delete("auth");
|
|
796
|
-
}
|
|
797
|
-
function getAuthFilePath() {
|
|
798
|
-
return store.path;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
// src/cmds/login.ts
|
|
802
755
|
function sleep(ms) {
|
|
803
756
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
804
757
|
}
|
|
@@ -822,17 +775,17 @@ async function promptYesNo(question, defaultYes = true) {
|
|
|
822
775
|
});
|
|
823
776
|
}
|
|
824
777
|
async function runLoginFlow() {
|
|
825
|
-
console.log(
|
|
778
|
+
console.log(pc3.blue("\u{1F510} Starting Keyway login...\n"));
|
|
826
779
|
const repoName = detectGitRepo();
|
|
827
780
|
const start = await startDeviceLogin(repoName);
|
|
828
781
|
const verifyUrl = start.verificationUriComplete || start.verificationUri;
|
|
829
782
|
if (!verifyUrl) {
|
|
830
783
|
throw new Error("Missing verification URL from the auth server.");
|
|
831
784
|
}
|
|
832
|
-
console.log(`Code: ${
|
|
785
|
+
console.log(`Code: ${pc3.bold(pc3.green(start.userCode))}`);
|
|
833
786
|
console.log("Waiting for auth...");
|
|
834
787
|
open(verifyUrl).catch(() => {
|
|
835
|
-
console.log(
|
|
788
|
+
console.log(pc3.gray(`Open this URL in your browser: ${verifyUrl}`));
|
|
836
789
|
});
|
|
837
790
|
const pollIntervalMs = (start.interval ?? 5) * 1e3;
|
|
838
791
|
const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
|
|
@@ -861,9 +814,9 @@ async function runLoginFlow() {
|
|
|
861
814
|
login_method: "device"
|
|
862
815
|
});
|
|
863
816
|
}
|
|
864
|
-
console.log(
|
|
817
|
+
console.log(pc3.green("\n\u2713 Login successful"));
|
|
865
818
|
if (result.githubLogin) {
|
|
866
|
-
console.log(`Authenticated GitHub user: ${
|
|
819
|
+
console.log(`Authenticated GitHub user: ${pc3.cyan(result.githubLogin)}`);
|
|
867
820
|
}
|
|
868
821
|
return result.keywayToken;
|
|
869
822
|
}
|
|
@@ -876,7 +829,7 @@ async function ensureLogin(options = {}) {
|
|
|
876
829
|
return envToken;
|
|
877
830
|
}
|
|
878
831
|
if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
|
|
879
|
-
console.warn(
|
|
832
|
+
console.warn(pc3.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
|
|
880
833
|
}
|
|
881
834
|
const stored = await getStoredAuth();
|
|
882
835
|
if (stored?.keywayToken) {
|
|
@@ -896,16 +849,16 @@ async function ensureLogin(options = {}) {
|
|
|
896
849
|
async function runTokenLogin() {
|
|
897
850
|
const repoName = detectGitRepo();
|
|
898
851
|
if (repoName) {
|
|
899
|
-
console.log(`\u{1F4C1} Detected: ${
|
|
852
|
+
console.log(`\u{1F4C1} Detected: ${pc3.cyan(repoName)}`);
|
|
900
853
|
}
|
|
901
854
|
const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
|
|
902
855
|
const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
|
|
903
856
|
console.log("Opening GitHub...");
|
|
904
857
|
open(url).catch(() => {
|
|
905
|
-
console.log(
|
|
858
|
+
console.log(pc3.gray(`Open this URL in your browser: ${url}`));
|
|
906
859
|
});
|
|
907
|
-
console.log(
|
|
908
|
-
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."));
|
|
909
862
|
const { token } = await prompts2(
|
|
910
863
|
{
|
|
911
864
|
type: "password",
|
|
@@ -942,7 +895,7 @@ async function runTokenLogin() {
|
|
|
942
895
|
github_username: validation.username,
|
|
943
896
|
login_method: "pat"
|
|
944
897
|
});
|
|
945
|
-
console.log(
|
|
898
|
+
console.log(pc3.green("\u2705 Authenticated"), `as ${pc3.cyan(`@${validation.username}`)}`);
|
|
946
899
|
return trimmedToken;
|
|
947
900
|
}
|
|
948
901
|
async function loginCommand(options = {}) {
|
|
@@ -958,20 +911,20 @@ async function loginCommand(options = {}) {
|
|
|
958
911
|
command: "login",
|
|
959
912
|
error: truncateMessage(message)
|
|
960
913
|
});
|
|
961
|
-
console.error(
|
|
914
|
+
console.error(pc3.red(`
|
|
962
915
|
\u2717 ${message}`));
|
|
963
916
|
process.exit(1);
|
|
964
917
|
}
|
|
965
918
|
}
|
|
966
919
|
async function logoutCommand() {
|
|
967
920
|
clearAuth();
|
|
968
|
-
console.log(
|
|
969
|
-
console.log(
|
|
921
|
+
console.log(pc3.green("\u2713 Logged out of Keyway"));
|
|
922
|
+
console.log(pc3.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
970
923
|
}
|
|
971
924
|
|
|
972
925
|
// src/cmds/push.ts
|
|
973
926
|
function deriveEnvFromFile(file) {
|
|
974
|
-
const base =
|
|
927
|
+
const base = path4.basename(file);
|
|
975
928
|
const match = base.match(/\.env(?:\.(.+))?$/);
|
|
976
929
|
if (match) {
|
|
977
930
|
return match[1] || "development";
|
|
@@ -980,15 +933,15 @@ function deriveEnvFromFile(file) {
|
|
|
980
933
|
}
|
|
981
934
|
function discoverEnvCandidates(cwd) {
|
|
982
935
|
try {
|
|
983
|
-
const entries =
|
|
936
|
+
const entries = fs4.readdirSync(cwd);
|
|
984
937
|
const hasEnvLocal = entries.includes(".env.local");
|
|
985
938
|
if (hasEnvLocal) {
|
|
986
|
-
console.log(
|
|
939
|
+
console.log(pc4.gray("\u2139\uFE0F Detected .env.local \u2014 not synced by design (machine-specific secrets)"));
|
|
987
940
|
}
|
|
988
941
|
const candidates = entries.filter((name) => name.startsWith(".env") && name !== ".env.local").map((name) => {
|
|
989
|
-
const fullPath =
|
|
942
|
+
const fullPath = path4.join(cwd, name);
|
|
990
943
|
try {
|
|
991
|
-
const stat =
|
|
944
|
+
const stat = fs4.statSync(fullPath);
|
|
992
945
|
if (!stat.isFile()) return null;
|
|
993
946
|
return { file: name, env: deriveEnvFromFile(name) };
|
|
994
947
|
} catch {
|
|
@@ -1009,7 +962,7 @@ function discoverEnvCandidates(cwd) {
|
|
|
1009
962
|
}
|
|
1010
963
|
async function pushCommand(options) {
|
|
1011
964
|
try {
|
|
1012
|
-
console.log(
|
|
965
|
+
console.log(pc4.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
1013
966
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1014
967
|
let environment = options.env;
|
|
1015
968
|
let envFile = options.file;
|
|
@@ -1051,8 +1004,8 @@ async function pushCommand(options) {
|
|
|
1051
1004
|
message: "Path to env file:",
|
|
1052
1005
|
validate: (value) => {
|
|
1053
1006
|
if (!value) return "Path is required";
|
|
1054
|
-
const resolved =
|
|
1055
|
-
if (!
|
|
1007
|
+
const resolved = path4.resolve(process.cwd(), value);
|
|
1008
|
+
if (!fs4.existsSync(resolved)) return `File not found: ${value}`;
|
|
1056
1009
|
return true;
|
|
1057
1010
|
}
|
|
1058
1011
|
},
|
|
@@ -1072,8 +1025,8 @@ async function pushCommand(options) {
|
|
|
1072
1025
|
if (!envFile) {
|
|
1073
1026
|
envFile = ".env";
|
|
1074
1027
|
}
|
|
1075
|
-
let envFilePath =
|
|
1076
|
-
if (!
|
|
1028
|
+
let envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1029
|
+
if (!fs4.existsSync(envFilePath)) {
|
|
1077
1030
|
if (!isInteractive3) {
|
|
1078
1031
|
throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
|
|
1079
1032
|
}
|
|
@@ -1084,8 +1037,8 @@ async function pushCommand(options) {
|
|
|
1084
1037
|
message: `File not found: ${envFile}. Enter an env file path to use:`,
|
|
1085
1038
|
validate: (value) => {
|
|
1086
1039
|
if (!value || typeof value !== "string") return "Path is required";
|
|
1087
|
-
const resolved =
|
|
1088
|
-
if (!
|
|
1040
|
+
const resolved = path4.resolve(process.cwd(), value);
|
|
1041
|
+
if (!fs4.existsSync(resolved)) return `File not found: ${value}`;
|
|
1089
1042
|
return true;
|
|
1090
1043
|
}
|
|
1091
1044
|
},
|
|
@@ -1099,9 +1052,9 @@ async function pushCommand(options) {
|
|
|
1099
1052
|
throw new Error("Push cancelled (no env file provided).");
|
|
1100
1053
|
}
|
|
1101
1054
|
envFile = newPath.trim();
|
|
1102
|
-
envFilePath =
|
|
1055
|
+
envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1103
1056
|
}
|
|
1104
|
-
const content =
|
|
1057
|
+
const content = fs4.readFileSync(envFilePath, "utf-8");
|
|
1105
1058
|
if (content.trim().length === 0) {
|
|
1106
1059
|
throw new Error(`File is empty: ${envFile}`);
|
|
1107
1060
|
}
|
|
@@ -1109,11 +1062,11 @@ async function pushCommand(options) {
|
|
|
1109
1062
|
const trimmed = line.trim();
|
|
1110
1063
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1111
1064
|
});
|
|
1112
|
-
console.log(`File: ${
|
|
1113
|
-
console.log(`Environment: ${
|
|
1114
|
-
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())}`);
|
|
1115
1068
|
const repoFullName = getCurrentRepoFullName();
|
|
1116
|
-
console.log(`Repository: ${
|
|
1069
|
+
console.log(`Repository: ${pc4.cyan(repoFullName)}`);
|
|
1117
1070
|
if (!options.yes) {
|
|
1118
1071
|
const isInteractive4 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1119
1072
|
if (!isInteractive4) {
|
|
@@ -1133,7 +1086,7 @@ async function pushCommand(options) {
|
|
|
1133
1086
|
}
|
|
1134
1087
|
);
|
|
1135
1088
|
if (!confirm) {
|
|
1136
|
-
console.log(
|
|
1089
|
+
console.log(pc4.yellow("Push aborted."));
|
|
1137
1090
|
return;
|
|
1138
1091
|
}
|
|
1139
1092
|
}
|
|
@@ -1145,20 +1098,20 @@ async function pushCommand(options) {
|
|
|
1145
1098
|
});
|
|
1146
1099
|
console.log("\nUploading secrets...");
|
|
1147
1100
|
const response = await pushSecrets(repoFullName, environment, content, accessToken);
|
|
1148
|
-
console.log(
|
|
1101
|
+
console.log(pc4.green("\n\u2713 " + response.message));
|
|
1149
1102
|
if (response.stats) {
|
|
1150
1103
|
const { created, updated, deleted } = response.stats;
|
|
1151
1104
|
const parts = [];
|
|
1152
|
-
if (created > 0) parts.push(
|
|
1153
|
-
if (updated > 0) parts.push(
|
|
1154
|
-
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`));
|
|
1155
1108
|
if (parts.length > 0) {
|
|
1156
1109
|
console.log(`Stats: ${parts.join(", ")}`);
|
|
1157
1110
|
}
|
|
1158
1111
|
}
|
|
1159
1112
|
console.log(`
|
|
1160
1113
|
Your secrets are now encrypted and stored securely.`);
|
|
1161
|
-
console.log(`To retrieve them, run: ${
|
|
1114
|
+
console.log(`To retrieve them, run: ${pc4.cyan(`keyway pull --env ${environment}`)}`);
|
|
1162
1115
|
await shutdownAnalytics();
|
|
1163
1116
|
} catch (error) {
|
|
1164
1117
|
let message;
|
|
@@ -1171,13 +1124,13 @@ Your secrets are now encrypted and stored securely.`);
|
|
|
1171
1124
|
const availableEnvs = envNotFoundMatch[2];
|
|
1172
1125
|
message = `Environment '${requestedEnv}' does not exist in this vault.`;
|
|
1173
1126
|
hint = `Available environments: ${availableEnvs}
|
|
1174
|
-
Use ${
|
|
1127
|
+
Use ${pc4.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1175
1128
|
}
|
|
1176
1129
|
if (error.statusCode === 403 && error.upgradeUrl) {
|
|
1177
|
-
hint = `${
|
|
1130
|
+
hint = `${pc4.yellow("\u26A1")} Upgrade to Pro: ${pc4.cyan(error.upgradeUrl)}`;
|
|
1178
1131
|
} else if (error.statusCode === 403 && message.toLowerCase().includes("read-only")) {
|
|
1179
1132
|
message = "This vault is read-only on your current plan.";
|
|
1180
|
-
hint = `Upgrade to Pro to unlock editing: ${
|
|
1133
|
+
hint = `Upgrade to Pro to unlock editing: ${pc4.cyan("https://keyway.sh/settings")}`;
|
|
1181
1134
|
}
|
|
1182
1135
|
} else if (error instanceof Error) {
|
|
1183
1136
|
message = truncateMessage(error.message);
|
|
@@ -1189,10 +1142,10 @@ Use ${pc3.cyan(`keyway push --env <environment>`)} to specify one, or create '${
|
|
|
1189
1142
|
error: message
|
|
1190
1143
|
});
|
|
1191
1144
|
await shutdownAnalytics();
|
|
1192
|
-
console.error(
|
|
1145
|
+
console.error(pc4.red(`
|
|
1193
1146
|
\u2717 ${message}`));
|
|
1194
1147
|
if (hint) {
|
|
1195
|
-
console.error(
|
|
1148
|
+
console.error(pc4.gray(`
|
|
1196
1149
|
${hint}`));
|
|
1197
1150
|
}
|
|
1198
1151
|
process.exit(1);
|
|
@@ -1213,19 +1166,26 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1213
1166
|
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1214
1167
|
const envToken = process.env.KEYWAY_TOKEN;
|
|
1215
1168
|
if (envToken) {
|
|
1216
|
-
|
|
1169
|
+
const result = await ensureGitHubAppInstalledOnly(repoFullName, envToken);
|
|
1170
|
+
if (result === null) {
|
|
1171
|
+
throw new Error("KEYWAY_TOKEN is invalid or expired. Please update the token.");
|
|
1172
|
+
}
|
|
1173
|
+
return result;
|
|
1217
1174
|
}
|
|
1218
1175
|
const stored = await getStoredAuth();
|
|
1219
1176
|
if (stored?.keywayToken) {
|
|
1220
|
-
|
|
1177
|
+
const result = await ensureGitHubAppInstalledOnly(repoFullName, stored.keywayToken);
|
|
1178
|
+
if (result !== null) {
|
|
1179
|
+
return result;
|
|
1180
|
+
}
|
|
1221
1181
|
}
|
|
1222
1182
|
const allowPrompt = options.allowPrompt !== false;
|
|
1223
1183
|
if (!allowPrompt || !isInteractive2()) {
|
|
1224
1184
|
throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
|
|
1225
1185
|
}
|
|
1226
1186
|
console.log("");
|
|
1227
|
-
console.log(
|
|
1228
|
-
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."));
|
|
1229
1189
|
console.log("");
|
|
1230
1190
|
const { shouldProceed } = await prompts4({
|
|
1231
1191
|
type: "confirm",
|
|
@@ -1238,11 +1198,11 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1238
1198
|
}
|
|
1239
1199
|
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1240
1200
|
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1241
|
-
console.log(
|
|
1201
|
+
console.log(pc5.gray("\n Opening browser..."));
|
|
1242
1202
|
await open2(installUrl);
|
|
1243
1203
|
console.log("");
|
|
1244
|
-
console.log(
|
|
1245
|
-
console.log(
|
|
1204
|
+
console.log(pc5.blue("\u23F3 Waiting for installation & authorization..."));
|
|
1205
|
+
console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1246
1206
|
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1247
1207
|
const startTime = Date.now();
|
|
1248
1208
|
let accessToken = null;
|
|
@@ -1257,7 +1217,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1257
1217
|
githubLogin: result.githubLogin,
|
|
1258
1218
|
expiresAt: result.expiresAt
|
|
1259
1219
|
});
|
|
1260
|
-
console.log(
|
|
1220
|
+
console.log(pc5.green("\u2713 Signed in!"));
|
|
1261
1221
|
if (result.githubLogin) {
|
|
1262
1222
|
identifyUser(result.githubLogin, {
|
|
1263
1223
|
github_username: result.githubLogin,
|
|
@@ -1269,34 +1229,45 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1269
1229
|
if (accessToken) {
|
|
1270
1230
|
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1271
1231
|
if (installStatus.installed) {
|
|
1272
|
-
console.log(
|
|
1232
|
+
console.log(pc5.green("\u2713 GitHub App installed!"));
|
|
1273
1233
|
console.log("");
|
|
1274
1234
|
return accessToken;
|
|
1275
1235
|
}
|
|
1276
1236
|
}
|
|
1277
|
-
process.stdout.write(
|
|
1237
|
+
process.stdout.write(pc5.gray("."));
|
|
1278
1238
|
} catch {
|
|
1279
1239
|
}
|
|
1280
1240
|
}
|
|
1281
1241
|
console.log("");
|
|
1282
|
-
console.log(
|
|
1283
|
-
console.log(
|
|
1242
|
+
console.log(pc5.yellow("\u26A0 Timed out waiting for setup."));
|
|
1243
|
+
console.log(pc5.gray(` Install the GitHub App: ${installUrl}`));
|
|
1284
1244
|
throw new Error("Setup timed out. Please try again.");
|
|
1285
1245
|
}
|
|
1286
1246
|
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
1287
1247
|
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1288
|
-
|
|
1248
|
+
let status;
|
|
1249
|
+
try {
|
|
1250
|
+
status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
if (error instanceof APIError && error.statusCode === 401) {
|
|
1253
|
+
console.log(pc5.yellow("\n\u26A0 Session expired or invalid. Clearing credentials..."));
|
|
1254
|
+
const { clearAuth: clearAuth2 } = await import("./auth-QLPQ24HZ.js");
|
|
1255
|
+
clearAuth2();
|
|
1256
|
+
return null;
|
|
1257
|
+
}
|
|
1258
|
+
throw error;
|
|
1259
|
+
}
|
|
1289
1260
|
if (status.installed) {
|
|
1290
1261
|
return accessToken;
|
|
1291
1262
|
}
|
|
1292
1263
|
console.log("");
|
|
1293
|
-
console.log(
|
|
1264
|
+
console.log(pc5.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1294
1265
|
console.log("");
|
|
1295
|
-
console.log(
|
|
1296
|
-
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)."));
|
|
1297
1268
|
console.log("");
|
|
1298
1269
|
if (!isInteractive2()) {
|
|
1299
|
-
console.log(
|
|
1270
|
+
console.log(pc5.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1300
1271
|
throw new Error("GitHub App installation required.");
|
|
1301
1272
|
}
|
|
1302
1273
|
const { shouldInstall } = await prompts4({
|
|
@@ -1306,50 +1277,50 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1306
1277
|
initial: true
|
|
1307
1278
|
});
|
|
1308
1279
|
if (!shouldInstall) {
|
|
1309
|
-
console.log(
|
|
1280
|
+
console.log(pc5.gray(`
|
|
1310
1281
|
You can install later: ${status.installUrl}`));
|
|
1311
1282
|
throw new Error("GitHub App installation required.");
|
|
1312
1283
|
}
|
|
1313
|
-
console.log(
|
|
1284
|
+
console.log(pc5.gray("\n Opening browser..."));
|
|
1314
1285
|
await open2(status.installUrl);
|
|
1315
1286
|
console.log("");
|
|
1316
|
-
console.log(
|
|
1317
|
-
console.log(
|
|
1287
|
+
console.log(pc5.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1288
|
+
console.log(pc5.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1318
1289
|
const startTime = Date.now();
|
|
1319
1290
|
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1320
1291
|
await sleep2(POLL_INTERVAL_MS);
|
|
1321
1292
|
try {
|
|
1322
1293
|
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1323
1294
|
if (pollStatus.installed) {
|
|
1324
|
-
console.log(
|
|
1295
|
+
console.log(pc5.green("\u2713 GitHub App installed!"));
|
|
1325
1296
|
console.log("");
|
|
1326
1297
|
return accessToken;
|
|
1327
1298
|
}
|
|
1328
|
-
process.stdout.write(
|
|
1299
|
+
process.stdout.write(pc5.gray("."));
|
|
1329
1300
|
} catch {
|
|
1330
1301
|
}
|
|
1331
1302
|
}
|
|
1332
1303
|
console.log("");
|
|
1333
|
-
console.log(
|
|
1334
|
-
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}`));
|
|
1335
1306
|
throw new Error("GitHub App installation timed out.");
|
|
1336
1307
|
}
|
|
1337
1308
|
async function initCommand(options = {}) {
|
|
1338
1309
|
try {
|
|
1339
1310
|
const repoFullName = getCurrentRepoFullName();
|
|
1340
1311
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1341
|
-
console.log(
|
|
1342
|
-
console.log(` ${
|
|
1312
|
+
console.log(pc5.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1313
|
+
console.log(` ${pc5.gray("Repository:")} ${pc5.white(repoFullName)}`);
|
|
1343
1314
|
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1344
1315
|
allowPrompt: options.loginPrompt !== false
|
|
1345
1316
|
});
|
|
1346
1317
|
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1347
1318
|
await initVault(repoFullName, accessToken);
|
|
1348
|
-
console.log(
|
|
1319
|
+
console.log(pc5.green("\u2713 Vault created!"));
|
|
1349
1320
|
try {
|
|
1350
1321
|
const badgeAdded = await addBadgeToReadme(true);
|
|
1351
1322
|
if (badgeAdded) {
|
|
1352
|
-
console.log(
|
|
1323
|
+
console.log(pc5.green("\u2713 Badge added to README.md"));
|
|
1353
1324
|
}
|
|
1354
1325
|
} catch {
|
|
1355
1326
|
}
|
|
@@ -1357,7 +1328,7 @@ async function initCommand(options = {}) {
|
|
|
1357
1328
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1358
1329
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1359
1330
|
if (envCandidates.length > 0 && isInteractive3) {
|
|
1360
|
-
console.log(
|
|
1331
|
+
console.log(pc5.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1361
1332
|
`));
|
|
1362
1333
|
const { shouldPush } = await prompts4({
|
|
1363
1334
|
type: "confirm",
|
|
@@ -1371,25 +1342,25 @@ async function initCommand(options = {}) {
|
|
|
1371
1342
|
return;
|
|
1372
1343
|
}
|
|
1373
1344
|
}
|
|
1374
|
-
console.log(
|
|
1345
|
+
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1375
1346
|
console.log("");
|
|
1376
1347
|
if (envCandidates.length === 0) {
|
|
1377
|
-
console.log(` ${
|
|
1378
|
-
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
|
|
1379
1350
|
`);
|
|
1380
1351
|
} else {
|
|
1381
|
-
console.log(` ${
|
|
1352
|
+
console.log(` ${pc5.yellow("\u2192")} Run ${pc5.cyan("keyway push")} to sync your secrets
|
|
1382
1353
|
`);
|
|
1383
1354
|
}
|
|
1384
|
-
console.log(` ${
|
|
1355
|
+
console.log(` ${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
|
|
1385
1356
|
console.log("");
|
|
1386
1357
|
await shutdownAnalytics();
|
|
1387
1358
|
} catch (error) {
|
|
1388
1359
|
if (error instanceof APIError) {
|
|
1389
1360
|
if (error.statusCode === 409) {
|
|
1390
|
-
console.log(
|
|
1391
|
-
console.log(` ${
|
|
1392
|
-
console.log(` ${
|
|
1361
|
+
console.log(pc5.yellow("\n\u26A0 Vault already exists for this repository.\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()}`)}`);
|
|
1393
1364
|
console.log("");
|
|
1394
1365
|
await shutdownAnalytics();
|
|
1395
1366
|
return;
|
|
@@ -1397,15 +1368,15 @@ async function initCommand(options = {}) {
|
|
|
1397
1368
|
if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
|
|
1398
1369
|
const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
|
|
1399
1370
|
console.log("");
|
|
1400
|
-
console.log(
|
|
1371
|
+
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1401
1372
|
console.log("");
|
|
1402
|
-
console.log(` ${
|
|
1373
|
+
console.log(` ${pc5.yellow("\u26A1")} ${pc5.bold("Plan Limit Reached")}`);
|
|
1403
1374
|
console.log("");
|
|
1404
|
-
console.log(
|
|
1375
|
+
console.log(pc5.white(` ${error.message}`));
|
|
1405
1376
|
console.log("");
|
|
1406
|
-
console.log(` ${
|
|
1377
|
+
console.log(` ${pc5.cyan("Upgrade now \u2192")} ${pc5.underline(upgradeUrl)}`);
|
|
1407
1378
|
console.log("");
|
|
1408
|
-
console.log(
|
|
1379
|
+
console.log(pc5.dim("\u2500".repeat(50)));
|
|
1409
1380
|
console.log("");
|
|
1410
1381
|
await shutdownAnalytics();
|
|
1411
1382
|
process.exit(1);
|
|
@@ -1417,25 +1388,25 @@ async function initCommand(options = {}) {
|
|
|
1417
1388
|
error: message
|
|
1418
1389
|
});
|
|
1419
1390
|
await shutdownAnalytics();
|
|
1420
|
-
console.error(
|
|
1391
|
+
console.error(pc5.red(`
|
|
1421
1392
|
\u2717 ${message}`));
|
|
1422
1393
|
process.exit(1);
|
|
1423
1394
|
}
|
|
1424
1395
|
}
|
|
1425
1396
|
|
|
1426
1397
|
// src/cmds/pull.ts
|
|
1427
|
-
import
|
|
1428
|
-
import
|
|
1429
|
-
import
|
|
1398
|
+
import pc6 from "picocolors";
|
|
1399
|
+
import fs5 from "fs";
|
|
1400
|
+
import path5 from "path";
|
|
1430
1401
|
import prompts5 from "prompts";
|
|
1431
1402
|
async function pullCommand(options) {
|
|
1432
1403
|
try {
|
|
1433
1404
|
const environment = options.env || "development";
|
|
1434
1405
|
const envFile = options.file || ".env";
|
|
1435
|
-
console.log(
|
|
1436
|
-
console.log(`Environment: ${
|
|
1406
|
+
console.log(pc6.blue("\u{1F510} Pulling secrets from Keyway...\n"));
|
|
1407
|
+
console.log(`Environment: ${pc6.cyan(environment)}`);
|
|
1437
1408
|
const repoFullName = getCurrentRepoFullName();
|
|
1438
|
-
console.log(`Repository: ${
|
|
1409
|
+
console.log(`Repository: ${pc6.cyan(repoFullName)}`);
|
|
1439
1410
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1440
1411
|
trackEvent(AnalyticsEvents.CLI_PULL, {
|
|
1441
1412
|
repoFullName,
|
|
@@ -1443,11 +1414,11 @@ async function pullCommand(options) {
|
|
|
1443
1414
|
});
|
|
1444
1415
|
console.log("\nDownloading secrets...");
|
|
1445
1416
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1446
|
-
const envFilePath =
|
|
1447
|
-
if (
|
|
1417
|
+
const envFilePath = path5.resolve(process.cwd(), envFile);
|
|
1418
|
+
if (fs5.existsSync(envFilePath)) {
|
|
1448
1419
|
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1449
1420
|
if (options.yes) {
|
|
1450
|
-
console.log(
|
|
1421
|
+
console.log(pc6.yellow(`
|
|
1451
1422
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1452
1423
|
} else if (!isInteractive3) {
|
|
1453
1424
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
@@ -1466,21 +1437,21 @@ async function pullCommand(options) {
|
|
|
1466
1437
|
}
|
|
1467
1438
|
);
|
|
1468
1439
|
if (!confirm) {
|
|
1469
|
-
console.log(
|
|
1440
|
+
console.log(pc6.yellow("Pull aborted."));
|
|
1470
1441
|
return;
|
|
1471
1442
|
}
|
|
1472
1443
|
}
|
|
1473
1444
|
}
|
|
1474
|
-
|
|
1445
|
+
fs5.writeFileSync(envFilePath, response.content, "utf-8");
|
|
1475
1446
|
const lines = response.content.split("\n").filter((line) => {
|
|
1476
1447
|
const trimmed = line.trim();
|
|
1477
1448
|
return trimmed.length > 0 && !trimmed.startsWith("#");
|
|
1478
1449
|
});
|
|
1479
|
-
console.log(
|
|
1450
|
+
console.log(pc6.green(`
|
|
1480
1451
|
\u2713 Secrets downloaded successfully`));
|
|
1481
1452
|
console.log(`
|
|
1482
|
-
File: ${
|
|
1483
|
-
console.log(`Variables: ${
|
|
1453
|
+
File: ${pc6.cyan(envFile)}`);
|
|
1454
|
+
console.log(`Variables: ${pc6.cyan(lines.length.toString())}`);
|
|
1484
1455
|
await shutdownAnalytics();
|
|
1485
1456
|
} catch (error) {
|
|
1486
1457
|
const message = error instanceof APIError ? `API ${error.statusCode}: ${error.message}` : error instanceof Error ? truncateMessage(error.message) : "Unknown error";
|
|
@@ -1489,20 +1460,20 @@ File: ${pc5.cyan(envFile)}`);
|
|
|
1489
1460
|
error: message
|
|
1490
1461
|
});
|
|
1491
1462
|
await shutdownAnalytics();
|
|
1492
|
-
console.error(
|
|
1463
|
+
console.error(pc6.red(`
|
|
1493
1464
|
\u2717 ${message}`));
|
|
1494
1465
|
process.exit(1);
|
|
1495
1466
|
}
|
|
1496
1467
|
}
|
|
1497
1468
|
|
|
1498
1469
|
// src/cmds/doctor.ts
|
|
1499
|
-
import
|
|
1470
|
+
import pc7 from "picocolors";
|
|
1500
1471
|
|
|
1501
1472
|
// src/core/doctor.ts
|
|
1502
1473
|
import { execSync as execSync2 } from "child_process";
|
|
1503
|
-
import { writeFileSync
|
|
1474
|
+
import { writeFileSync, unlinkSync, readFileSync, existsSync } from "fs";
|
|
1504
1475
|
import { tmpdir } from "os";
|
|
1505
|
-
import { join
|
|
1476
|
+
import { join } from "path";
|
|
1506
1477
|
var API_HEALTH_URL = `${process.env.KEYWAY_API_URL || INTERNAL_API_URL}/v1/health`;
|
|
1507
1478
|
async function checkNode() {
|
|
1508
1479
|
const nodeVersion = process.versions.node;
|
|
@@ -1619,9 +1590,9 @@ async function checkNetwork() {
|
|
|
1619
1590
|
}
|
|
1620
1591
|
}
|
|
1621
1592
|
async function checkFileSystem() {
|
|
1622
|
-
const testFile =
|
|
1593
|
+
const testFile = join(tmpdir(), `.keyway-test-${Date.now()}.tmp`);
|
|
1623
1594
|
try {
|
|
1624
|
-
|
|
1595
|
+
writeFileSync(testFile, "test");
|
|
1625
1596
|
unlinkSync(testFile);
|
|
1626
1597
|
return {
|
|
1627
1598
|
id: "filesystem",
|
|
@@ -1640,7 +1611,7 @@ async function checkFileSystem() {
|
|
|
1640
1611
|
}
|
|
1641
1612
|
async function checkGitignore() {
|
|
1642
1613
|
try {
|
|
1643
|
-
if (!
|
|
1614
|
+
if (!existsSync(".gitignore")) {
|
|
1644
1615
|
return {
|
|
1645
1616
|
id: "gitignore",
|
|
1646
1617
|
name: ".gitignore configuration",
|
|
@@ -1648,7 +1619,7 @@ async function checkGitignore() {
|
|
|
1648
1619
|
detail: "No .gitignore file found"
|
|
1649
1620
|
};
|
|
1650
1621
|
}
|
|
1651
|
-
const gitignoreContent =
|
|
1622
|
+
const gitignoreContent = readFileSync(".gitignore", "utf-8");
|
|
1652
1623
|
const hasEnvPattern = gitignoreContent.includes("*.env") || gitignoreContent.includes(".env*");
|
|
1653
1624
|
const hasDotEnv = gitignoreContent.includes(".env");
|
|
1654
1625
|
if (hasEnvPattern || hasDotEnv) {
|
|
@@ -1750,9 +1721,9 @@ async function runAllChecks(options = {}) {
|
|
|
1750
1721
|
// src/cmds/doctor.ts
|
|
1751
1722
|
function formatSummary(results) {
|
|
1752
1723
|
const parts = [
|
|
1753
|
-
|
|
1754
|
-
results.summary.warn > 0 ?
|
|
1755
|
-
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
|
|
1756
1727
|
].filter(Boolean);
|
|
1757
1728
|
return parts.join(", ");
|
|
1758
1729
|
}
|
|
@@ -1769,20 +1740,20 @@ async function doctorCommand(options = {}) {
|
|
|
1769
1740
|
process.stdout.write(JSON.stringify(results, null, 0) + "\n");
|
|
1770
1741
|
process.exit(results.exitCode);
|
|
1771
1742
|
}
|
|
1772
|
-
console.log(
|
|
1743
|
+
console.log(pc7.cyan("\n\u{1F50D} Keyway Doctor - Environment Check\n"));
|
|
1773
1744
|
results.checks.forEach((check) => {
|
|
1774
|
-
const icon = check.status === "pass" ?
|
|
1775
|
-
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}`) : "";
|
|
1776
1747
|
console.log(` ${icon} ${check.name}${detail}`);
|
|
1777
1748
|
});
|
|
1778
1749
|
console.log(`
|
|
1779
1750
|
Summary: ${formatSummary(results)}`);
|
|
1780
1751
|
if (results.summary.fail > 0) {
|
|
1781
|
-
console.log(
|
|
1752
|
+
console.log(pc7.red("\u26A0 Some checks failed. Please resolve the issues above before using Keyway."));
|
|
1782
1753
|
} else if (results.summary.warn > 0) {
|
|
1783
|
-
console.log(
|
|
1754
|
+
console.log(pc7.yellow("\u26A0 Some warnings detected. Keyway should work but consider addressing them."));
|
|
1784
1755
|
} else {
|
|
1785
|
-
console.log(
|
|
1756
|
+
console.log(pc7.green("\u2728 All checks passed! Your environment is ready for Keyway."));
|
|
1786
1757
|
}
|
|
1787
1758
|
process.exit(results.exitCode);
|
|
1788
1759
|
} catch (error) {
|
|
@@ -1803,7 +1774,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1803
1774
|
};
|
|
1804
1775
|
process.stdout.write(JSON.stringify(errorResult, null, 0) + "\n");
|
|
1805
1776
|
} else {
|
|
1806
|
-
console.error(
|
|
1777
|
+
console.error(pc7.red(`
|
|
1807
1778
|
\u2717 ${message}`));
|
|
1808
1779
|
}
|
|
1809
1780
|
process.exit(1);
|
|
@@ -1811,7 +1782,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1811
1782
|
}
|
|
1812
1783
|
|
|
1813
1784
|
// src/cmds/connect.ts
|
|
1814
|
-
import
|
|
1785
|
+
import pc8 from "picocolors";
|
|
1815
1786
|
import open3 from "open";
|
|
1816
1787
|
import prompts6 from "prompts";
|
|
1817
1788
|
async function connectCommand(provider, options = {}) {
|
|
@@ -1821,13 +1792,13 @@ async function connectCommand(provider, options = {}) {
|
|
|
1821
1792
|
const providerInfo = providers.find((p) => p.name === provider.toLowerCase());
|
|
1822
1793
|
if (!providerInfo) {
|
|
1823
1794
|
const available = providers.map((p) => p.name).join(", ");
|
|
1824
|
-
console.error(
|
|
1825
|
-
console.log(
|
|
1795
|
+
console.error(pc8.red(`Unknown provider: ${provider}`));
|
|
1796
|
+
console.log(pc8.gray(`Available providers: ${available || "none"}`));
|
|
1826
1797
|
process.exit(1);
|
|
1827
1798
|
}
|
|
1828
1799
|
if (!providerInfo.configured) {
|
|
1829
|
-
console.error(
|
|
1830
|
-
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."));
|
|
1831
1802
|
process.exit(1);
|
|
1832
1803
|
}
|
|
1833
1804
|
const { connections } = await getConnections(accessToken);
|
|
@@ -1840,20 +1811,20 @@ async function connectCommand(provider, options = {}) {
|
|
|
1840
1811
|
initial: false
|
|
1841
1812
|
});
|
|
1842
1813
|
if (!reconnect) {
|
|
1843
|
-
console.log(
|
|
1814
|
+
console.log(pc8.gray("Keeping existing connection."));
|
|
1844
1815
|
return;
|
|
1845
1816
|
}
|
|
1846
1817
|
}
|
|
1847
|
-
console.log(
|
|
1818
|
+
console.log(pc8.blue(`
|
|
1848
1819
|
Connecting to ${providerInfo.displayName}...
|
|
1849
1820
|
`));
|
|
1850
1821
|
const authUrl = getProviderAuthUrl(provider.toLowerCase());
|
|
1851
1822
|
const startTime = /* @__PURE__ */ new Date();
|
|
1852
|
-
console.log(
|
|
1853
|
-
console.log(
|
|
1823
|
+
console.log(pc8.gray("Opening browser for authorization..."));
|
|
1824
|
+
console.log(pc8.gray(`If the browser doesn't open, visit: ${authUrl}`));
|
|
1854
1825
|
await open3(authUrl).catch(() => {
|
|
1855
1826
|
});
|
|
1856
|
-
console.log(
|
|
1827
|
+
console.log(pc8.gray("Waiting for authorization..."));
|
|
1857
1828
|
const maxAttempts = 60;
|
|
1858
1829
|
let attempts = 0;
|
|
1859
1830
|
let connected = false;
|
|
@@ -1867,7 +1838,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1867
1838
|
);
|
|
1868
1839
|
if (newConn) {
|
|
1869
1840
|
connected = true;
|
|
1870
|
-
console.log(
|
|
1841
|
+
console.log(pc8.green(`
|
|
1871
1842
|
\u2713 Connected to ${providerInfo.displayName}!`));
|
|
1872
1843
|
break;
|
|
1873
1844
|
}
|
|
@@ -1875,8 +1846,8 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1875
1846
|
}
|
|
1876
1847
|
}
|
|
1877
1848
|
if (!connected) {
|
|
1878
|
-
console.log(
|
|
1879
|
-
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."));
|
|
1880
1851
|
}
|
|
1881
1852
|
trackEvent(AnalyticsEvents.CLI_CONNECT, {
|
|
1882
1853
|
provider: provider.toLowerCase(),
|
|
@@ -1888,7 +1859,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1888
1859
|
command: "connect",
|
|
1889
1860
|
error: truncateMessage(message)
|
|
1890
1861
|
});
|
|
1891
|
-
console.error(
|
|
1862
|
+
console.error(pc8.red(`
|
|
1892
1863
|
\u2717 ${message}`));
|
|
1893
1864
|
process.exit(1);
|
|
1894
1865
|
}
|
|
@@ -1898,24 +1869,24 @@ async function connectionsCommand(options = {}) {
|
|
|
1898
1869
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
1899
1870
|
const { connections } = await getConnections(accessToken);
|
|
1900
1871
|
if (connections.length === 0) {
|
|
1901
|
-
console.log(
|
|
1902
|
-
console.log(
|
|
1903
|
-
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"));
|
|
1904
1875
|
return;
|
|
1905
1876
|
}
|
|
1906
|
-
console.log(
|
|
1877
|
+
console.log(pc8.blue("\n\u{1F4E1} Provider Connections\n"));
|
|
1907
1878
|
for (const conn of connections) {
|
|
1908
1879
|
const providerName = conn.provider.charAt(0).toUpperCase() + conn.provider.slice(1);
|
|
1909
|
-
const teamInfo = conn.providerTeamId ?
|
|
1880
|
+
const teamInfo = conn.providerTeamId ? pc8.gray(` (Team: ${conn.providerTeamId})`) : "";
|
|
1910
1881
|
const date = new Date(conn.createdAt).toLocaleDateString();
|
|
1911
|
-
console.log(` ${
|
|
1912
|
-
console.log(
|
|
1913
|
-
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}`));
|
|
1914
1885
|
console.log("");
|
|
1915
1886
|
}
|
|
1916
1887
|
} catch (error) {
|
|
1917
1888
|
const message = error instanceof Error ? error.message : "Failed to list connections";
|
|
1918
|
-
console.error(
|
|
1889
|
+
console.error(pc8.red(`
|
|
1919
1890
|
\u2717 ${message}`));
|
|
1920
1891
|
process.exit(1);
|
|
1921
1892
|
}
|
|
@@ -1926,7 +1897,7 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1926
1897
|
const { connections } = await getConnections(accessToken);
|
|
1927
1898
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
1928
1899
|
if (!connection) {
|
|
1929
|
-
console.log(
|
|
1900
|
+
console.log(pc8.gray(`No connection found for provider: ${provider}`));
|
|
1930
1901
|
return;
|
|
1931
1902
|
}
|
|
1932
1903
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
@@ -1937,11 +1908,11 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1937
1908
|
initial: false
|
|
1938
1909
|
});
|
|
1939
1910
|
if (!confirm) {
|
|
1940
|
-
console.log(
|
|
1911
|
+
console.log(pc8.gray("Cancelled."));
|
|
1941
1912
|
return;
|
|
1942
1913
|
}
|
|
1943
1914
|
await deleteConnection(accessToken, connection.id);
|
|
1944
|
-
console.log(
|
|
1915
|
+
console.log(pc8.green(`
|
|
1945
1916
|
\u2713 Disconnected from ${providerName}`));
|
|
1946
1917
|
trackEvent(AnalyticsEvents.CLI_DISCONNECT, {
|
|
1947
1918
|
provider: provider.toLowerCase()
|
|
@@ -1952,15 +1923,24 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
1952
1923
|
command: "disconnect",
|
|
1953
1924
|
error: truncateMessage(message)
|
|
1954
1925
|
});
|
|
1955
|
-
console.error(
|
|
1926
|
+
console.error(pc8.red(`
|
|
1956
1927
|
\u2717 ${message}`));
|
|
1957
1928
|
process.exit(1);
|
|
1958
1929
|
}
|
|
1959
1930
|
}
|
|
1960
1931
|
|
|
1961
1932
|
// src/cmds/sync.ts
|
|
1962
|
-
import
|
|
1933
|
+
import pc9 from "picocolors";
|
|
1963
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
|
+
}
|
|
1964
1944
|
function findMatchingProject(projects, repoFullName) {
|
|
1965
1945
|
const repoFullNameLower = repoFullName.toLowerCase();
|
|
1966
1946
|
const repoName = repoFullName.split("/")[1]?.toLowerCase();
|
|
@@ -2000,11 +1980,11 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2000
1980
|
let title = p.name;
|
|
2001
1981
|
const badges = [];
|
|
2002
1982
|
if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
|
|
2003
|
-
badges.push(
|
|
1983
|
+
badges.push(pc9.green("\u2190 linked"));
|
|
2004
1984
|
} else if (p.name.toLowerCase() === repoName) {
|
|
2005
|
-
badges.push(
|
|
1985
|
+
badges.push(pc9.green("\u2190 same name"));
|
|
2006
1986
|
} else if (p.linkedRepo) {
|
|
2007
|
-
badges.push(
|
|
1987
|
+
badges.push(pc9.gray(`\u2192 ${p.linkedRepo}`));
|
|
2008
1988
|
}
|
|
2009
1989
|
if (badges.length > 0) {
|
|
2010
1990
|
title = `${p.name} ${badges.join(" ")}`;
|
|
@@ -2018,7 +1998,7 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2018
1998
|
choices
|
|
2019
1999
|
});
|
|
2020
2000
|
if (!projectChoice) {
|
|
2021
|
-
console.log(
|
|
2001
|
+
console.log(pc9.gray("Cancelled."));
|
|
2022
2002
|
process.exit(0);
|
|
2023
2003
|
}
|
|
2024
2004
|
return projects.find((p) => p.id === projectChoice);
|
|
@@ -2026,28 +2006,28 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2026
2006
|
async function syncCommand(provider, options = {}) {
|
|
2027
2007
|
try {
|
|
2028
2008
|
if (options.pull && options.allowDelete) {
|
|
2029
|
-
console.error(
|
|
2030
|
-
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."));
|
|
2031
2011
|
process.exit(1);
|
|
2032
2012
|
}
|
|
2033
2013
|
const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
|
|
2034
2014
|
const repoFullName = detectGitRepo();
|
|
2035
2015
|
if (!repoFullName) {
|
|
2036
|
-
console.error(
|
|
2037
|
-
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."));
|
|
2038
2018
|
process.exit(1);
|
|
2039
2019
|
}
|
|
2040
|
-
console.log(
|
|
2020
|
+
console.log(pc9.gray(`Repository: ${repoFullName}`));
|
|
2041
2021
|
const { connections } = await getConnections(accessToken);
|
|
2042
2022
|
const connection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2043
2023
|
if (!connection) {
|
|
2044
|
-
console.error(
|
|
2045
|
-
console.log(
|
|
2024
|
+
console.error(pc9.red(`Not connected to ${provider}.`));
|
|
2025
|
+
console.log(pc9.gray(`Run: keyway connect ${provider}`));
|
|
2046
2026
|
process.exit(1);
|
|
2047
2027
|
}
|
|
2048
2028
|
const { projects } = await getConnectionProjects(accessToken, connection.id);
|
|
2049
2029
|
if (projects.length === 0) {
|
|
2050
|
-
console.error(
|
|
2030
|
+
console.error(pc9.red(`No projects found in your ${provider} account.`));
|
|
2051
2031
|
process.exit(1);
|
|
2052
2032
|
}
|
|
2053
2033
|
let selectedProject;
|
|
@@ -2056,21 +2036,21 @@ async function syncCommand(provider, options = {}) {
|
|
|
2056
2036
|
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase()
|
|
2057
2037
|
);
|
|
2058
2038
|
if (!found) {
|
|
2059
|
-
console.error(
|
|
2060
|
-
console.log(
|
|
2061
|
-
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}`)));
|
|
2062
2042
|
process.exit(1);
|
|
2063
2043
|
}
|
|
2064
2044
|
selectedProject = found;
|
|
2065
2045
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2066
2046
|
console.log("");
|
|
2067
|
-
console.log(
|
|
2068
|
-
console.log(
|
|
2069
|
-
console.log(
|
|
2070
|
-
console.log(
|
|
2071
|
-
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}`));
|
|
2072
2052
|
if (selectedProject.linkedRepo) {
|
|
2073
|
-
console.log(
|
|
2053
|
+
console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2074
2054
|
}
|
|
2075
2055
|
console.log("");
|
|
2076
2056
|
}
|
|
@@ -2079,9 +2059,9 @@ async function syncCommand(provider, options = {}) {
|
|
|
2079
2059
|
if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
|
|
2080
2060
|
selectedProject = autoMatch.project;
|
|
2081
2061
|
const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
|
|
2082
|
-
console.log(
|
|
2062
|
+
console.log(pc9.green(`\u2713 Auto-selected project: ${selectedProject.name} (${matchReason})`));
|
|
2083
2063
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2084
|
-
console.log(
|
|
2064
|
+
console.log(pc9.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
|
|
2085
2065
|
const { useDetected } = await prompts7({
|
|
2086
2066
|
type: "confirm",
|
|
2087
2067
|
name: "useDetected",
|
|
@@ -2097,13 +2077,13 @@ async function syncCommand(provider, options = {}) {
|
|
|
2097
2077
|
selectedProject = projects[0];
|
|
2098
2078
|
if (!projectMatchesRepo(selectedProject, repoFullName)) {
|
|
2099
2079
|
console.log("");
|
|
2100
|
-
console.log(
|
|
2101
|
-
console.log(
|
|
2102
|
-
console.log(
|
|
2103
|
-
console.log(
|
|
2104
|
-
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}`));
|
|
2105
2085
|
if (selectedProject.linkedRepo) {
|
|
2106
|
-
console.log(
|
|
2086
|
+
console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2107
2087
|
}
|
|
2108
2088
|
console.log("");
|
|
2109
2089
|
const { continueAnyway } = await prompts7({
|
|
@@ -2113,14 +2093,14 @@ async function syncCommand(provider, options = {}) {
|
|
|
2113
2093
|
initial: false
|
|
2114
2094
|
});
|
|
2115
2095
|
if (!continueAnyway) {
|
|
2116
|
-
console.log(
|
|
2096
|
+
console.log(pc9.gray("Cancelled."));
|
|
2117
2097
|
process.exit(0);
|
|
2118
2098
|
}
|
|
2119
2099
|
}
|
|
2120
2100
|
} else {
|
|
2121
|
-
console.log(
|
|
2101
|
+
console.log(pc9.yellow(`
|
|
2122
2102
|
\u26A0\uFE0F No matching project found for ${repoFullName}`));
|
|
2123
|
-
console.log(
|
|
2103
|
+
console.log(pc9.gray("Select a project manually:\n"));
|
|
2124
2104
|
selectedProject = await promptProjectSelection(projects, repoFullName);
|
|
2125
2105
|
}
|
|
2126
2106
|
}
|
|
@@ -2128,13 +2108,13 @@ async function syncCommand(provider, options = {}) {
|
|
|
2128
2108
|
const autoMatch = findMatchingProject(projects, repoFullName);
|
|
2129
2109
|
if (autoMatch && autoMatch.project.id !== selectedProject.id) {
|
|
2130
2110
|
console.log("");
|
|
2131
|
-
console.log(
|
|
2132
|
-
console.log(
|
|
2133
|
-
console.log(
|
|
2134
|
-
console.log(
|
|
2135
|
-
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}`));
|
|
2136
2116
|
if (selectedProject.linkedRepo) {
|
|
2137
|
-
console.log(
|
|
2117
|
+
console.log(pc9.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2138
2118
|
}
|
|
2139
2119
|
console.log("");
|
|
2140
2120
|
const { continueAnyway } = await prompts7({
|
|
@@ -2144,18 +2124,56 @@ async function syncCommand(provider, options = {}) {
|
|
|
2144
2124
|
initial: false
|
|
2145
2125
|
});
|
|
2146
2126
|
if (!continueAnyway) {
|
|
2147
|
-
console.log(
|
|
2127
|
+
console.log(pc9.gray("Cancelled."));
|
|
2148
2128
|
process.exit(0);
|
|
2149
2129
|
}
|
|
2150
2130
|
}
|
|
2151
2131
|
}
|
|
2152
|
-
const keywayEnv = options.environment || "production";
|
|
2153
|
-
const providerEnv = options.providerEnv || "production";
|
|
2154
|
-
const direction = options.pull ? "pull" : "push";
|
|
2155
2132
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
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";
|
|
2159
2177
|
const status = await getSyncStatus(
|
|
2160
2178
|
accessToken,
|
|
2161
2179
|
repoFullName,
|
|
@@ -2163,10 +2181,10 @@ async function syncCommand(provider, options = {}) {
|
|
|
2163
2181
|
selectedProject.id,
|
|
2164
2182
|
keywayEnv
|
|
2165
2183
|
);
|
|
2166
|
-
if (status.isFirstSync &&
|
|
2167
|
-
console.log(
|
|
2184
|
+
if (status.isFirstSync && direction === "push" && status.vaultIsEmpty && status.providerHasSecrets) {
|
|
2185
|
+
console.log(pc9.yellow(`
|
|
2168
2186
|
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2169
|
-
console.log(
|
|
2187
|
+
console.log(pc9.gray(` (Use --environment to sync a different environment)`));
|
|
2170
2188
|
const { importFirst } = await prompts7({
|
|
2171
2189
|
type: "confirm",
|
|
2172
2190
|
name: "importFirst",
|
|
@@ -2208,7 +2226,7 @@ async function syncCommand(provider, options = {}) {
|
|
|
2208
2226
|
command: "sync",
|
|
2209
2227
|
error: truncateMessage(message)
|
|
2210
2228
|
});
|
|
2211
|
-
console.error(
|
|
2229
|
+
console.error(pc9.red(`
|
|
2212
2230
|
\u2717 ${message}`));
|
|
2213
2231
|
process.exit(1);
|
|
2214
2232
|
}
|
|
@@ -2225,33 +2243,33 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2225
2243
|
});
|
|
2226
2244
|
const totalChanges = preview.toCreate.length + preview.toUpdate.length + preview.toDelete.length;
|
|
2227
2245
|
if (totalChanges === 0) {
|
|
2228
|
-
console.log(
|
|
2246
|
+
console.log(pc9.green("\n\u2713 Already in sync. No changes needed."));
|
|
2229
2247
|
return;
|
|
2230
2248
|
}
|
|
2231
|
-
console.log(
|
|
2249
|
+
console.log(pc9.blue("\n\u{1F4CB} Sync Preview\n"));
|
|
2232
2250
|
if (preview.toCreate.length > 0) {
|
|
2233
|
-
console.log(
|
|
2234
|
-
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}`)));
|
|
2235
2253
|
if (preview.toCreate.length > 5) {
|
|
2236
|
-
console.log(
|
|
2254
|
+
console.log(pc9.gray(` ... and ${preview.toCreate.length - 5} more`));
|
|
2237
2255
|
}
|
|
2238
2256
|
}
|
|
2239
2257
|
if (preview.toUpdate.length > 0) {
|
|
2240
|
-
console.log(
|
|
2241
|
-
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}`)));
|
|
2242
2260
|
if (preview.toUpdate.length > 5) {
|
|
2243
|
-
console.log(
|
|
2261
|
+
console.log(pc9.gray(` ... and ${preview.toUpdate.length - 5} more`));
|
|
2244
2262
|
}
|
|
2245
2263
|
}
|
|
2246
2264
|
if (preview.toDelete.length > 0) {
|
|
2247
|
-
console.log(
|
|
2248
|
-
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}`)));
|
|
2249
2267
|
if (preview.toDelete.length > 5) {
|
|
2250
|
-
console.log(
|
|
2268
|
+
console.log(pc9.gray(` ... and ${preview.toDelete.length - 5} more`));
|
|
2251
2269
|
}
|
|
2252
2270
|
}
|
|
2253
2271
|
if (preview.toSkip.length > 0) {
|
|
2254
|
-
console.log(
|
|
2272
|
+
console.log(pc9.gray(` \u25CB ${preview.toSkip.length} unchanged`));
|
|
2255
2273
|
}
|
|
2256
2274
|
console.log("");
|
|
2257
2275
|
if (!skipConfirm) {
|
|
@@ -2263,11 +2281,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2263
2281
|
initial: true
|
|
2264
2282
|
});
|
|
2265
2283
|
if (!confirm) {
|
|
2266
|
-
console.log(
|
|
2284
|
+
console.log(pc9.gray("Cancelled."));
|
|
2267
2285
|
return;
|
|
2268
2286
|
}
|
|
2269
2287
|
}
|
|
2270
|
-
console.log(
|
|
2288
|
+
console.log(pc9.blue("\n\u23F3 Syncing...\n"));
|
|
2271
2289
|
const result = await executeSync(accessToken, repoFullName, {
|
|
2272
2290
|
connectionId,
|
|
2273
2291
|
projectId: project.id,
|
|
@@ -2277,11 +2295,11 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2277
2295
|
allowDelete
|
|
2278
2296
|
});
|
|
2279
2297
|
if (result.success) {
|
|
2280
|
-
console.log(
|
|
2281
|
-
console.log(
|
|
2282
|
-
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}`));
|
|
2283
2301
|
if (result.stats.deleted > 0) {
|
|
2284
|
-
console.log(
|
|
2302
|
+
console.log(pc9.gray(` Deleted: ${result.stats.deleted}`));
|
|
2285
2303
|
}
|
|
2286
2304
|
trackEvent(AnalyticsEvents.CLI_SYNC, {
|
|
2287
2305
|
provider,
|
|
@@ -2291,7 +2309,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2291
2309
|
deleted: result.stats.deleted
|
|
2292
2310
|
});
|
|
2293
2311
|
} else {
|
|
2294
|
-
console.error(
|
|
2312
|
+
console.error(pc9.red(`
|
|
2295
2313
|
\u2717 ${result.error}`));
|
|
2296
2314
|
process.exit(1);
|
|
2297
2315
|
}
|
|
@@ -2301,13 +2319,14 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2301
2319
|
var program = new Command();
|
|
2302
2320
|
var TAGLINE = "Sync secrets with your team and infra";
|
|
2303
2321
|
var showBanner = () => {
|
|
2304
|
-
const text =
|
|
2322
|
+
const text = pc10.bold(pc10.cyan("Keyway CLI"));
|
|
2305
2323
|
console.log(`
|
|
2306
2324
|
${text}
|
|
2307
|
-
${
|
|
2325
|
+
${pc10.gray(TAGLINE)}
|
|
2308
2326
|
`);
|
|
2309
2327
|
};
|
|
2310
2328
|
showBanner();
|
|
2329
|
+
warnIfEnvNotGitignored();
|
|
2311
2330
|
program.name("keyway").description(TAGLINE).version(package_default.version);
|
|
2312
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) => {
|
|
2313
2332
|
await initCommand(options);
|
|
@@ -2336,10 +2355,10 @@ program.command("connections").description("List your provider connections").opt
|
|
|
2336
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) => {
|
|
2337
2356
|
await disconnectCommand(provider, options);
|
|
2338
2357
|
});
|
|
2339
|
-
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) => {
|
|
2340
2359
|
await syncCommand(provider, options);
|
|
2341
2360
|
});
|
|
2342
2361
|
program.parseAsync().catch((error) => {
|
|
2343
|
-
console.error(
|
|
2362
|
+
console.error(pc10.red("Error:"), error.message || error);
|
|
2344
2363
|
process.exit(1);
|
|
2345
2364
|
});
|