@keywaysh/cli 0.1.4 → 0.1.5

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 +205 -43
  2. package/package.json +17 -13
package/dist/cli.js CHANGED
@@ -102,7 +102,7 @@ var INTERNAL_POSTHOG_HOST = "https://eu.i.posthog.com";
102
102
  // package.json
103
103
  var package_default = {
104
104
  name: "@keywaysh/cli",
105
- version: "0.1.4",
105
+ version: "0.1.5",
106
106
  description: "One link to all your secrets",
107
107
  type: "module",
108
108
  bin: {
@@ -146,6 +146,7 @@ var package_default = {
146
146
  node: ">=18.0.0"
147
147
  },
148
148
  dependencies: {
149
+ "balanced-match": "^3.0.1",
149
150
  commander: "^14.0.0",
150
151
  conf: "^15.0.2",
151
152
  open: "^11.0.0",
@@ -154,6 +155,7 @@ var package_default = {
154
155
  prompts: "^2.4.2"
155
156
  },
156
157
  devDependencies: {
158
+ "@types/balanced-match": "^3.0.2",
157
159
  "@types/node": "^24.2.0",
158
160
  "@types/prompts": "^2.4.9",
159
161
  tsup: "^8.5.0",
@@ -491,6 +493,19 @@ async function executeSync(accessToken, repoFullName, options) {
491
493
  const wrapped = await handleResponse(response);
492
494
  return wrapped.data;
493
495
  }
496
+ async function connectWithToken(accessToken, provider, providerToken) {
497
+ const response = await fetchWithTimeout(`${API_BASE_URL}/v1/integrations/${provider}/connect`, {
498
+ method: "POST",
499
+ headers: {
500
+ "Content-Type": "application/json",
501
+ "User-Agent": USER_AGENT,
502
+ Authorization: `Bearer ${accessToken}`
503
+ },
504
+ body: JSON.stringify({ token: providerToken })
505
+ });
506
+ const wrapped = await handleResponse(response);
507
+ return wrapped.data;
508
+ }
494
509
  async function checkVaultExists(accessToken, repoFullName) {
495
510
  const [owner, repo] = repoFullName.split("/");
496
511
  try {
@@ -673,23 +688,76 @@ import fs3 from "fs";
673
688
  import path3 from "path";
674
689
  import prompts from "prompts";
675
690
  import pc2 from "picocolors";
691
+ import balanced from "balanced-match";
676
692
  function generateBadge(repo) {
677
693
  return `[![Keyway Secrets](https://www.keyway.sh/badge.svg?repo=${repo})](https://www.keyway.sh/vaults/${repo})`;
678
694
  }
695
+ var BADGE_PREFIX = /\[!\[[^\]]*\]\([^)]*\)\]\(/g;
696
+ var H1_PATTERN = /^#\s+/;
697
+ var CODE_FENCE = /^```/;
698
+ function findLastBadgeEnd(line) {
699
+ let lastEnd = -1;
700
+ let match;
701
+ BADGE_PREFIX.lastIndex = 0;
702
+ while ((match = BADGE_PREFIX.exec(line)) !== null) {
703
+ const prefixEnd = match.index + match[0].length - 1;
704
+ const remainder = line.substring(prefixEnd);
705
+ const balancedMatch = balanced("(", ")", remainder);
706
+ if (balancedMatch) {
707
+ lastEnd = prefixEnd + balancedMatch.end + 1;
708
+ }
709
+ }
710
+ return lastEnd;
711
+ }
679
712
  function insertBadgeIntoReadme(readmeContent, badge) {
680
713
  if (readmeContent.includes("keyway.sh/badge.svg")) {
681
714
  return readmeContent;
682
715
  }
683
716
  const lines = readmeContent.split(/\r?\n/);
684
- const titleIndex = lines.findIndex((line) => /^#(?!#)\s+/.test(line.trim()));
685
- if (titleIndex !== -1) {
686
- const before = lines.slice(0, titleIndex + 1);
687
- const after = lines.slice(titleIndex + 1);
717
+ let inCodeBlock = false;
718
+ let inHtmlComment = false;
719
+ let lastBadgeLine = -1;
720
+ let lastBadgeEndIndex = -1;
721
+ let firstH1Line = -1;
722
+ for (let i = 0; i < lines.length; i++) {
723
+ const line = lines[i];
724
+ const trimmed = line.trim();
725
+ if (CODE_FENCE.test(trimmed)) {
726
+ inCodeBlock = !inCodeBlock;
727
+ continue;
728
+ }
729
+ if (inCodeBlock) continue;
730
+ if (trimmed.includes("<!--")) inHtmlComment = true;
731
+ if (trimmed.includes("-->")) {
732
+ inHtmlComment = false;
733
+ continue;
734
+ }
735
+ if (inHtmlComment) continue;
736
+ BADGE_PREFIX.lastIndex = 0;
737
+ if (BADGE_PREFIX.test(line)) {
738
+ lastBadgeLine = i;
739
+ lastBadgeEndIndex = findLastBadgeEnd(line);
740
+ }
741
+ if (firstH1Line === -1 && H1_PATTERN.test(line)) {
742
+ firstH1Line = i;
743
+ }
744
+ }
745
+ if (lastBadgeLine >= 0 && lastBadgeEndIndex > 0) {
746
+ const line = lines[lastBadgeLine];
747
+ lines[lastBadgeLine] = line.slice(0, lastBadgeEndIndex) + " " + badge + line.slice(lastBadgeEndIndex);
748
+ return lines.join("\n");
749
+ }
750
+ if (firstH1Line >= 0) {
751
+ const before = lines.slice(0, firstH1Line + 1);
752
+ const after = lines.slice(firstH1Line + 1);
688
753
  while (after.length > 0 && after[0].trim() === "") {
689
754
  after.shift();
690
755
  }
691
- const newLines = [...before, "", badge, "", ...after];
692
- return newLines.join("\n");
756
+ if (after.length > 0) {
757
+ return [...before, "", badge, "", ...after].join("\n");
758
+ } else {
759
+ return [...before, "", badge, ""].join("\n");
760
+ }
693
761
  }
694
762
  return `${badge}
695
763
 
@@ -1812,6 +1880,86 @@ Summary: ${formatSummary(results)}`);
1812
1880
  import pc8 from "picocolors";
1813
1881
  import open3 from "open";
1814
1882
  import prompts6 from "prompts";
1883
+ var TOKEN_AUTH_PROVIDERS = ["railway"];
1884
+ function getTokenCreationUrl(provider) {
1885
+ switch (provider) {
1886
+ case "railway":
1887
+ return "https://railway.com/account/tokens";
1888
+ default:
1889
+ return "";
1890
+ }
1891
+ }
1892
+ async function connectWithTokenFlow(accessToken, provider, displayName) {
1893
+ const tokenUrl = getTokenCreationUrl(provider);
1894
+ console.log(pc8.gray(`Create a ${displayName} API Token at:`));
1895
+ console.log(pc8.cyan(`\u2192 ${tokenUrl}
1896
+ `));
1897
+ if (provider === "railway") {
1898
+ console.log(pc8.yellow("Tip: Select the workspace containing your projects."));
1899
+ console.log(pc8.yellow(` Do NOT use "No workspace" - it won't have access to your projects.
1900
+ `));
1901
+ }
1902
+ const { token } = await prompts6({
1903
+ type: "password",
1904
+ name: "token",
1905
+ message: `${displayName} API Token:`
1906
+ });
1907
+ if (!token) {
1908
+ console.log(pc8.gray("Cancelled."));
1909
+ return false;
1910
+ }
1911
+ console.log(pc8.gray("\nValidating token..."));
1912
+ try {
1913
+ const result = await connectWithToken(accessToken, provider, token);
1914
+ if (result.success) {
1915
+ console.log(pc8.green(`
1916
+ \u2713 Connected to ${displayName}!`));
1917
+ console.log(pc8.gray(` Account: ${result.user.username}`));
1918
+ if (result.user.teamName) {
1919
+ console.log(pc8.gray(` Team: ${result.user.teamName}`));
1920
+ }
1921
+ return true;
1922
+ } else {
1923
+ console.log(pc8.red("\n\u2717 Connection failed."));
1924
+ return false;
1925
+ }
1926
+ } catch (error) {
1927
+ const message = error instanceof Error ? error.message : "Token validation failed";
1928
+ console.log(pc8.red(`
1929
+ \u2717 ${message}`));
1930
+ return false;
1931
+ }
1932
+ }
1933
+ async function connectWithOAuthFlow(accessToken, provider, displayName) {
1934
+ const authUrl = getProviderAuthUrl(provider);
1935
+ const startTime = /* @__PURE__ */ new Date();
1936
+ console.log(pc8.gray("Opening browser for authorization..."));
1937
+ console.log(pc8.gray(`If the browser doesn't open, visit: ${authUrl}`));
1938
+ await open3(authUrl).catch(() => {
1939
+ });
1940
+ console.log(pc8.gray("Waiting for authorization..."));
1941
+ const maxAttempts = 60;
1942
+ let attempts = 0;
1943
+ while (attempts < maxAttempts) {
1944
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
1945
+ attempts++;
1946
+ try {
1947
+ const { connections } = await getConnections(accessToken);
1948
+ const newConn = connections.find(
1949
+ (c) => c.provider === provider && new Date(c.createdAt) > startTime
1950
+ );
1951
+ if (newConn) {
1952
+ console.log(pc8.green(`
1953
+ \u2713 Connected to ${displayName}!`));
1954
+ return true;
1955
+ }
1956
+ } catch {
1957
+ }
1958
+ }
1959
+ console.log(pc8.red("\n\u2717 Authorization timeout."));
1960
+ console.log(pc8.gray("Run `keyway connections` to check if the connection was established."));
1961
+ return false;
1962
+ }
1815
1963
  async function connectCommand(provider, options = {}) {
1816
1964
  try {
1817
1965
  const accessToken = await ensureLogin({ allowPrompt: options.loginPrompt !== false });
@@ -1845,36 +1993,11 @@ async function connectCommand(provider, options = {}) {
1845
1993
  console.log(pc8.blue(`
1846
1994
  Connecting to ${providerInfo.displayName}...
1847
1995
  `));
1848
- const authUrl = getProviderAuthUrl(provider.toLowerCase());
1849
- const startTime = /* @__PURE__ */ new Date();
1850
- console.log(pc8.gray("Opening browser for authorization..."));
1851
- console.log(pc8.gray(`If the browser doesn't open, visit: ${authUrl}`));
1852
- await open3(authUrl).catch(() => {
1853
- });
1854
- console.log(pc8.gray("Waiting for authorization..."));
1855
- const maxAttempts = 60;
1856
- let attempts = 0;
1857
1996
  let connected = false;
1858
- while (attempts < maxAttempts) {
1859
- await new Promise((resolve) => setTimeout(resolve, 5e3));
1860
- attempts++;
1861
- try {
1862
- const { connections: connections2 } = await getConnections(accessToken);
1863
- const newConn = connections2.find(
1864
- (c) => c.provider === provider.toLowerCase() && new Date(c.createdAt) > startTime
1865
- );
1866
- if (newConn) {
1867
- connected = true;
1868
- console.log(pc8.green(`
1869
- \u2713 Connected to ${providerInfo.displayName}!`));
1870
- break;
1871
- }
1872
- } catch {
1873
- }
1874
- }
1875
- if (!connected) {
1876
- console.log(pc8.red("\n\u2717 Authorization timeout."));
1877
- console.log(pc8.gray("Run `keyway connections` to check if the connection was established."));
1997
+ if (TOKEN_AUTH_PROVIDERS.includes(provider.toLowerCase())) {
1998
+ connected = await connectWithTokenFlow(accessToken, provider.toLowerCase(), providerInfo.displayName);
1999
+ } else {
2000
+ connected = await connectWithOAuthFlow(accessToken, provider.toLowerCase(), providerInfo.displayName);
1878
2001
  }
1879
2002
  trackEvent(AnalyticsEvents.CLI_CONNECT, {
1880
2003
  provider: provider.toLowerCase(),
@@ -1898,7 +2021,7 @@ async function connectionsCommand(options = {}) {
1898
2021
  if (connections.length === 0) {
1899
2022
  console.log(pc8.gray("No provider connections found."));
1900
2023
  console.log(pc8.gray("\nConnect to a provider with: keyway connect <provider>"));
1901
- console.log(pc8.gray("Available providers: vercel"));
2024
+ console.log(pc8.gray("Available providers: vercel, railway"));
1902
2025
  return;
1903
2026
  }
1904
2027
  console.log(pc8.blue("\n\u{1F4E1} Provider Connections\n"));
@@ -1968,6 +2091,25 @@ function mapToVercelEnvironment(keywayEnv) {
1968
2091
  };
1969
2092
  return mapping[keywayEnv.toLowerCase()] || "production";
1970
2093
  }
2094
+ function mapToRailwayEnvironment(keywayEnv) {
2095
+ const mapping = {
2096
+ production: "production",
2097
+ staging: "staging",
2098
+ dev: "development",
2099
+ development: "development"
2100
+ };
2101
+ return mapping[keywayEnv.toLowerCase()] || "production";
2102
+ }
2103
+ function mapToProviderEnvironment(provider, keywayEnv) {
2104
+ switch (provider.toLowerCase()) {
2105
+ case "vercel":
2106
+ return mapToVercelEnvironment(keywayEnv);
2107
+ case "railway":
2108
+ return mapToRailwayEnvironment(keywayEnv);
2109
+ default:
2110
+ return keywayEnv;
2111
+ }
2112
+ }
1971
2113
  function findMatchingProject(projects, repoFullName) {
1972
2114
  const repoFullNameLower = repoFullName.toLowerCase();
1973
2115
  const repoName = repoFullName.split("/")[1]?.toLowerCase();
@@ -2045,12 +2187,32 @@ async function syncCommand(provider, options = {}) {
2045
2187
  process.exit(1);
2046
2188
  }
2047
2189
  console.log(pc9.gray(`Repository: ${repoFullName}`));
2048
- const { connections } = await getConnections(accessToken);
2049
- const connection = connections.find((c) => c.provider === provider.toLowerCase());
2190
+ let { connections } = await getConnections(accessToken);
2191
+ let connection = connections.find((c) => c.provider === provider.toLowerCase());
2050
2192
  if (!connection) {
2051
- console.error(pc9.red(`Not connected to ${provider}.`));
2052
- console.log(pc9.gray(`Run: keyway connect ${provider}`));
2053
- process.exit(1);
2193
+ const providerDisplayName = provider.charAt(0).toUpperCase() + provider.slice(1);
2194
+ console.log(pc9.yellow(`
2195
+ Not connected to ${providerDisplayName}.`));
2196
+ const { shouldConnect } = await prompts7({
2197
+ type: "confirm",
2198
+ name: "shouldConnect",
2199
+ message: `Connect to ${providerDisplayName} now?`,
2200
+ initial: true
2201
+ });
2202
+ if (!shouldConnect) {
2203
+ console.log(pc9.gray("Cancelled."));
2204
+ process.exit(0);
2205
+ }
2206
+ await connectCommand(provider, { loginPrompt: false });
2207
+ const refreshed = await getConnections(accessToken);
2208
+ connections = refreshed.connections;
2209
+ connection = connections.find((c) => c.provider === provider.toLowerCase());
2210
+ if (!connection) {
2211
+ console.error(pc9.red(`
2212
+ Connection to ${providerDisplayName} failed.`));
2213
+ process.exit(1);
2214
+ }
2215
+ console.log("");
2054
2216
  }
2055
2217
  const { projects } = await getConnectionProjects(accessToken, connection.id);
2056
2218
  if (projects.length === 0) {
@@ -2178,7 +2340,7 @@ async function syncCommand(provider, options = {}) {
2178
2340
  }
2179
2341
  keywayEnv = selectedEnv;
2180
2342
  if (!options.providerEnv) {
2181
- providerEnv = mapToVercelEnvironment(keywayEnv);
2343
+ providerEnv = mapToProviderEnvironment(provider, keywayEnv);
2182
2344
  }
2183
2345
  }
2184
2346
  if (needsDirectionPrompt) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keywaysh/cli",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "One link to all your secrets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,18 @@
10
10
  "files": [
11
11
  "dist"
12
12
  ],
13
+ "scripts": {
14
+ "dev": "pnpm exec tsx src/cli.ts",
15
+ "dev:local": "NODE_TLS_REJECT_UNAUTHORIZED=0 KEYWAY_API_URL=https://localhost/api pnpm exec tsx src/cli.ts",
16
+ "build": "pnpm exec tsup",
17
+ "build:watch": "pnpm exec tsup --watch",
18
+ "prepublishOnly": "pnpm run build",
19
+ "test": "pnpm exec vitest run",
20
+ "test:watch": "pnpm exec vitest",
21
+ "release": "npm version patch && git push && git push --tags",
22
+ "release:minor": "npm version minor && git push && git push --tags",
23
+ "release:major": "npm version major && git push && git push --tags"
24
+ },
13
25
  "keywords": [
14
26
  "secrets",
15
27
  "env",
@@ -27,10 +39,12 @@
27
39
  "bugs": {
28
40
  "url": "https://github.com/keywaysh/cli/issues"
29
41
  },
42
+ "packageManager": "pnpm@10.6.1",
30
43
  "engines": {
31
44
  "node": ">=18.0.0"
32
45
  },
33
46
  "dependencies": {
47
+ "balanced-match": "^3.0.1",
34
48
  "commander": "^14.0.0",
35
49
  "conf": "^15.0.2",
36
50
  "open": "^11.0.0",
@@ -39,22 +53,12 @@
39
53
  "prompts": "^2.4.2"
40
54
  },
41
55
  "devDependencies": {
56
+ "@types/balanced-match": "^3.0.2",
42
57
  "@types/node": "^24.2.0",
43
58
  "@types/prompts": "^2.4.9",
44
59
  "tsup": "^8.5.0",
45
60
  "tsx": "^4.20.3",
46
61
  "typescript": "^5.9.2",
47
62
  "vitest": "^3.2.4"
48
- },
49
- "scripts": {
50
- "dev": "pnpm exec tsx src/cli.ts",
51
- "dev:local": "NODE_TLS_REJECT_UNAUTHORIZED=0 KEYWAY_API_URL=https://localhost/api pnpm exec tsx src/cli.ts",
52
- "build": "pnpm exec tsup",
53
- "build:watch": "pnpm exec tsup --watch",
54
- "test": "pnpm exec vitest run",
55
- "test:watch": "pnpm exec vitest",
56
- "release": "npm version patch && git push && git push --tags",
57
- "release:minor": "npm version minor && git push && git push --tags",
58
- "release:major": "npm version major && git push && git push --tags"
59
63
  }
60
- }
64
+ }