@keywaysh/cli 0.0.20 → 0.1.0
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 +380 -173
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import pc9 from "picocolors";
|
|
|
7
7
|
// src/cmds/init.ts
|
|
8
8
|
import pc4 from "picocolors";
|
|
9
9
|
import prompts4 from "prompts";
|
|
10
|
+
import open2 from "open";
|
|
10
11
|
|
|
11
12
|
// src/utils/git.ts
|
|
12
13
|
import { execSync } from "child_process";
|
|
@@ -69,7 +70,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
69
70
|
// package.json
|
|
70
71
|
var package_default = {
|
|
71
72
|
name: "@keywaysh/cli",
|
|
72
|
-
version: "0.0
|
|
73
|
+
version: "0.1.0",
|
|
73
74
|
description: "One link to all your secrets",
|
|
74
75
|
type: "module",
|
|
75
76
|
bin: {
|
|
@@ -338,7 +339,8 @@ async function validateToken(token) {
|
|
|
338
339
|
},
|
|
339
340
|
body: JSON.stringify({})
|
|
340
341
|
});
|
|
341
|
-
|
|
342
|
+
const wrapped = await handleResponse(response);
|
|
343
|
+
return wrapped.data;
|
|
342
344
|
}
|
|
343
345
|
async function getProviders() {
|
|
344
346
|
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations`, {
|
|
@@ -347,7 +349,8 @@ async function getProviders() {
|
|
|
347
349
|
"User-Agent": USER_AGENT
|
|
348
350
|
}
|
|
349
351
|
});
|
|
350
|
-
|
|
352
|
+
const wrapped = await handleResponse(response);
|
|
353
|
+
return wrapped.data;
|
|
351
354
|
}
|
|
352
355
|
async function getConnections(accessToken) {
|
|
353
356
|
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/connections`, {
|
|
@@ -357,7 +360,8 @@ async function getConnections(accessToken) {
|
|
|
357
360
|
Authorization: `Bearer ${accessToken}`
|
|
358
361
|
}
|
|
359
362
|
});
|
|
360
|
-
|
|
363
|
+
const wrapped = await handleResponse(response);
|
|
364
|
+
return wrapped.data;
|
|
361
365
|
}
|
|
362
366
|
async function deleteConnection(accessToken, connectionId) {
|
|
363
367
|
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/connections/${connectionId}`, {
|
|
@@ -367,7 +371,7 @@ async function deleteConnection(accessToken, connectionId) {
|
|
|
367
371
|
Authorization: `Bearer ${accessToken}`
|
|
368
372
|
}
|
|
369
373
|
});
|
|
370
|
-
|
|
374
|
+
await handleResponse(response);
|
|
371
375
|
}
|
|
372
376
|
function getProviderAuthUrl(provider, redirectUri) {
|
|
373
377
|
const params = redirectUri ? `?redirect_uri=${encodeURIComponent(redirectUri)}` : "";
|
|
@@ -381,7 +385,8 @@ async function getConnectionProjects(accessToken, connectionId) {
|
|
|
381
385
|
Authorization: `Bearer ${accessToken}`
|
|
382
386
|
}
|
|
383
387
|
});
|
|
384
|
-
|
|
388
|
+
const wrapped = await handleResponse(response);
|
|
389
|
+
return wrapped.data;
|
|
385
390
|
}
|
|
386
391
|
async function getSyncStatus(accessToken, repoFullName, connectionId, projectId, environment = "production") {
|
|
387
392
|
const [owner, repo] = repoFullName.split("/");
|
|
@@ -400,7 +405,8 @@ async function getSyncStatus(accessToken, repoFullName, connectionId, projectId,
|
|
|
400
405
|
}
|
|
401
406
|
}
|
|
402
407
|
);
|
|
403
|
-
|
|
408
|
+
const wrapped = await handleResponse(response);
|
|
409
|
+
return wrapped.data;
|
|
404
410
|
}
|
|
405
411
|
async function getSyncPreview(accessToken, repoFullName, options) {
|
|
406
412
|
const [owner, repo] = repoFullName.split("/");
|
|
@@ -424,7 +430,8 @@ async function getSyncPreview(accessToken, repoFullName, options) {
|
|
|
424
430
|
6e4
|
|
425
431
|
// 60 seconds for sync operations
|
|
426
432
|
);
|
|
427
|
-
|
|
433
|
+
const wrapped = await handleResponse(response);
|
|
434
|
+
return wrapped.data;
|
|
428
435
|
}
|
|
429
436
|
async function executeSync(accessToken, repoFullName, options) {
|
|
430
437
|
const [owner, repo] = repoFullName.split("/");
|
|
@@ -449,7 +456,21 @@ async function executeSync(accessToken, repoFullName, options) {
|
|
|
449
456
|
12e4
|
|
450
457
|
// 2 minutes for sync execution
|
|
451
458
|
);
|
|
452
|
-
|
|
459
|
+
const wrapped = await handleResponse(response);
|
|
460
|
+
return wrapped.data;
|
|
461
|
+
}
|
|
462
|
+
async function checkGitHubAppInstallation(repoOwner, repoName, accessToken) {
|
|
463
|
+
const response = await fetchWithTimeout(`${API_BASE_URL}/v1/github/check-installation`, {
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: {
|
|
466
|
+
"Content-Type": "application/json",
|
|
467
|
+
"User-Agent": USER_AGENT,
|
|
468
|
+
Authorization: `Bearer ${accessToken}`
|
|
469
|
+
},
|
|
470
|
+
body: JSON.stringify({ repoOwner, repoName })
|
|
471
|
+
});
|
|
472
|
+
const wrapped = await handleResponse(response);
|
|
473
|
+
return wrapped.data;
|
|
453
474
|
}
|
|
454
475
|
|
|
455
476
|
// src/utils/analytics.ts
|
|
@@ -541,6 +562,30 @@ async function shutdownAnalytics() {
|
|
|
541
562
|
await posthog.shutdown();
|
|
542
563
|
}
|
|
543
564
|
}
|
|
565
|
+
function identifyUser(userId, properties) {
|
|
566
|
+
try {
|
|
567
|
+
if (TELEMETRY_DISABLED) return;
|
|
568
|
+
if (!posthog) initPostHog();
|
|
569
|
+
if (!posthog) return;
|
|
570
|
+
const sanitizedProperties = properties ? sanitizeProperties(properties) : {};
|
|
571
|
+
posthog.identify({
|
|
572
|
+
distinctId: userId,
|
|
573
|
+
properties: {
|
|
574
|
+
...sanitizedProperties,
|
|
575
|
+
source: "cli"
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
const anonId = getDistinctId();
|
|
579
|
+
if (anonId && anonId !== userId) {
|
|
580
|
+
posthog.alias({
|
|
581
|
+
distinctId: userId,
|
|
582
|
+
alias: anonId
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
} catch (error) {
|
|
586
|
+
console.debug("Analytics identify error:", error);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
544
589
|
var AnalyticsEvents = {
|
|
545
590
|
CLI_INIT: "cli_init",
|
|
546
591
|
CLI_PUSH: "cli_push",
|
|
@@ -554,45 +599,151 @@ var AnalyticsEvents = {
|
|
|
554
599
|
CLI_FEEDBACK: "cli_feedback"
|
|
555
600
|
};
|
|
556
601
|
|
|
557
|
-
// src/cmds/
|
|
602
|
+
// src/cmds/readme.ts
|
|
603
|
+
import fs2 from "fs";
|
|
604
|
+
import path2 from "path";
|
|
605
|
+
import prompts from "prompts";
|
|
558
606
|
import pc from "picocolors";
|
|
607
|
+
function generateBadge(repo) {
|
|
608
|
+
return `[](https://www.keyway.sh/vaults/${repo})`;
|
|
609
|
+
}
|
|
610
|
+
function insertBadgeIntoReadme(readmeContent, badge) {
|
|
611
|
+
if (readmeContent.includes("keyway.sh/badge.svg")) {
|
|
612
|
+
return readmeContent;
|
|
613
|
+
}
|
|
614
|
+
const lines = readmeContent.split(/\r?\n/);
|
|
615
|
+
const titleIndex = lines.findIndex((line) => /^#(?!#)\s+/.test(line.trim()));
|
|
616
|
+
if (titleIndex !== -1) {
|
|
617
|
+
const before = lines.slice(0, titleIndex + 1);
|
|
618
|
+
const after = lines.slice(titleIndex + 1);
|
|
619
|
+
while (after.length > 0 && after[0].trim() === "") {
|
|
620
|
+
after.shift();
|
|
621
|
+
}
|
|
622
|
+
const newLines = [...before, "", badge, "", ...after];
|
|
623
|
+
return newLines.join("\n");
|
|
624
|
+
}
|
|
625
|
+
return `${badge}
|
|
626
|
+
|
|
627
|
+
${readmeContent}`;
|
|
628
|
+
}
|
|
629
|
+
function findReadmePath(cwd) {
|
|
630
|
+
const candidates = ["README.md", "readme.md", "Readme.md"];
|
|
631
|
+
for (const candidate of candidates) {
|
|
632
|
+
const candidatePath = path2.join(cwd, candidate);
|
|
633
|
+
if (fs2.existsSync(candidatePath)) {
|
|
634
|
+
return candidatePath;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
async function ensureReadme(repoName, cwd) {
|
|
640
|
+
const existing = findReadmePath(cwd);
|
|
641
|
+
if (existing) return existing;
|
|
642
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
643
|
+
if (!isInteractive3) {
|
|
644
|
+
console.log(pc.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
const { confirm } = await prompts(
|
|
648
|
+
{
|
|
649
|
+
type: "confirm",
|
|
650
|
+
name: "confirm",
|
|
651
|
+
message: "No README found. Create a default README.md?",
|
|
652
|
+
initial: false
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
onCancel: () => ({ confirm: false })
|
|
656
|
+
}
|
|
657
|
+
);
|
|
658
|
+
if (!confirm) {
|
|
659
|
+
console.log(pc.yellow("Skipping badge insertion (no README)."));
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
const defaultPath = path2.join(cwd, "README.md");
|
|
663
|
+
const content = `# ${repoName}
|
|
664
|
+
|
|
665
|
+
`;
|
|
666
|
+
fs2.writeFileSync(defaultPath, content, "utf-8");
|
|
667
|
+
return defaultPath;
|
|
668
|
+
}
|
|
669
|
+
async function addBadgeToReadme(silent = false) {
|
|
670
|
+
const repo = detectGitRepo();
|
|
671
|
+
if (!repo) {
|
|
672
|
+
throw new Error("This directory is not a Git repository.");
|
|
673
|
+
}
|
|
674
|
+
const cwd = process.cwd();
|
|
675
|
+
const readmePath = await ensureReadme(repo, cwd);
|
|
676
|
+
if (!readmePath) return false;
|
|
677
|
+
const badge = generateBadge(repo);
|
|
678
|
+
const content = fs2.readFileSync(readmePath, "utf-8");
|
|
679
|
+
const updated = insertBadgeIntoReadme(content, badge);
|
|
680
|
+
if (updated === content) {
|
|
681
|
+
if (!silent) {
|
|
682
|
+
console.log(pc.gray("Keyway badge already present in README."));
|
|
683
|
+
}
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
fs2.writeFileSync(readmePath, updated, "utf-8");
|
|
687
|
+
if (!silent) {
|
|
688
|
+
console.log(pc.green(`\u2713 Keyway badge added to ${path2.basename(readmePath)}`));
|
|
689
|
+
}
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/cmds/push.ts
|
|
694
|
+
import pc3 from "picocolors";
|
|
695
|
+
import fs3 from "fs";
|
|
696
|
+
import path3 from "path";
|
|
697
|
+
import prompts3 from "prompts";
|
|
698
|
+
|
|
699
|
+
// src/cmds/login.ts
|
|
700
|
+
import pc2 from "picocolors";
|
|
559
701
|
import readline from "readline";
|
|
560
702
|
import open from "open";
|
|
561
|
-
import
|
|
703
|
+
import prompts2 from "prompts";
|
|
562
704
|
|
|
563
705
|
// src/utils/auth.ts
|
|
564
706
|
import Conf from "conf";
|
|
565
|
-
import { createCipheriv, createDecipheriv, randomBytes
|
|
566
|
-
import {
|
|
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";
|
|
567
711
|
var store = new Conf({
|
|
568
712
|
projectName: "keyway",
|
|
569
713
|
configName: "config",
|
|
570
714
|
fileMode: 384
|
|
571
715
|
});
|
|
572
|
-
var
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
+
}
|
|
581
735
|
return key;
|
|
582
736
|
}
|
|
583
|
-
|
|
584
|
-
const key =
|
|
737
|
+
function encryptToken(token) {
|
|
738
|
+
const key = getOrCreateEncryptionKey();
|
|
585
739
|
const iv = randomBytes(16);
|
|
586
740
|
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
587
|
-
const encrypted = Buffer.concat([
|
|
588
|
-
cipher.update(token, "utf8"),
|
|
589
|
-
cipher.final()
|
|
590
|
-
]);
|
|
741
|
+
const encrypted = Buffer.concat([cipher.update(token, "utf8"), cipher.final()]);
|
|
591
742
|
const authTag = cipher.getAuthTag();
|
|
592
743
|
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted.toString("hex")}`;
|
|
593
744
|
}
|
|
594
|
-
|
|
595
|
-
const key =
|
|
745
|
+
function decryptToken(encryptedData) {
|
|
746
|
+
const key = getOrCreateEncryptionKey();
|
|
596
747
|
const parts = encryptedData.split(":");
|
|
597
748
|
if (parts.length !== 3) {
|
|
598
749
|
throw new Error("Invalid encrypted token format");
|
|
@@ -602,10 +753,7 @@ async function decryptToken(encryptedData) {
|
|
|
602
753
|
const encrypted = Buffer.from(parts[2], "hex");
|
|
603
754
|
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
604
755
|
decipher.setAuthTag(authTag);
|
|
605
|
-
const decrypted = Buffer.concat([
|
|
606
|
-
decipher.update(encrypted),
|
|
607
|
-
decipher.final()
|
|
608
|
-
]);
|
|
756
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
609
757
|
return decrypted.toString("utf8");
|
|
610
758
|
}
|
|
611
759
|
function isExpired(auth) {
|
|
@@ -620,14 +768,14 @@ async function getStoredAuth() {
|
|
|
620
768
|
return null;
|
|
621
769
|
}
|
|
622
770
|
try {
|
|
623
|
-
const decrypted =
|
|
771
|
+
const decrypted = decryptToken(encryptedData);
|
|
624
772
|
const auth = JSON.parse(decrypted);
|
|
625
773
|
if (isExpired(auth)) {
|
|
626
774
|
clearAuth();
|
|
627
775
|
return null;
|
|
628
776
|
}
|
|
629
777
|
return auth;
|
|
630
|
-
} catch
|
|
778
|
+
} catch {
|
|
631
779
|
console.error("Failed to decrypt stored auth, clearing...");
|
|
632
780
|
clearAuth();
|
|
633
781
|
return null;
|
|
@@ -640,7 +788,7 @@ async function saveAuthToken(token, meta) {
|
|
|
640
788
|
expiresAt: meta?.expiresAt,
|
|
641
789
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
642
790
|
};
|
|
643
|
-
const encrypted =
|
|
791
|
+
const encrypted = encryptToken(JSON.stringify(auth));
|
|
644
792
|
store.set("auth", encrypted);
|
|
645
793
|
}
|
|
646
794
|
function clearAuth() {
|
|
@@ -674,17 +822,17 @@ async function promptYesNo(question, defaultYes = true) {
|
|
|
674
822
|
});
|
|
675
823
|
}
|
|
676
824
|
async function runLoginFlow() {
|
|
677
|
-
console.log(
|
|
825
|
+
console.log(pc2.blue("\u{1F510} Starting Keyway login...\n"));
|
|
678
826
|
const repoName = detectGitRepo();
|
|
679
827
|
const start = await startDeviceLogin(repoName);
|
|
680
828
|
const verifyUrl = start.verificationUriComplete || start.verificationUri;
|
|
681
829
|
if (!verifyUrl) {
|
|
682
830
|
throw new Error("Missing verification URL from the auth server.");
|
|
683
831
|
}
|
|
684
|
-
console.log(`Code: ${
|
|
832
|
+
console.log(`Code: ${pc2.bold(pc2.green(start.userCode))}`);
|
|
685
833
|
console.log("Waiting for auth...");
|
|
686
834
|
open(verifyUrl).catch(() => {
|
|
687
|
-
console.log(
|
|
835
|
+
console.log(pc2.gray(`Open this URL in your browser: ${verifyUrl}`));
|
|
688
836
|
});
|
|
689
837
|
const pollIntervalMs = (start.interval ?? 5) * 1e3;
|
|
690
838
|
const maxTimeoutMs = Math.min((start.expiresIn ?? 900) * 1e3, 30 * 60 * 1e3);
|
|
@@ -707,9 +855,15 @@ async function runLoginFlow() {
|
|
|
707
855
|
method: "device",
|
|
708
856
|
repo: repoName
|
|
709
857
|
});
|
|
710
|
-
console.log(pc.green("\n\u2713 Login successful"));
|
|
711
858
|
if (result.githubLogin) {
|
|
712
|
-
|
|
859
|
+
identifyUser(result.githubLogin, {
|
|
860
|
+
github_username: result.githubLogin,
|
|
861
|
+
login_method: "device"
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
console.log(pc2.green("\n\u2713 Login successful"));
|
|
865
|
+
if (result.githubLogin) {
|
|
866
|
+
console.log(`Authenticated GitHub user: ${pc2.cyan(result.githubLogin)}`);
|
|
713
867
|
}
|
|
714
868
|
return result.keywayToken;
|
|
715
869
|
}
|
|
@@ -722,7 +876,7 @@ async function ensureLogin(options = {}) {
|
|
|
722
876
|
return envToken;
|
|
723
877
|
}
|
|
724
878
|
if (process.env.GITHUB_TOKEN && !process.env.KEYWAY_TOKEN) {
|
|
725
|
-
console.warn(
|
|
879
|
+
console.warn(pc2.yellow("Note: GITHUB_TOKEN found but not used. Set KEYWAY_TOKEN for Keyway authentication."));
|
|
726
880
|
}
|
|
727
881
|
const stored = await getStoredAuth();
|
|
728
882
|
if (stored?.keywayToken) {
|
|
@@ -742,17 +896,17 @@ async function ensureLogin(options = {}) {
|
|
|
742
896
|
async function runTokenLogin() {
|
|
743
897
|
const repoName = detectGitRepo();
|
|
744
898
|
if (repoName) {
|
|
745
|
-
console.log(`\u{1F4C1} Detected: ${
|
|
899
|
+
console.log(`\u{1F4C1} Detected: ${pc2.cyan(repoName)}`);
|
|
746
900
|
}
|
|
747
901
|
const description = repoName ? `Keyway CLI for ${repoName}` : "Keyway CLI";
|
|
748
902
|
const url = `https://github.com/settings/personal-access-tokens/new?description=${encodeURIComponent(description)}`;
|
|
749
903
|
console.log("Opening GitHub...");
|
|
750
904
|
open(url).catch(() => {
|
|
751
|
-
console.log(
|
|
905
|
+
console.log(pc2.gray(`Open this URL in your browser: ${url}`));
|
|
752
906
|
});
|
|
753
|
-
console.log(
|
|
754
|
-
console.log(
|
|
755
|
-
const { token } = await
|
|
907
|
+
console.log(pc2.gray("Select the detected repo (or scope manually)."));
|
|
908
|
+
console.log(pc2.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
|
|
909
|
+
const { token } = await prompts2(
|
|
756
910
|
{
|
|
757
911
|
type: "password",
|
|
758
912
|
name: "token",
|
|
@@ -784,7 +938,11 @@ async function runTokenLogin() {
|
|
|
784
938
|
method: "pat",
|
|
785
939
|
repo: repoName
|
|
786
940
|
});
|
|
787
|
-
|
|
941
|
+
identifyUser(validation.username, {
|
|
942
|
+
github_username: validation.username,
|
|
943
|
+
login_method: "pat"
|
|
944
|
+
});
|
|
945
|
+
console.log(pc2.green("\u2705 Authenticated"), `as ${pc2.cyan(`@${validation.username}`)}`);
|
|
788
946
|
return trimmedToken;
|
|
789
947
|
}
|
|
790
948
|
async function loginCommand(options = {}) {
|
|
@@ -800,113 +958,18 @@ async function loginCommand(options = {}) {
|
|
|
800
958
|
command: "login",
|
|
801
959
|
error: truncateMessage(message)
|
|
802
960
|
});
|
|
803
|
-
console.error(
|
|
961
|
+
console.error(pc2.red(`
|
|
804
962
|
\u2717 ${message}`));
|
|
805
963
|
process.exit(1);
|
|
806
964
|
}
|
|
807
965
|
}
|
|
808
966
|
async function logoutCommand() {
|
|
809
967
|
clearAuth();
|
|
810
|
-
console.log(
|
|
811
|
-
console.log(
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// src/cmds/readme.ts
|
|
815
|
-
import fs2 from "fs";
|
|
816
|
-
import path2 from "path";
|
|
817
|
-
import prompts2 from "prompts";
|
|
818
|
-
import pc2 from "picocolors";
|
|
819
|
-
function generateBadge(repo) {
|
|
820
|
-
return `[](https://www.keyway.sh/vaults/${repo})`;
|
|
821
|
-
}
|
|
822
|
-
function insertBadgeIntoReadme(readmeContent, badge) {
|
|
823
|
-
if (readmeContent.includes("keyway.sh/badge.svg")) {
|
|
824
|
-
return readmeContent;
|
|
825
|
-
}
|
|
826
|
-
const lines = readmeContent.split(/\r?\n/);
|
|
827
|
-
const titleIndex = lines.findIndex((line) => /^#(?!#)\s+/.test(line.trim()));
|
|
828
|
-
if (titleIndex !== -1) {
|
|
829
|
-
const before = lines.slice(0, titleIndex + 1);
|
|
830
|
-
const after = lines.slice(titleIndex + 1);
|
|
831
|
-
while (after.length > 0 && after[0].trim() === "") {
|
|
832
|
-
after.shift();
|
|
833
|
-
}
|
|
834
|
-
const newLines = [...before, "", badge, "", ...after];
|
|
835
|
-
return newLines.join("\n");
|
|
836
|
-
}
|
|
837
|
-
return `${badge}
|
|
838
|
-
|
|
839
|
-
${readmeContent}`;
|
|
840
|
-
}
|
|
841
|
-
function findReadmePath(cwd) {
|
|
842
|
-
const candidates = ["README.md", "readme.md", "Readme.md"];
|
|
843
|
-
for (const candidate of candidates) {
|
|
844
|
-
const candidatePath = path2.join(cwd, candidate);
|
|
845
|
-
if (fs2.existsSync(candidatePath)) {
|
|
846
|
-
return candidatePath;
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
851
|
-
async function ensureReadme(repoName, cwd) {
|
|
852
|
-
const existing = findReadmePath(cwd);
|
|
853
|
-
if (existing) return existing;
|
|
854
|
-
const isInteractive2 = process.stdin.isTTY && process.stdout.isTTY;
|
|
855
|
-
if (!isInteractive2) {
|
|
856
|
-
console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
857
|
-
return null;
|
|
858
|
-
}
|
|
859
|
-
const { confirm } = await prompts2(
|
|
860
|
-
{
|
|
861
|
-
type: "confirm",
|
|
862
|
-
name: "confirm",
|
|
863
|
-
message: "No README found. Create a default README.md?",
|
|
864
|
-
initial: false
|
|
865
|
-
},
|
|
866
|
-
{
|
|
867
|
-
onCancel: () => ({ confirm: false })
|
|
868
|
-
}
|
|
869
|
-
);
|
|
870
|
-
if (!confirm) {
|
|
871
|
-
console.log(pc2.yellow("Skipping badge insertion (no README)."));
|
|
872
|
-
return null;
|
|
873
|
-
}
|
|
874
|
-
const defaultPath = path2.join(cwd, "README.md");
|
|
875
|
-
const content = `# ${repoName}
|
|
876
|
-
|
|
877
|
-
`;
|
|
878
|
-
fs2.writeFileSync(defaultPath, content, "utf-8");
|
|
879
|
-
return defaultPath;
|
|
880
|
-
}
|
|
881
|
-
async function addBadgeToReadme(silent = false) {
|
|
882
|
-
const repo = detectGitRepo();
|
|
883
|
-
if (!repo) {
|
|
884
|
-
throw new Error("This directory is not a Git repository.");
|
|
885
|
-
}
|
|
886
|
-
const cwd = process.cwd();
|
|
887
|
-
const readmePath = await ensureReadme(repo, cwd);
|
|
888
|
-
if (!readmePath) return false;
|
|
889
|
-
const badge = generateBadge(repo);
|
|
890
|
-
const content = fs2.readFileSync(readmePath, "utf-8");
|
|
891
|
-
const updated = insertBadgeIntoReadme(content, badge);
|
|
892
|
-
if (updated === content) {
|
|
893
|
-
if (!silent) {
|
|
894
|
-
console.log(pc2.gray("Keyway badge already present in README."));
|
|
895
|
-
}
|
|
896
|
-
return false;
|
|
897
|
-
}
|
|
898
|
-
fs2.writeFileSync(readmePath, updated, "utf-8");
|
|
899
|
-
if (!silent) {
|
|
900
|
-
console.log(pc2.green(`\u2713 Keyway badge added to ${path2.basename(readmePath)}`));
|
|
901
|
-
}
|
|
902
|
-
return true;
|
|
968
|
+
console.log(pc2.green("\u2713 Logged out of Keyway"));
|
|
969
|
+
console.log(pc2.gray(`Auth cache cleared: ${getAuthFilePath()}`));
|
|
903
970
|
}
|
|
904
971
|
|
|
905
972
|
// src/cmds/push.ts
|
|
906
|
-
import pc3 from "picocolors";
|
|
907
|
-
import fs3 from "fs";
|
|
908
|
-
import path3 from "path";
|
|
909
|
-
import prompts3 from "prompts";
|
|
910
973
|
function deriveEnvFromFile(file) {
|
|
911
974
|
const base = path3.basename(file);
|
|
912
975
|
const match = base.match(/\.env(?:\.(.+))?$/);
|
|
@@ -947,7 +1010,7 @@ function discoverEnvCandidates(cwd) {
|
|
|
947
1010
|
async function pushCommand(options) {
|
|
948
1011
|
try {
|
|
949
1012
|
console.log(pc3.blue("\u{1F510} Pushing secrets to Keyway...\n"));
|
|
950
|
-
const
|
|
1013
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
951
1014
|
let environment = options.env;
|
|
952
1015
|
let envFile = options.file;
|
|
953
1016
|
const candidates = discoverEnvCandidates(process.cwd());
|
|
@@ -957,7 +1020,7 @@ async function pushCommand(options) {
|
|
|
957
1020
|
envFile = match.file;
|
|
958
1021
|
}
|
|
959
1022
|
}
|
|
960
|
-
if (!environment && !envFile &&
|
|
1023
|
+
if (!environment && !envFile && isInteractive3 && candidates.length > 0) {
|
|
961
1024
|
const { choice } = await prompts3(
|
|
962
1025
|
{
|
|
963
1026
|
type: "select",
|
|
@@ -1011,7 +1074,7 @@ async function pushCommand(options) {
|
|
|
1011
1074
|
}
|
|
1012
1075
|
let envFilePath = path3.resolve(process.cwd(), envFile);
|
|
1013
1076
|
if (!fs3.existsSync(envFilePath)) {
|
|
1014
|
-
if (!
|
|
1077
|
+
if (!isInteractive3) {
|
|
1015
1078
|
throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
|
|
1016
1079
|
}
|
|
1017
1080
|
const { newPath } = await prompts3(
|
|
@@ -1052,8 +1115,8 @@ async function pushCommand(options) {
|
|
|
1052
1115
|
const repoFullName = getCurrentRepoFullName();
|
|
1053
1116
|
console.log(`Repository: ${pc3.cyan(repoFullName)}`);
|
|
1054
1117
|
if (!options.yes) {
|
|
1055
|
-
const
|
|
1056
|
-
if (!
|
|
1118
|
+
const isInteractive4 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1119
|
+
if (!isInteractive4) {
|
|
1057
1120
|
throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
1058
1121
|
}
|
|
1059
1122
|
const { confirm } = await prompts3(
|
|
@@ -1110,6 +1173,12 @@ Your secrets are now encrypted and stored securely.`);
|
|
|
1110
1173
|
hint = `Available environments: ${availableEnvs}
|
|
1111
1174
|
Use ${pc3.cyan(`keyway push --env <environment>`)} to specify one, or create '${requestedEnv}' via the dashboard.`;
|
|
1112
1175
|
}
|
|
1176
|
+
if (error.statusCode === 403 && error.upgradeUrl) {
|
|
1177
|
+
hint = `${pc3.yellow("\u26A1")} Upgrade to Pro: ${pc3.cyan(error.upgradeUrl)}`;
|
|
1178
|
+
} else if (error.statusCode === 403 && message.toLowerCase().includes("read-only")) {
|
|
1179
|
+
message = "This vault is read-only on your current plan.";
|
|
1180
|
+
hint = `Upgrade to Pro to unlock editing: ${pc3.cyan("https://keyway.sh/settings")}`;
|
|
1181
|
+
}
|
|
1113
1182
|
} else if (error instanceof Error) {
|
|
1114
1183
|
message = truncateMessage(error.message);
|
|
1115
1184
|
} else {
|
|
@@ -1132,15 +1201,150 @@ ${hint}`));
|
|
|
1132
1201
|
|
|
1133
1202
|
// src/cmds/init.ts
|
|
1134
1203
|
var DASHBOARD_URL = "https://www.keyway.sh/dashboard/vaults";
|
|
1204
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
1205
|
+
var POLL_TIMEOUT_MS = 12e4;
|
|
1206
|
+
function sleep2(ms) {
|
|
1207
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1208
|
+
}
|
|
1209
|
+
function isInteractive2() {
|
|
1210
|
+
return Boolean(process.stdout.isTTY && process.stdin.isTTY && !process.env.CI);
|
|
1211
|
+
}
|
|
1212
|
+
async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
1213
|
+
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1214
|
+
const envToken = process.env.KEYWAY_TOKEN;
|
|
1215
|
+
if (envToken) {
|
|
1216
|
+
return ensureGitHubAppInstalledOnly(repoFullName, envToken);
|
|
1217
|
+
}
|
|
1218
|
+
const stored = await getStoredAuth();
|
|
1219
|
+
if (stored?.keywayToken) {
|
|
1220
|
+
return ensureGitHubAppInstalledOnly(repoFullName, stored.keywayToken);
|
|
1221
|
+
}
|
|
1222
|
+
const allowPrompt = options.allowPrompt !== false;
|
|
1223
|
+
if (!allowPrompt || !isInteractive2()) {
|
|
1224
|
+
throw new Error('No Keyway session found. Run "keyway login" to authenticate.');
|
|
1225
|
+
}
|
|
1226
|
+
console.log("");
|
|
1227
|
+
console.log(pc4.gray(" Keyway uses a GitHub App for secure access."));
|
|
1228
|
+
console.log(pc4.gray(" Installing the app will also log you in."));
|
|
1229
|
+
console.log("");
|
|
1230
|
+
const { shouldProceed } = await prompts4({
|
|
1231
|
+
type: "confirm",
|
|
1232
|
+
name: "shouldProceed",
|
|
1233
|
+
message: "Open browser to install Keyway & sign in?",
|
|
1234
|
+
initial: true
|
|
1235
|
+
});
|
|
1236
|
+
if (!shouldProceed) {
|
|
1237
|
+
throw new Error('Setup required. Run "keyway init" when ready.');
|
|
1238
|
+
}
|
|
1239
|
+
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1240
|
+
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1241
|
+
console.log(pc4.gray("\n Opening browser..."));
|
|
1242
|
+
await open2(installUrl);
|
|
1243
|
+
console.log("");
|
|
1244
|
+
console.log(pc4.blue("\u23F3 Waiting for installation & authorization..."));
|
|
1245
|
+
console.log(pc4.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1246
|
+
const pollIntervalMs = Math.max((deviceStart.interval ?? 5) * 1e3, POLL_INTERVAL_MS);
|
|
1247
|
+
const startTime = Date.now();
|
|
1248
|
+
let accessToken = null;
|
|
1249
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1250
|
+
await sleep2(pollIntervalMs);
|
|
1251
|
+
try {
|
|
1252
|
+
if (!accessToken) {
|
|
1253
|
+
const result = await pollDeviceLogin(deviceStart.deviceCode);
|
|
1254
|
+
if (result.status === "approved" && result.keywayToken) {
|
|
1255
|
+
accessToken = result.keywayToken;
|
|
1256
|
+
await saveAuthToken(result.keywayToken, {
|
|
1257
|
+
githubLogin: result.githubLogin,
|
|
1258
|
+
expiresAt: result.expiresAt
|
|
1259
|
+
});
|
|
1260
|
+
console.log(pc4.green("\u2713 Signed in!"));
|
|
1261
|
+
if (result.githubLogin) {
|
|
1262
|
+
identifyUser(result.githubLogin, {
|
|
1263
|
+
github_username: result.githubLogin,
|
|
1264
|
+
login_method: "github_app"
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
if (accessToken) {
|
|
1270
|
+
const installStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1271
|
+
if (installStatus.installed) {
|
|
1272
|
+
console.log(pc4.green("\u2713 GitHub App installed!"));
|
|
1273
|
+
console.log("");
|
|
1274
|
+
return accessToken;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
process.stdout.write(pc4.gray("."));
|
|
1278
|
+
} catch {
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
console.log("");
|
|
1282
|
+
console.log(pc4.yellow("\u26A0 Timed out waiting for setup."));
|
|
1283
|
+
console.log(pc4.gray(` Install the GitHub App: ${installUrl}`));
|
|
1284
|
+
throw new Error("Setup timed out. Please try again.");
|
|
1285
|
+
}
|
|
1286
|
+
async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
1287
|
+
const [repoOwner, repoName] = repoFullName.split("/");
|
|
1288
|
+
const status = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1289
|
+
if (status.installed) {
|
|
1290
|
+
return accessToken;
|
|
1291
|
+
}
|
|
1292
|
+
console.log("");
|
|
1293
|
+
console.log(pc4.yellow("\u26A0 GitHub App not installed for this repository"));
|
|
1294
|
+
console.log("");
|
|
1295
|
+
console.log(pc4.gray(" The Keyway GitHub App is required to securely manage secrets."));
|
|
1296
|
+
console.log(pc4.gray(" It only requests minimal permissions (repository metadata)."));
|
|
1297
|
+
console.log("");
|
|
1298
|
+
if (!isInteractive2()) {
|
|
1299
|
+
console.log(pc4.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1300
|
+
throw new Error("GitHub App installation required.");
|
|
1301
|
+
}
|
|
1302
|
+
const { shouldInstall } = await prompts4({
|
|
1303
|
+
type: "confirm",
|
|
1304
|
+
name: "shouldInstall",
|
|
1305
|
+
message: "Open browser to install Keyway GitHub App?",
|
|
1306
|
+
initial: true
|
|
1307
|
+
});
|
|
1308
|
+
if (!shouldInstall) {
|
|
1309
|
+
console.log(pc4.gray(`
|
|
1310
|
+
You can install later: ${status.installUrl}`));
|
|
1311
|
+
throw new Error("GitHub App installation required.");
|
|
1312
|
+
}
|
|
1313
|
+
console.log(pc4.gray("\n Opening browser..."));
|
|
1314
|
+
await open2(status.installUrl);
|
|
1315
|
+
console.log("");
|
|
1316
|
+
console.log(pc4.blue("\u23F3 Waiting for GitHub App installation..."));
|
|
1317
|
+
console.log(pc4.gray(" (Press Ctrl+C to cancel)\n"));
|
|
1318
|
+
const startTime = Date.now();
|
|
1319
|
+
while (Date.now() - startTime < POLL_TIMEOUT_MS) {
|
|
1320
|
+
await sleep2(POLL_INTERVAL_MS);
|
|
1321
|
+
try {
|
|
1322
|
+
const pollStatus = await checkGitHubAppInstallation(repoOwner, repoName, accessToken);
|
|
1323
|
+
if (pollStatus.installed) {
|
|
1324
|
+
console.log(pc4.green("\u2713 GitHub App installed!"));
|
|
1325
|
+
console.log("");
|
|
1326
|
+
return accessToken;
|
|
1327
|
+
}
|
|
1328
|
+
process.stdout.write(pc4.gray("."));
|
|
1329
|
+
} catch {
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
console.log("");
|
|
1333
|
+
console.log(pc4.yellow("\u26A0 Timed out waiting for installation."));
|
|
1334
|
+
console.log(pc4.gray(` You can install the GitHub App later: ${status.installUrl}`));
|
|
1335
|
+
throw new Error("GitHub App installation timed out.");
|
|
1336
|
+
}
|
|
1135
1337
|
async function initCommand(options = {}) {
|
|
1136
1338
|
try {
|
|
1137
1339
|
const repoFullName = getCurrentRepoFullName();
|
|
1138
1340
|
const dashboardLink = `${DASHBOARD_URL}/${repoFullName}`;
|
|
1139
1341
|
console.log(pc4.blue("\u{1F510} Initializing Keyway vault...\n"));
|
|
1140
1342
|
console.log(` ${pc4.gray("Repository:")} ${pc4.white(repoFullName)}`);
|
|
1141
|
-
const accessToken = await
|
|
1142
|
-
|
|
1143
|
-
|
|
1343
|
+
const accessToken = await ensureLoginAndGitHubApp(repoFullName, {
|
|
1344
|
+
allowPrompt: options.loginPrompt !== false
|
|
1345
|
+
});
|
|
1346
|
+
trackEvent(AnalyticsEvents.CLI_INIT, { repoFullName, githubAppInstalled: true });
|
|
1347
|
+
await initVault(repoFullName, accessToken);
|
|
1144
1348
|
console.log(pc4.green("\u2713 Vault created!"));
|
|
1145
1349
|
try {
|
|
1146
1350
|
const badgeAdded = await addBadgeToReadme(true);
|
|
@@ -1151,8 +1355,8 @@ async function initCommand(options = {}) {
|
|
|
1151
1355
|
}
|
|
1152
1356
|
console.log("");
|
|
1153
1357
|
const envCandidates = discoverEnvCandidates(process.cwd());
|
|
1154
|
-
const
|
|
1155
|
-
if (envCandidates.length > 0 &&
|
|
1358
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1359
|
+
if (envCandidates.length > 0 && isInteractive3) {
|
|
1156
1360
|
console.log(pc4.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1157
1361
|
`));
|
|
1158
1362
|
const { shouldPush } = await prompts4({
|
|
@@ -1190,15 +1394,16 @@ async function initCommand(options = {}) {
|
|
|
1190
1394
|
await shutdownAnalytics();
|
|
1191
1395
|
return;
|
|
1192
1396
|
}
|
|
1193
|
-
if (error.error === "
|
|
1397
|
+
if (error.error === "Plan Limit Reached" || error.upgradeUrl) {
|
|
1398
|
+
const upgradeUrl = error.upgradeUrl || "https://keyway.sh/pricing";
|
|
1194
1399
|
console.log("");
|
|
1195
1400
|
console.log(pc4.dim("\u2500".repeat(50)));
|
|
1196
1401
|
console.log("");
|
|
1197
|
-
console.log(` ${pc4.yellow("\u26A1")} ${pc4.bold("
|
|
1402
|
+
console.log(` ${pc4.yellow("\u26A1")} ${pc4.bold("Plan Limit Reached")}`);
|
|
1198
1403
|
console.log("");
|
|
1199
|
-
console.log(pc4.
|
|
1404
|
+
console.log(pc4.white(` ${error.message}`));
|
|
1200
1405
|
console.log("");
|
|
1201
|
-
console.log(` ${pc4.cyan("\u2192")} ${pc4.underline(
|
|
1406
|
+
console.log(` ${pc4.cyan("Upgrade now \u2192")} ${pc4.underline(upgradeUrl)}`);
|
|
1202
1407
|
console.log("");
|
|
1203
1408
|
console.log(pc4.dim("\u2500".repeat(50)));
|
|
1204
1409
|
console.log("");
|
|
@@ -1240,11 +1445,11 @@ async function pullCommand(options) {
|
|
|
1240
1445
|
const response = await pullSecrets(repoFullName, environment, accessToken);
|
|
1241
1446
|
const envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1242
1447
|
if (fs4.existsSync(envFilePath)) {
|
|
1243
|
-
const
|
|
1448
|
+
const isInteractive3 = process.stdin.isTTY && process.stdout.isTTY;
|
|
1244
1449
|
if (options.yes) {
|
|
1245
1450
|
console.log(pc5.yellow(`
|
|
1246
1451
|
\u26A0 Overwriting existing file: ${envFile}`));
|
|
1247
|
-
} else if (!
|
|
1452
|
+
} else if (!isInteractive3) {
|
|
1248
1453
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
1249
1454
|
} else {
|
|
1250
1455
|
const { confirm } = await prompts5(
|
|
@@ -1295,9 +1500,9 @@ import pc6 from "picocolors";
|
|
|
1295
1500
|
|
|
1296
1501
|
// src/core/doctor.ts
|
|
1297
1502
|
import { execSync as execSync2 } from "child_process";
|
|
1298
|
-
import { writeFileSync, unlinkSync, readFileSync, existsSync } from "fs";
|
|
1503
|
+
import { writeFileSync as writeFileSync2, unlinkSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
1299
1504
|
import { tmpdir } from "os";
|
|
1300
|
-
import { join } from "path";
|
|
1505
|
+
import { join as join2 } from "path";
|
|
1301
1506
|
var API_HEALTH_URL = `${process.env.KEYWAY_API_URL || INTERNAL_API_URL}/v1/health`;
|
|
1302
1507
|
async function checkNode() {
|
|
1303
1508
|
const nodeVersion = process.versions.node;
|
|
@@ -1414,9 +1619,9 @@ async function checkNetwork() {
|
|
|
1414
1619
|
}
|
|
1415
1620
|
}
|
|
1416
1621
|
async function checkFileSystem() {
|
|
1417
|
-
const testFile =
|
|
1622
|
+
const testFile = join2(tmpdir(), `.keyway-test-${Date.now()}.tmp`);
|
|
1418
1623
|
try {
|
|
1419
|
-
|
|
1624
|
+
writeFileSync2(testFile, "test");
|
|
1420
1625
|
unlinkSync(testFile);
|
|
1421
1626
|
return {
|
|
1422
1627
|
id: "filesystem",
|
|
@@ -1435,7 +1640,7 @@ async function checkFileSystem() {
|
|
|
1435
1640
|
}
|
|
1436
1641
|
async function checkGitignore() {
|
|
1437
1642
|
try {
|
|
1438
|
-
if (!
|
|
1643
|
+
if (!existsSync2(".gitignore")) {
|
|
1439
1644
|
return {
|
|
1440
1645
|
id: "gitignore",
|
|
1441
1646
|
name: ".gitignore configuration",
|
|
@@ -1443,7 +1648,7 @@ async function checkGitignore() {
|
|
|
1443
1648
|
detail: "No .gitignore file found"
|
|
1444
1649
|
};
|
|
1445
1650
|
}
|
|
1446
|
-
const gitignoreContent =
|
|
1651
|
+
const gitignoreContent = readFileSync2(".gitignore", "utf-8");
|
|
1447
1652
|
const hasEnvPattern = gitignoreContent.includes("*.env") || gitignoreContent.includes(".env*");
|
|
1448
1653
|
const hasDotEnv = gitignoreContent.includes(".env");
|
|
1449
1654
|
if (hasEnvPattern || hasDotEnv) {
|
|
@@ -1607,7 +1812,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1607
1812
|
|
|
1608
1813
|
// src/cmds/connect.ts
|
|
1609
1814
|
import pc7 from "picocolors";
|
|
1610
|
-
import
|
|
1815
|
+
import open3 from "open";
|
|
1611
1816
|
import prompts6 from "prompts";
|
|
1612
1817
|
async function connectCommand(provider, options = {}) {
|
|
1613
1818
|
try {
|
|
@@ -1646,7 +1851,7 @@ Connecting to ${providerInfo.displayName}...
|
|
|
1646
1851
|
const startTime = /* @__PURE__ */ new Date();
|
|
1647
1852
|
console.log(pc7.gray("Opening browser for authorization..."));
|
|
1648
1853
|
console.log(pc7.gray(`If the browser doesn't open, visit: ${authUrl}`));
|
|
1649
|
-
await
|
|
1854
|
+
await open3(authUrl).catch(() => {
|
|
1650
1855
|
});
|
|
1651
1856
|
console.log(pc7.gray("Waiting for authorization..."));
|
|
1652
1857
|
const maxAttempts = 60;
|
|
@@ -1949,6 +2154,7 @@ async function syncCommand(provider, options = {}) {
|
|
|
1949
2154
|
const direction = options.pull ? "pull" : "push";
|
|
1950
2155
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
1951
2156
|
console.log(pc8.gray(`Project: ${selectedProject.name}`));
|
|
2157
|
+
console.log(pc8.gray(`Environment: ${keywayEnv}${providerEnv !== keywayEnv ? ` \u2192 ${providerEnv}` : ""}`));
|
|
1952
2158
|
console.log(pc8.gray(`Direction: ${direction === "push" ? "Keyway \u2192 " + providerName : providerName + " \u2192 Keyway"}`));
|
|
1953
2159
|
const status = await getSyncStatus(
|
|
1954
2160
|
accessToken,
|
|
@@ -1959,7 +2165,8 @@ async function syncCommand(provider, options = {}) {
|
|
|
1959
2165
|
);
|
|
1960
2166
|
if (status.isFirstSync && !options.pull && status.vaultIsEmpty && status.providerHasSecrets) {
|
|
1961
2167
|
console.log(pc8.yellow(`
|
|
1962
|
-
\u26A0\uFE0F Your Keyway vault is empty, but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2168
|
+
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2169
|
+
console.log(pc8.gray(` (Use --environment to sync a different environment)`));
|
|
1963
2170
|
const { importFirst } = await prompts7({
|
|
1964
2171
|
type: "confirm",
|
|
1965
2172
|
name: "importFirst",
|
|
@@ -2129,7 +2336,7 @@ program.command("connections").description("List your provider connections").opt
|
|
|
2129
2336
|
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) => {
|
|
2130
2337
|
await disconnectCommand(provider, options);
|
|
2131
2338
|
});
|
|
2132
|
-
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
|
|
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) => {
|
|
2133
2340
|
await syncCommand(provider, options);
|
|
2134
2341
|
});
|
|
2135
2342
|
program.parseAsync().catch((error) => {
|