@keywaysh/cli 0.1.6 → 0.1.8
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 +129 -41
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -101,7 +101,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
|
101
101
|
// package.json
|
|
102
102
|
var package_default = {
|
|
103
103
|
name: "@keywaysh/cli",
|
|
104
|
-
version: "0.1.
|
|
104
|
+
version: "0.1.8",
|
|
105
105
|
description: "One link to all your secrets",
|
|
106
106
|
type: "module",
|
|
107
107
|
bin: {
|
|
@@ -444,6 +444,29 @@ async function getSyncStatus(accessToken, repoFullName, connectionId, projectId,
|
|
|
444
444
|
const wrapped = await handleResponse(response);
|
|
445
445
|
return wrapped.data;
|
|
446
446
|
}
|
|
447
|
+
async function getSyncDiff(accessToken, repoFullName, options) {
|
|
448
|
+
const [owner, repo] = repoFullName.split("/");
|
|
449
|
+
const params = new URLSearchParams({
|
|
450
|
+
connectionId: options.connectionId,
|
|
451
|
+
projectId: options.projectId,
|
|
452
|
+
keywayEnvironment: options.keywayEnvironment || "production",
|
|
453
|
+
providerEnvironment: options.providerEnvironment || "production"
|
|
454
|
+
});
|
|
455
|
+
const response = await fetchWithTimeout(
|
|
456
|
+
`${API_BASE_URL}/v1/integrations/vaults/${owner}/${repo}/sync/diff?${params}`,
|
|
457
|
+
{
|
|
458
|
+
method: "GET",
|
|
459
|
+
headers: {
|
|
460
|
+
"User-Agent": USER_AGENT,
|
|
461
|
+
Authorization: `Bearer ${accessToken}`
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
6e4
|
|
465
|
+
// 60 seconds
|
|
466
|
+
);
|
|
467
|
+
const wrapped = await handleResponse(response);
|
|
468
|
+
return wrapped.data;
|
|
469
|
+
}
|
|
447
470
|
async function getSyncPreview(accessToken, repoFullName, options) {
|
|
448
471
|
const [owner, repo] = repoFullName.split("/");
|
|
449
472
|
const params = new URLSearchParams({
|
|
@@ -1087,6 +1110,9 @@ async function pushCommand(options) {
|
|
|
1087
1110
|
let environment = options.env;
|
|
1088
1111
|
let envFile = options.file;
|
|
1089
1112
|
const candidates = discoverEnvCandidates(process.cwd());
|
|
1113
|
+
if (candidates.length === 0 && !envFile) {
|
|
1114
|
+
throw new Error("No .env file found. Create a .env file first, or use --file <path> to specify one.");
|
|
1115
|
+
}
|
|
1090
1116
|
if (environment && !envFile) {
|
|
1091
1117
|
const match = candidates.find((c) => c.env === environment);
|
|
1092
1118
|
if (match) {
|
|
@@ -1145,34 +1171,9 @@ async function pushCommand(options) {
|
|
|
1145
1171
|
if (!envFile) {
|
|
1146
1172
|
envFile = ".env";
|
|
1147
1173
|
}
|
|
1148
|
-
|
|
1174
|
+
const envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1149
1175
|
if (!fs4.existsSync(envFilePath)) {
|
|
1150
|
-
|
|
1151
|
-
throw new Error(`File not found: ${envFile}. Provide --file <path> or run interactively to choose a file.`);
|
|
1152
|
-
}
|
|
1153
|
-
const { newPath } = await prompts3(
|
|
1154
|
-
{
|
|
1155
|
-
type: "text",
|
|
1156
|
-
name: "newPath",
|
|
1157
|
-
message: `File not found: ${envFile}. Enter an env file path to use:`,
|
|
1158
|
-
validate: (value) => {
|
|
1159
|
-
if (!value || typeof value !== "string") return "Path is required";
|
|
1160
|
-
const resolved = path4.resolve(process.cwd(), value);
|
|
1161
|
-
if (!fs4.existsSync(resolved)) return `File not found: ${value}`;
|
|
1162
|
-
return true;
|
|
1163
|
-
}
|
|
1164
|
-
},
|
|
1165
|
-
{
|
|
1166
|
-
onCancel: () => {
|
|
1167
|
-
throw new Error("Push cancelled (no env file provided).");
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
);
|
|
1171
|
-
if (!newPath || typeof newPath !== "string") {
|
|
1172
|
-
throw new Error("Push cancelled (no env file provided).");
|
|
1173
|
-
}
|
|
1174
|
-
envFile = newPath.trim();
|
|
1175
|
-
envFilePath = path4.resolve(process.cwd(), envFile);
|
|
1176
|
+
throw new Error(`File not found: ${envFile}`);
|
|
1176
1177
|
}
|
|
1177
1178
|
const content = fs4.readFileSync(envFilePath, "utf-8");
|
|
1178
1179
|
if (content.trim().length === 0) {
|
|
@@ -1231,7 +1232,9 @@ async function pushCommand(options) {
|
|
|
1231
1232
|
}
|
|
1232
1233
|
console.log(`
|
|
1233
1234
|
Your secrets are now encrypted and stored securely.`);
|
|
1234
|
-
|
|
1235
|
+
const dashboardLink = `https://www.keyway.sh/dashboard/vaults/${repoFullName}`;
|
|
1236
|
+
console.log(`
|
|
1237
|
+
${pc5.blue("\u2394")} Dashboard: ${pc5.underline(dashboardLink)}`);
|
|
1235
1238
|
await shutdownAnalytics();
|
|
1236
1239
|
} catch (error) {
|
|
1237
1240
|
let message;
|
|
@@ -2136,6 +2139,45 @@ function mapToProviderEnvironment(provider, keywayEnv) {
|
|
|
2136
2139
|
return keywayEnv;
|
|
2137
2140
|
}
|
|
2138
2141
|
}
|
|
2142
|
+
function displayDiffSummary(diff, providerName) {
|
|
2143
|
+
const totalDiff = diff.onlyInKeyway.length + diff.onlyInProvider.length + diff.different.length;
|
|
2144
|
+
if (totalDiff === 0 && diff.same.length > 0) {
|
|
2145
|
+
console.log(pc10.green(`
|
|
2146
|
+
\u2713 Already in sync (${diff.same.length} secrets)`));
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
console.log(pc10.blue("\n\u{1F4CA} Comparison Summary\n"));
|
|
2150
|
+
console.log(pc10.gray(` Keyway: ${diff.keywayCount} secrets | ${providerName}: ${diff.providerCount} secrets
|
|
2151
|
+
`));
|
|
2152
|
+
if (diff.onlyInKeyway.length > 0) {
|
|
2153
|
+
console.log(pc10.cyan(` \u2192 ${diff.onlyInKeyway.length} only in Keyway`));
|
|
2154
|
+
diff.onlyInKeyway.slice(0, 3).forEach((key) => console.log(pc10.gray(` ${key}`)));
|
|
2155
|
+
if (diff.onlyInKeyway.length > 3) {
|
|
2156
|
+
console.log(pc10.gray(` ... and ${diff.onlyInKeyway.length - 3} more`));
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
if (diff.onlyInProvider.length > 0) {
|
|
2160
|
+
console.log(pc10.magenta(` \u2190 ${diff.onlyInProvider.length} only in ${providerName}`));
|
|
2161
|
+
diff.onlyInProvider.slice(0, 3).forEach((key) => console.log(pc10.gray(` ${key}`)));
|
|
2162
|
+
if (diff.onlyInProvider.length > 3) {
|
|
2163
|
+
console.log(pc10.gray(` ... and ${diff.onlyInProvider.length - 3} more`));
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
if (diff.different.length > 0) {
|
|
2167
|
+
console.log(pc10.yellow(` \u2260 ${diff.different.length} with different values`));
|
|
2168
|
+
diff.different.slice(0, 3).forEach((key) => console.log(pc10.gray(` ${key}`)));
|
|
2169
|
+
if (diff.different.length > 3) {
|
|
2170
|
+
console.log(pc10.gray(` ... and ${diff.different.length - 3} more`));
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
if (diff.same.length > 0) {
|
|
2174
|
+
console.log(pc10.gray(` = ${diff.same.length} identical`));
|
|
2175
|
+
}
|
|
2176
|
+
console.log("");
|
|
2177
|
+
}
|
|
2178
|
+
function getProjectDisplayName(project) {
|
|
2179
|
+
return project.serviceName || project.name;
|
|
2180
|
+
}
|
|
2139
2181
|
function findMatchingProject(projects, repoFullName) {
|
|
2140
2182
|
const repoFullNameLower = repoFullName.toLowerCase();
|
|
2141
2183
|
const repoName = repoFullName.split("/")[1]?.toLowerCase();
|
|
@@ -2172,17 +2214,18 @@ function projectMatchesRepo(project, repoFullName) {
|
|
|
2172
2214
|
async function promptProjectSelection(projects, repoFullName) {
|
|
2173
2215
|
const repoName = repoFullName.split("/")[1]?.toLowerCase() || "";
|
|
2174
2216
|
const choices = projects.map((p) => {
|
|
2175
|
-
|
|
2217
|
+
const displayName = getProjectDisplayName(p);
|
|
2218
|
+
let title = displayName;
|
|
2176
2219
|
const badges = [];
|
|
2177
2220
|
if (p.linkedRepo?.toLowerCase() === repoFullName.toLowerCase()) {
|
|
2178
2221
|
badges.push(pc10.green("\u2190 linked"));
|
|
2179
|
-
} else if (p.name.toLowerCase() === repoName) {
|
|
2222
|
+
} else if (p.name.toLowerCase() === repoName || p.serviceName?.toLowerCase() === repoName) {
|
|
2180
2223
|
badges.push(pc10.green("\u2190 same name"));
|
|
2181
2224
|
} else if (p.linkedRepo) {
|
|
2182
2225
|
badges.push(pc10.gray(`\u2192 ${p.linkedRepo}`));
|
|
2183
2226
|
}
|
|
2184
2227
|
if (badges.length > 0) {
|
|
2185
|
-
title = `${
|
|
2228
|
+
title = `${displayName} ${badges.join(" ")}`;
|
|
2186
2229
|
}
|
|
2187
2230
|
return { title, value: p.id };
|
|
2188
2231
|
});
|
|
@@ -2248,12 +2291,12 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2248
2291
|
let selectedProject;
|
|
2249
2292
|
if (options.project) {
|
|
2250
2293
|
const found = projects.find(
|
|
2251
|
-
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase()
|
|
2294
|
+
(p) => p.id === options.project || p.name.toLowerCase() === options.project?.toLowerCase() || p.serviceName?.toLowerCase() === options.project?.toLowerCase()
|
|
2252
2295
|
);
|
|
2253
2296
|
if (!found) {
|
|
2254
2297
|
console.error(pc10.red(`Project not found: ${options.project}`));
|
|
2255
2298
|
console.log(pc10.gray("Available projects:"));
|
|
2256
|
-
projects.forEach((p) => console.log(pc10.gray(` - ${p
|
|
2299
|
+
projects.forEach((p) => console.log(pc10.gray(` - ${getProjectDisplayName(p)}`)));
|
|
2257
2300
|
process.exit(1);
|
|
2258
2301
|
}
|
|
2259
2302
|
selectedProject = found;
|
|
@@ -2263,7 +2306,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2263
2306
|
console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2264
2307
|
console.log(pc10.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
2265
2308
|
console.log(pc10.yellow(` Current repo: ${repoFullName}`));
|
|
2266
|
-
console.log(pc10.yellow(` Selected project: ${selectedProject
|
|
2309
|
+
console.log(pc10.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
|
|
2267
2310
|
if (selectedProject.linkedRepo) {
|
|
2268
2311
|
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2269
2312
|
}
|
|
@@ -2274,13 +2317,14 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2274
2317
|
if (autoMatch && (autoMatch.matchType === "linked_repo" || autoMatch.matchType === "exact_name")) {
|
|
2275
2318
|
selectedProject = autoMatch.project;
|
|
2276
2319
|
const matchReason = autoMatch.matchType === "linked_repo" ? `linked to ${repoFullName}` : "exact name match";
|
|
2277
|
-
console.log(pc10.green(`\u2713 Auto-selected project: ${selectedProject
|
|
2320
|
+
console.log(pc10.green(`\u2713 Auto-selected project: ${getProjectDisplayName(selectedProject)} (${matchReason})`));
|
|
2278
2321
|
} else if (autoMatch && autoMatch.matchType === "partial_name") {
|
|
2279
|
-
|
|
2322
|
+
const partialDisplayName = getProjectDisplayName(autoMatch.project);
|
|
2323
|
+
console.log(pc10.yellow(`Detected project: ${partialDisplayName} (partial match)`));
|
|
2280
2324
|
const { useDetected } = await prompts7({
|
|
2281
2325
|
type: "confirm",
|
|
2282
2326
|
name: "useDetected",
|
|
2283
|
-
message: `Use ${
|
|
2327
|
+
message: `Use ${partialDisplayName}?`,
|
|
2284
2328
|
initial: true
|
|
2285
2329
|
});
|
|
2286
2330
|
if (useDetected) {
|
|
@@ -2296,7 +2340,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2296
2340
|
console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: Project does not match current repository \u2502"));
|
|
2297
2341
|
console.log(pc10.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
2298
2342
|
console.log(pc10.yellow(` Current repo: ${repoFullName}`));
|
|
2299
|
-
console.log(pc10.yellow(` Only project: ${selectedProject
|
|
2343
|
+
console.log(pc10.yellow(` Only project: ${getProjectDisplayName(selectedProject)}`));
|
|
2300
2344
|
if (selectedProject.linkedRepo) {
|
|
2301
2345
|
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2302
2346
|
}
|
|
@@ -2327,7 +2371,7 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2327
2371
|
console.log(pc10.yellow("\u2502 \u26A0\uFE0F WARNING: You selected a different project \u2502"));
|
|
2328
2372
|
console.log(pc10.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
2329
2373
|
console.log(pc10.yellow(` Current repo: ${repoFullName}`));
|
|
2330
|
-
console.log(pc10.yellow(` Selected project: ${selectedProject
|
|
2374
|
+
console.log(pc10.yellow(` Selected project: ${getProjectDisplayName(selectedProject)}`));
|
|
2331
2375
|
if (selectedProject.linkedRepo) {
|
|
2332
2376
|
console.log(pc10.yellow(` Project linked to: ${selectedProject.linkedRepo}`));
|
|
2333
2377
|
}
|
|
@@ -2366,7 +2410,51 @@ Connection to ${providerDisplayName} failed.`));
|
|
|
2366
2410
|
}
|
|
2367
2411
|
keywayEnv = selectedEnv;
|
|
2368
2412
|
if (!options.providerEnv) {
|
|
2369
|
-
|
|
2413
|
+
if (selectedProject.environments && selectedProject.environments.length > 0) {
|
|
2414
|
+
const mappedEnv = mapToProviderEnvironment(provider, keywayEnv);
|
|
2415
|
+
const envExists = selectedProject.environments.some(
|
|
2416
|
+
(e) => e.toLowerCase() === mappedEnv.toLowerCase()
|
|
2417
|
+
);
|
|
2418
|
+
if (envExists) {
|
|
2419
|
+
providerEnv = mappedEnv;
|
|
2420
|
+
} else if (selectedProject.environments.length === 1) {
|
|
2421
|
+
providerEnv = selectedProject.environments[0];
|
|
2422
|
+
console.log(pc10.gray(`Using ${providerName} environment: ${providerEnv}`));
|
|
2423
|
+
} else {
|
|
2424
|
+
const { selectedProviderEnv } = await prompts7({
|
|
2425
|
+
type: "select",
|
|
2426
|
+
name: "selectedProviderEnv",
|
|
2427
|
+
message: `${providerName} environment:`,
|
|
2428
|
+
choices: selectedProject.environments.map((e) => ({ title: e, value: e })),
|
|
2429
|
+
initial: Math.max(0, selectedProject.environments.findIndex(
|
|
2430
|
+
(e) => e.toLowerCase() === "production"
|
|
2431
|
+
))
|
|
2432
|
+
});
|
|
2433
|
+
if (!selectedProviderEnv) {
|
|
2434
|
+
console.log(pc10.gray("Cancelled."));
|
|
2435
|
+
process.exit(0);
|
|
2436
|
+
}
|
|
2437
|
+
providerEnv = selectedProviderEnv;
|
|
2438
|
+
}
|
|
2439
|
+
} else {
|
|
2440
|
+
providerEnv = mapToProviderEnvironment(provider, keywayEnv);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
if (needsDirectionPrompt) {
|
|
2445
|
+
const effectiveKeywayEnv = keywayEnv || "production";
|
|
2446
|
+
const effectiveProviderEnv = providerEnv || mapToProviderEnvironment(provider, effectiveKeywayEnv);
|
|
2447
|
+
console.log(pc10.gray("\nComparing secrets..."));
|
|
2448
|
+
const diff = await getSyncDiff(accessToken, repoFullName, {
|
|
2449
|
+
connectionId: connection.id,
|
|
2450
|
+
projectId: selectedProject.id,
|
|
2451
|
+
keywayEnvironment: effectiveKeywayEnv,
|
|
2452
|
+
providerEnvironment: effectiveProviderEnv
|
|
2453
|
+
});
|
|
2454
|
+
displayDiffSummary(diff, providerName);
|
|
2455
|
+
const totalDiff = diff.onlyInKeyway.length + diff.onlyInProvider.length + diff.different.length;
|
|
2456
|
+
if (totalDiff === 0) {
|
|
2457
|
+
return;
|
|
2370
2458
|
}
|
|
2371
2459
|
}
|
|
2372
2460
|
if (needsDirectionPrompt) {
|