@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.
Files changed (2) hide show
  1. package/dist/cli.js +129 -41
  2. 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.6",
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
- let envFilePath = path4.resolve(process.cwd(), envFile);
1174
+ const envFilePath = path4.resolve(process.cwd(), envFile);
1149
1175
  if (!fs4.existsSync(envFilePath)) {
1150
- if (!isInteractive2) {
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
- console.log(`To retrieve them, run: ${pc5.cyan(`keyway pull --env ${environment}`)}`);
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
- let title = p.name;
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 = `${p.name} ${badges.join(" ")}`;
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.name}`)));
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.name}`));
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.name} (${matchReason})`));
2320
+ console.log(pc10.green(`\u2713 Auto-selected project: ${getProjectDisplayName(selectedProject)} (${matchReason})`));
2278
2321
  } else if (autoMatch && autoMatch.matchType === "partial_name") {
2279
- console.log(pc10.yellow(`Detected project: ${autoMatch.project.name} (partial match)`));
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 ${autoMatch.project.name}?`,
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.name}`));
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.name}`));
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
- providerEnv = mapToProviderEnvironment(provider, keywayEnv);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keywaysh/cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "One link to all your secrets",
5
5
  "type": "module",
6
6
  "bin": {