@keywaysh/cli 0.1.12 → 0.1.13
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 +78 -37
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12,13 +12,14 @@ import pc11 from "picocolors";
|
|
|
12
12
|
|
|
13
13
|
// src/cmds/init.ts
|
|
14
14
|
import pc6 from "picocolors";
|
|
15
|
-
import
|
|
15
|
+
import prompts5 from "prompts";
|
|
16
16
|
|
|
17
17
|
// src/utils/git.ts
|
|
18
18
|
import { execSync } from "child_process";
|
|
19
19
|
import fs from "fs";
|
|
20
20
|
import path from "path";
|
|
21
21
|
import pc from "picocolors";
|
|
22
|
+
import prompts from "prompts";
|
|
22
23
|
function getCurrentRepoFullName() {
|
|
23
24
|
try {
|
|
24
25
|
if (!isGitRepository()) {
|
|
@@ -87,9 +88,47 @@ function checkEnvGitignore() {
|
|
|
87
88
|
return true;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
function addEnvToGitignore() {
|
|
92
|
+
try {
|
|
93
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
94
|
+
encoding: "utf-8",
|
|
95
|
+
stdio: "pipe"
|
|
96
|
+
}).trim();
|
|
97
|
+
const gitignorePath = path.join(gitRoot, ".gitignore");
|
|
98
|
+
const envEntry = ".env*";
|
|
99
|
+
if (fs.existsSync(gitignorePath)) {
|
|
100
|
+
const content = fs.readFileSync(gitignorePath, "utf-8");
|
|
101
|
+
const newContent = content.endsWith("\n") ? `${content}${envEntry}
|
|
102
|
+
` : `${content}
|
|
103
|
+
${envEntry}
|
|
104
|
+
`;
|
|
105
|
+
fs.writeFileSync(gitignorePath, newContent);
|
|
106
|
+
} else {
|
|
107
|
+
fs.writeFileSync(gitignorePath, `${envEntry}
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function warnIfEnvNotGitignored() {
|
|
116
|
+
if (checkEnvGitignore()) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(pc.yellow("\u26A0\uFE0F .env files are not in .gitignore - secrets may be committed"));
|
|
120
|
+
const { addToGitignore } = await prompts({
|
|
121
|
+
type: "confirm",
|
|
122
|
+
name: "addToGitignore",
|
|
123
|
+
message: "Add .env* to .gitignore?",
|
|
124
|
+
initial: true
|
|
125
|
+
});
|
|
126
|
+
if (addToGitignore) {
|
|
127
|
+
if (addEnvToGitignore()) {
|
|
128
|
+
console.log(pc.green("\u2713 Added .env* to .gitignore"));
|
|
129
|
+
} else {
|
|
130
|
+
console.log(pc.red("\u2717 Failed to update .gitignore"));
|
|
131
|
+
}
|
|
93
132
|
}
|
|
94
133
|
}
|
|
95
134
|
|
|
@@ -101,7 +140,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
101
140
|
// package.json
|
|
102
141
|
var package_default = {
|
|
103
142
|
name: "@keywaysh/cli",
|
|
104
|
-
version: "0.1.
|
|
143
|
+
version: "0.1.13",
|
|
105
144
|
description: "One link to all your secrets",
|
|
106
145
|
type: "module",
|
|
107
146
|
bin: {
|
|
@@ -719,7 +758,7 @@ var AnalyticsEvents = {
|
|
|
719
758
|
// src/cmds/readme.ts
|
|
720
759
|
import fs3 from "fs";
|
|
721
760
|
import path3 from "path";
|
|
722
|
-
import
|
|
761
|
+
import prompts2 from "prompts";
|
|
723
762
|
import pc2 from "picocolors";
|
|
724
763
|
import balanced from "balanced-match";
|
|
725
764
|
function generateBadge(repo) {
|
|
@@ -814,7 +853,7 @@ async function ensureReadme(repoName, cwd) {
|
|
|
814
853
|
console.log(pc2.yellow('No README found. Run "keyway readme add-badge" from a repo with a README.'));
|
|
815
854
|
return null;
|
|
816
855
|
}
|
|
817
|
-
const { confirm } = await
|
|
856
|
+
const { confirm } = await prompts2(
|
|
818
857
|
{
|
|
819
858
|
type: "confirm",
|
|
820
859
|
name: "confirm",
|
|
@@ -864,12 +903,12 @@ async function addBadgeToReadme(silent = false) {
|
|
|
864
903
|
import pc5 from "picocolors";
|
|
865
904
|
import fs4 from "fs";
|
|
866
905
|
import path4 from "path";
|
|
867
|
-
import
|
|
906
|
+
import prompts4 from "prompts";
|
|
868
907
|
|
|
869
908
|
// src/cmds/login.ts
|
|
870
909
|
import pc4 from "picocolors";
|
|
871
910
|
import readline from "readline";
|
|
872
|
-
import
|
|
911
|
+
import prompts3 from "prompts";
|
|
873
912
|
|
|
874
913
|
// src/utils/helpers.ts
|
|
875
914
|
import pc3 from "picocolors";
|
|
@@ -1010,7 +1049,7 @@ async function runTokenLogin() {
|
|
|
1010
1049
|
await openUrl(url);
|
|
1011
1050
|
console.log(pc4.gray("Select the detected repo (or scope manually)."));
|
|
1012
1051
|
console.log(pc4.gray("Permissions: Metadata \u2192 Read-only; Account permissions: None."));
|
|
1013
|
-
const { token } = await
|
|
1052
|
+
const { token } = await prompts3(
|
|
1014
1053
|
{
|
|
1015
1054
|
type: "password",
|
|
1016
1055
|
name: "token",
|
|
@@ -1128,7 +1167,7 @@ async function pushCommand(options) {
|
|
|
1128
1167
|
}
|
|
1129
1168
|
}
|
|
1130
1169
|
if (!environment && !envFile && isInteractive2 && candidates.length > 0) {
|
|
1131
|
-
const { choice } = await
|
|
1170
|
+
const { choice } = await prompts4(
|
|
1132
1171
|
{
|
|
1133
1172
|
type: "select",
|
|
1134
1173
|
name: "choice",
|
|
@@ -1151,7 +1190,7 @@ async function pushCommand(options) {
|
|
|
1151
1190
|
envFile = choice.file;
|
|
1152
1191
|
environment = choice.env;
|
|
1153
1192
|
} else if (choice === "custom") {
|
|
1154
|
-
const { fileInput } = await
|
|
1193
|
+
const { fileInput } = await prompts4(
|
|
1155
1194
|
{
|
|
1156
1195
|
type: "text",
|
|
1157
1196
|
name: "fileInput",
|
|
@@ -1201,7 +1240,7 @@ async function pushCommand(options) {
|
|
|
1201
1240
|
if (!isInteractive3) {
|
|
1202
1241
|
throw new Error("Confirmation required. Re-run with --yes in non-interactive environments.");
|
|
1203
1242
|
}
|
|
1204
|
-
const { confirm } = await
|
|
1243
|
+
const { confirm } = await prompts4(
|
|
1205
1244
|
{
|
|
1206
1245
|
type: "confirm",
|
|
1207
1246
|
name: "confirm",
|
|
@@ -1316,7 +1355,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1316
1355
|
const deviceStart = await startDeviceLogin(repoFullName);
|
|
1317
1356
|
const installUrl = deviceStart.githubAppInstallUrl || "https://github.com/apps/keyway/installations/new";
|
|
1318
1357
|
console.log("");
|
|
1319
|
-
const { shouldProceed } = await
|
|
1358
|
+
const { shouldProceed } = await prompts5({
|
|
1320
1359
|
type: "confirm",
|
|
1321
1360
|
name: "shouldProceed",
|
|
1322
1361
|
message: "Open browser to sign in?",
|
|
@@ -1376,7 +1415,7 @@ async function ensureLoginAndGitHubApp(repoFullName, options = {}) {
|
|
|
1376
1415
|
console.log(pc6.yellow("\u26A0 GitHub App not installed on this repository"));
|
|
1377
1416
|
console.log(pc6.gray(" The Keyway GitHub App is required for secure access."));
|
|
1378
1417
|
console.log("");
|
|
1379
|
-
const { shouldInstall } = await
|
|
1418
|
+
const { shouldInstall } = await prompts5({
|
|
1380
1419
|
type: "confirm",
|
|
1381
1420
|
name: "shouldInstall",
|
|
1382
1421
|
message: "Open browser to install GitHub App?",
|
|
@@ -1445,7 +1484,7 @@ async function ensureGitHubAppInstalledOnly(repoFullName, accessToken) {
|
|
|
1445
1484
|
console.log(pc6.gray(` Install the Keyway GitHub App: ${status.installUrl}`));
|
|
1446
1485
|
throw new Error("GitHub App installation required.");
|
|
1447
1486
|
}
|
|
1448
|
-
const { shouldInstall } = await
|
|
1487
|
+
const { shouldInstall } = await prompts5({
|
|
1449
1488
|
type: "confirm",
|
|
1450
1489
|
name: "shouldInstall",
|
|
1451
1490
|
message: "Open browser to install Keyway GitHub App?",
|
|
@@ -1519,7 +1558,7 @@ async function initCommand(options = {}) {
|
|
|
1519
1558
|
if (envCandidates.length > 0 && isInteractive2) {
|
|
1520
1559
|
console.log(pc6.gray(` Found ${envCandidates.length} env file(s): ${envCandidates.map((c) => c.file).join(", ")}
|
|
1521
1560
|
`));
|
|
1522
|
-
const { shouldPush } = await
|
|
1561
|
+
const { shouldPush } = await prompts5({
|
|
1523
1562
|
type: "confirm",
|
|
1524
1563
|
name: "shouldPush",
|
|
1525
1564
|
message: "Push secrets now?",
|
|
@@ -1577,7 +1616,7 @@ async function initCommand(options = {}) {
|
|
|
1577
1616
|
import pc7 from "picocolors";
|
|
1578
1617
|
import fs5 from "fs";
|
|
1579
1618
|
import path5 from "path";
|
|
1580
|
-
import
|
|
1619
|
+
import prompts6 from "prompts";
|
|
1581
1620
|
async function pullCommand(options) {
|
|
1582
1621
|
try {
|
|
1583
1622
|
const environment = options.env || "development";
|
|
@@ -1602,7 +1641,7 @@ async function pullCommand(options) {
|
|
|
1602
1641
|
} else if (!isInteractive2) {
|
|
1603
1642
|
throw new Error(`File ${envFile} exists. Re-run with --yes to overwrite or choose a different --file.`);
|
|
1604
1643
|
} else {
|
|
1605
|
-
const { confirm } = await
|
|
1644
|
+
const { confirm } = await prompts6(
|
|
1606
1645
|
{
|
|
1607
1646
|
type: "confirm",
|
|
1608
1647
|
name: "confirm",
|
|
@@ -1962,7 +2001,7 @@ Summary: ${formatSummary(results)}`);
|
|
|
1962
2001
|
|
|
1963
2002
|
// src/cmds/connect.ts
|
|
1964
2003
|
import pc9 from "picocolors";
|
|
1965
|
-
import
|
|
2004
|
+
import prompts7 from "prompts";
|
|
1966
2005
|
var TOKEN_AUTH_PROVIDERS = ["railway"];
|
|
1967
2006
|
function getTokenCreationUrl(provider) {
|
|
1968
2007
|
switch (provider) {
|
|
@@ -1979,7 +2018,7 @@ async function connectWithTokenFlow(accessToken, provider, displayName) {
|
|
|
1979
2018
|
console.log(pc9.yellow(` Do NOT use "No workspace" - it won't have access to your projects.`));
|
|
1980
2019
|
}
|
|
1981
2020
|
await openUrl(tokenUrl);
|
|
1982
|
-
const { token } = await
|
|
2021
|
+
const { token } = await prompts7({
|
|
1983
2022
|
type: "password",
|
|
1984
2023
|
name: "token",
|
|
1985
2024
|
message: `${displayName} API Token:`
|
|
@@ -2056,7 +2095,7 @@ async function connectCommand(provider, options = {}) {
|
|
|
2056
2095
|
const { connections } = await getConnections(accessToken);
|
|
2057
2096
|
const existingConnection = connections.find((c) => c.provider === provider.toLowerCase());
|
|
2058
2097
|
if (existingConnection) {
|
|
2059
|
-
const { reconnect } = await
|
|
2098
|
+
const { reconnect } = await prompts7({
|
|
2060
2099
|
type: "confirm",
|
|
2061
2100
|
name: "reconnect",
|
|
2062
2101
|
message: `You're already connected to ${providerInfo.displayName}. Reconnect?`,
|
|
@@ -2128,7 +2167,7 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2128
2167
|
return;
|
|
2129
2168
|
}
|
|
2130
2169
|
const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2131
|
-
const { confirm } = await
|
|
2170
|
+
const { confirm } = await prompts7({
|
|
2132
2171
|
type: "confirm",
|
|
2133
2172
|
name: "confirm",
|
|
2134
2173
|
message: `Disconnect from ${providerName}?`,
|
|
@@ -2158,7 +2197,7 @@ async function disconnectCommand(provider, options = {}) {
|
|
|
2158
2197
|
|
|
2159
2198
|
// src/cmds/sync.ts
|
|
2160
2199
|
import pc10 from "picocolors";
|
|
2161
|
-
import
|
|
2200
|
+
import prompts8 from "prompts";
|
|
2162
2201
|
function mapToVercelEnvironment(keywayEnv) {
|
|
2163
2202
|
const mapping = {
|
|
2164
2203
|
production: "production",
|
|
@@ -2289,7 +2328,7 @@ async function promptProjectSelection(projects, repoFullName) {
|
|
|
2289
2328
|
}
|
|
2290
2329
|
return { title, value: p.id };
|
|
2291
2330
|
});
|
|
2292
|
-
const { projectChoice } = await
|
|
2331
|
+
const { projectChoice } = await prompts8({
|
|
2293
2332
|
type: "select",
|
|
2294
2333
|
name: "projectChoice",
|
|
2295
2334
|
message: "Select a project:",
|
|
@@ -2320,7 +2359,7 @@ async function syncCommand(provider, options = {}) {
|
|
|
2320
2359
|
if (!vaultExists) {
|
|
2321
2360
|
console.log(pc10.yellow(`
|
|
2322
2361
|
No vault found for ${repoFullName}.`));
|
|
2323
|
-
const { shouldCreate } = await
|
|
2362
|
+
const { shouldCreate } = await prompts8({
|
|
2324
2363
|
type: "confirm",
|
|
2325
2364
|
name: "shouldCreate",
|
|
2326
2365
|
message: "Create vault now?",
|
|
@@ -2348,7 +2387,7 @@ No vault found for ${repoFullName}.`));
|
|
|
2348
2387
|
const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
2349
2388
|
console.log(pc10.yellow(`
|
|
2350
2389
|
Not connected to ${providerDisplayName}.`));
|
|
2351
|
-
const { shouldConnect } = await
|
|
2390
|
+
const { shouldConnect } = await prompts8({
|
|
2352
2391
|
type: "confirm",
|
|
2353
2392
|
name: "shouldConnect",
|
|
2354
2393
|
message: `Connect to ${providerDisplayName} now?`,
|
|
@@ -2407,7 +2446,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2407
2446
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2408
2447
|
const partialDisplayName = getProjectDisplayName(autoMatch.project);
|
|
2409
2448
|
console.log(pc10.yellow(`Detected project: ${partialDisplayName} (partial match)`));
|
|
2410
|
-
const { useDetected } = await
|
|
2449
|
+
const { useDetected } = await prompts8({
|
|
2411
2450
|
type: "confirm",
|
|
2412
2451
|
name: "useDetected",
|
|
2413
2452
|
message: `Use ${partialDisplayName}?`,
|
|
@@ -2431,7 +2470,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2431
2470
|
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2432
2471
|
}
|
|
2433
2472
|
console.log("");
|
|
2434
|
-
const { continueAnyway } = await
|
|
2473
|
+
const { continueAnyway } = await prompts8({
|
|
2435
2474
|
type: "confirm",
|
|
2436
2475
|
name: "continueAnyway",
|
|
2437
2476
|
message: "Continue anyway?",
|
|
@@ -2462,7 +2501,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2462
2501
|
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2463
2502
|
}
|
|
2464
2503
|
console.log("");
|
|
2465
|
-
const { continueAnyway } = await
|
|
2504
|
+
const { continueAnyway } = await prompts8({
|
|
2466
2505
|
type: "confirm",
|
|
2467
2506
|
name: "continueAnyway",
|
|
2468
2507
|
message: "Are you sure you want to sync with this project?",
|
|
@@ -2483,7 +2522,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2483
2522
|
if (needsEnvPrompt || needsDirectionPrompt) {
|
|
2484
2523
|
if (needsEnvPrompt) {
|
|
2485
2524
|
const vaultEnvs = await getVaultEnvironments(accessToken, repoFullName);
|
|
2486
|
-
const { selectedEnv } = await
|
|
2525
|
+
const { selectedEnv } = await prompts8({
|
|
2487
2526
|
type: "select",
|
|
2488
2527
|
name: "selectedEnv",
|
|
2489
2528
|
message: "Keyway environment:",
|
|
@@ -2507,7 +2546,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2507
2546
|
providerEnv = selectedProject.environments[0];
|
|
2508
2547
|
console.log(pc10.gray(`Using ${providerName} environment: ${providerEnv}`));
|
|
2509
2548
|
} else {
|
|
2510
|
-
const { selectedProviderEnv } = await
|
|
2549
|
+
const { selectedProviderEnv } = await prompts8({
|
|
2511
2550
|
type: "select",
|
|
2512
2551
|
name: "selectedProviderEnv",
|
|
2513
2552
|
message: `${providerName} environment:`,
|
|
@@ -2553,7 +2592,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2553
2592
|
} else if (diff.providerCount === 0 && diff.keywayCount > 0) {
|
|
2554
2593
|
defaultDirection = 0;
|
|
2555
2594
|
}
|
|
2556
|
-
const { selectedDirection } = await
|
|
2595
|
+
const { selectedDirection } = await prompts8({
|
|
2557
2596
|
type: "select",
|
|
2558
2597
|
name: "selectedDirection",
|
|
2559
2598
|
message: "Sync direction:",
|
|
@@ -2584,7 +2623,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2584
2623
|
console.log(pc10.yellow(`
|
|
2585
2624
|
\u26A0\uFE0F Your Keyway vault is empty for "${keywayEnv}", but ${providerName} has ${status.providerSecretCount} secrets.`));
|
|
2586
2625
|
console.log(pc10.gray(` (Use --environment to sync a different environment)`));
|
|
2587
|
-
const { importFirst } = await
|
|
2626
|
+
const { importFirst } = await prompts8({
|
|
2588
2627
|
type: "confirm",
|
|
2589
2628
|
name: "importFirst",
|
|
2590
2629
|
message: `Import secrets from ${providerName} first?`,
|
|
@@ -2675,7 +2714,7 @@ async function executeSyncOperation(accessToken, repoFullName, connectionId, pro
|
|
|
2675
2714
|
console.log("");
|
|
2676
2715
|
if (!skipConfirm) {
|
|
2677
2716
|
const target = direction === "push" ? providerName : "Keyway";
|
|
2678
|
-
const { confirm } = await
|
|
2717
|
+
const { confirm } = await prompts8({
|
|
2679
2718
|
type: "confirm",
|
|
2680
2719
|
name: "confirm",
|
|
2681
2720
|
message: `Apply ${totalChanges} changes to ${target}?`,
|
|
@@ -2733,7 +2772,6 @@ ${pc11.gray(TAGLINE)}
|
|
|
2733
2772
|
`);
|
|
2734
2773
|
};
|
|
2735
2774
|
showBanner();
|
|
2736
|
-
warnIfEnvNotGitignored();
|
|
2737
2775
|
program.name("keyway").description(TAGLINE).version(package_default.version);
|
|
2738
2776
|
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) => {
|
|
2739
2777
|
await initCommand(options);
|
|
@@ -2765,7 +2803,10 @@ program.command("disconnect <provider>").description("Disconnect from a provider
|
|
|
2765
2803
|
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) => {
|
|
2766
2804
|
await syncCommand(provider, options);
|
|
2767
2805
|
});
|
|
2768
|
-
|
|
2806
|
+
(async () => {
|
|
2807
|
+
await warnIfEnvNotGitignored();
|
|
2808
|
+
await program.parseAsync();
|
|
2809
|
+
})().catch((error) => {
|
|
2769
2810
|
console.error(pc11.red("Error:"), error.message || error);
|
|
2770
2811
|
process.exit(1);
|
|
2771
2812
|
});
|