@keywaysh/cli 0.1.16 → 0.3.1
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/README.md +16 -291
- package/bin/keyway +25 -0
- package/package.json +19 -56
- package/scripts/install.js +216 -0
- package/scripts/postinstall.js +157 -0
- package/dist/auth-QLPQ24HZ.js +0 -12
- package/dist/chunk-F4C46224.js +0 -102
- package/dist/cli.js +0 -3100
package/README.md
CHANGED
|
@@ -1,322 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
<h1>Keyway CLI</h1>
|
|
3
|
-
<strong>The simplest way to sync your project's environment variables.</strong><br/>
|
|
4
|
-
Stop sending <code>.env</code> files on Slack. One command and you're in sync.
|
|
5
|
-
<br/><br/>
|
|
6
|
-
<a href="https://keyway.sh">keyway.sh</a> ·
|
|
7
|
-
<a href="https://github.com/keywaysh/cli">GitHub</a> ·
|
|
8
|
-
<a href="https://www.npmjs.com/package/@keywaysh/cli">NPM</a> ·
|
|
9
|
-
<a href="https://status.keyway.sh">Status</a>
|
|
10
|
-
<br/><br/>
|
|
1
|
+
# @keywaysh/cli
|
|
11
2
|
|
|
12
|
-
|
|
13
|
-
[](https://www.npmjs.com/package/@keywaysh/cli)
|
|
3
|
+
GitHub-native secrets management. If you have repo access, you get secret access.
|
|
14
4
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Why Keyway?
|
|
18
|
-
|
|
19
|
-
Most devs store secrets in... chaotic places:
|
|
20
|
-
|
|
21
|
-
- Slack
|
|
22
|
-
- Notion
|
|
23
|
-
- Discord
|
|
24
|
-
- Google Docs
|
|
25
|
-
- Lost `.env` files
|
|
26
|
-
- Messages you can't find anymore
|
|
27
|
-
- Machine of the dev who left the project
|
|
28
|
-
|
|
29
|
-
**Keyway fixes that.**
|
|
30
|
-
|
|
31
|
-
If you have GitHub access to a repo → you have access to its secrets.
|
|
32
|
-
No invites. No dashboards. No complex config.
|
|
33
|
-
Just one command that works.
|
|
34
|
-
|
|
35
|
-
## Install
|
|
5
|
+
## Installation
|
|
36
6
|
|
|
37
7
|
```bash
|
|
38
8
|
npm install -g @keywaysh/cli
|
|
39
9
|
```
|
|
40
10
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
Inside any project connected to GitHub:
|
|
11
|
+
Or with npx:
|
|
44
12
|
|
|
45
13
|
```bash
|
|
46
|
-
|
|
47
|
-
keyway init
|
|
14
|
+
npx @keywaysh/cli pull
|
|
48
15
|
```
|
|
49
16
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
1. Keyway authenticates via GitHub OAuth
|
|
53
|
-
2. Detects your GitHub repo
|
|
54
|
-
3. Creates a vault for this repo
|
|
55
|
-
4. Asks if you want to sync your `.env`
|
|
56
|
-
|
|
57
|
-
Then any teammate can simply run:
|
|
58
|
-
|
|
59
|
-
```bash
|
|
60
|
-
keyway pull
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
And boom: your `.env` is recreated locally.
|
|
64
|
-
|
|
65
|
-
## Commands
|
|
66
|
-
|
|
67
|
-
### `keyway login`
|
|
68
|
-
|
|
69
|
-
Authenticate with GitHub through the Keyway OAuth/device flow.
|
|
17
|
+
## Quick Start
|
|
70
18
|
|
|
71
19
|
```bash
|
|
20
|
+
# Sign in with GitHub
|
|
72
21
|
keyway login
|
|
73
|
-
```
|
|
74
22
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
keyway login --token
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### `keyway init`
|
|
82
|
-
|
|
83
|
-
Initialize a vault for the current repository.
|
|
84
|
-
|
|
85
|
-
```bash
|
|
23
|
+
# Initialize vault for current repo
|
|
86
24
|
keyway init
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Creates a vault, pushes your `.env`, and sets everything up.
|
|
90
25
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Push your local `.env` to the vault.
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
# Push to development (default)
|
|
26
|
+
# Push secrets from .env
|
|
97
27
|
keyway push
|
|
98
28
|
|
|
99
|
-
#
|
|
100
|
-
keyway push --env production
|
|
101
|
-
|
|
102
|
-
# Push a different file
|
|
103
|
-
keyway push --file .env.staging --env staging
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Useful when:
|
|
107
|
-
- you added a new variable
|
|
108
|
-
- you rotated a key
|
|
109
|
-
- you fixed a staging/production mismatch
|
|
110
|
-
|
|
111
|
-
### `keyway pull`
|
|
112
|
-
|
|
113
|
-
Pull secrets from the vault and write them to `.env`.
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
# Pull development environment (default)
|
|
117
|
-
keyway pull
|
|
118
|
-
|
|
119
|
-
# Pull from a specific environment
|
|
120
|
-
keyway pull --env production
|
|
121
|
-
|
|
122
|
-
# Pull to a different file
|
|
123
|
-
keyway pull --file .env.local
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
Perfect for:
|
|
127
|
-
- onboarding a new dev
|
|
128
|
-
- syncing your local environment
|
|
129
|
-
- switching between machines
|
|
130
|
-
|
|
131
|
-
### `keyway doctor`
|
|
132
|
-
|
|
133
|
-
Diagnostic command to check your setup.
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
keyway doctor
|
|
137
|
-
|
|
138
|
-
# Output as JSON (for CI/CD)
|
|
139
|
-
keyway doctor --json
|
|
140
|
-
|
|
141
|
-
# Strict mode (treat warnings as failures)
|
|
142
|
-
keyway doctor --strict
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
**Checks performed:**
|
|
146
|
-
- Node.js version (≥18.0.0 required)
|
|
147
|
-
- Git installation and repository status
|
|
148
|
-
- API connectivity
|
|
149
|
-
- File system write permissions
|
|
150
|
-
- `.gitignore` configuration
|
|
151
|
-
- System clock synchronization
|
|
152
|
-
|
|
153
|
-
### `keyway logout`
|
|
154
|
-
|
|
155
|
-
Clear stored Keyway credentials.
|
|
156
|
-
|
|
157
|
-
```bash
|
|
158
|
-
keyway logout
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### `keyway sync`
|
|
162
|
-
|
|
163
|
-
**One command to deploy your secrets everywhere.**
|
|
164
|
-
|
|
165
|
-
Sync your vault to Vercel, Netlify, and more. No more copy-pasting environment variables between dashboards.
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
keyway sync vercel
|
|
169
|
-
keyway sync netlify
|
|
170
|
-
keyway sync railway
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
The CLI handles everything:
|
|
174
|
-
1. OAuth connection (first time only)
|
|
175
|
-
2. Project auto-detection
|
|
176
|
-
3. Smart diff (only updates what changed)
|
|
177
|
-
|
|
178
|
-
Your local `.env`, Vercel, Netlify, Railway — all in sync with one command.
|
|
179
|
-
|
|
180
|
-
## Security
|
|
181
|
-
|
|
182
|
-
Keyway is designed to be **simple and secure** — a major upgrade from Slack or Notion, without the complexity of Hashicorp Vault or AWS Secrets Manager.
|
|
183
|
-
|
|
184
|
-
**What we do:**
|
|
185
|
-
- AES-256-GCM encryption server-side and client-side token storage
|
|
186
|
-
- TLS everywhere (HTTPS enforced)
|
|
187
|
-
- GitHub read-only permissions
|
|
188
|
-
- No access to your code
|
|
189
|
-
- Secrets stored encrypted at rest
|
|
190
|
-
- No analytics on secret values (only metadata)
|
|
191
|
-
- Encrypted token storage with file permissions
|
|
192
|
-
|
|
193
|
-
**What we don't do:**
|
|
194
|
-
- No zero-trust enterprise model
|
|
195
|
-
- No access to your cloud infrastructure
|
|
196
|
-
- No access to your production deployment keys
|
|
197
|
-
|
|
198
|
-
For detailed security information, see [SECURITY.md](./SECURITY.md) and [keyway.sh/security](https://keyway.sh/security)
|
|
199
|
-
|
|
200
|
-
## Who is this for?
|
|
201
|
-
|
|
202
|
-
Keyway is perfect for:
|
|
203
|
-
- Solo developers
|
|
204
|
-
- Small teams
|
|
205
|
-
- Side-projects
|
|
206
|
-
- Early SaaS
|
|
207
|
-
- Agencies managing many repos
|
|
208
|
-
- Rapid prototyping
|
|
209
|
-
|
|
210
|
-
**Not designed for:**
|
|
211
|
-
- Banks
|
|
212
|
-
- Governments
|
|
213
|
-
- Enterprise zero-trust teams
|
|
214
|
-
*(you're looking for Vault, Doppler, or AWS Secrets Manager)*
|
|
215
|
-
|
|
216
|
-
## Example Workflow
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
git clone git@github.com:acme/backend.git
|
|
220
|
-
cd backend
|
|
29
|
+
# Pull secrets to .env
|
|
221
30
|
keyway pull
|
|
222
|
-
# ✓ secrets pulled
|
|
223
|
-
npm run dev
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
Add a new secret:
|
|
227
|
-
|
|
228
|
-
```bash
|
|
229
|
-
echo "STRIPE_KEY=sk_live_xxx" >> .env
|
|
230
|
-
keyway push
|
|
231
|
-
# ✓ 1 secret updated
|
|
232
31
|
```
|
|
233
32
|
|
|
234
|
-
##
|
|
33
|
+
## Alternative Installation
|
|
235
34
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
If you cannot use the login flow, set a GitHub token manually:
|
|
35
|
+
Using the install script:
|
|
239
36
|
|
|
240
37
|
```bash
|
|
241
|
-
|
|
242
|
-
export GITHUB_TOKEN=your_github_personal_access_token
|
|
243
|
-
|
|
244
|
-
# Or via git config
|
|
245
|
-
git config --global github.token your_github_personal_access_token
|
|
38
|
+
curl -fsSL https://keyway.sh/install.sh | sh
|
|
246
39
|
```
|
|
247
40
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
By default, Keyway uses the production API. To point to another API:
|
|
251
|
-
|
|
252
|
-
```bash
|
|
253
|
-
export KEYWAY_API_URL=http://localhost:3000
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### Disable Telemetry
|
|
257
|
-
|
|
258
|
-
```bash
|
|
259
|
-
export KEYWAY_DISABLE_TELEMETRY=1
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
## Privacy & Analytics
|
|
263
|
-
|
|
264
|
-
**NEVER tracked:**
|
|
265
|
-
- Secret names or values
|
|
266
|
-
- Environment variable content
|
|
267
|
-
- Access tokens
|
|
268
|
-
- File contents
|
|
269
|
-
|
|
270
|
-
**Only tracked:**
|
|
271
|
-
- Command usage (init, push, pull)
|
|
272
|
-
- Repository names (public info)
|
|
273
|
-
- Error messages (sanitized)
|
|
274
|
-
|
|
275
|
-
## Troubleshooting
|
|
276
|
-
|
|
277
|
-
### "Not in a git repository"
|
|
278
|
-
|
|
279
|
-
```bash
|
|
280
|
-
git init
|
|
281
|
-
git remote add origin git@github.com:your-org/your-repo.git
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
### "GitHub token not found"
|
|
285
|
-
|
|
286
|
-
```bash
|
|
287
|
-
keyway login
|
|
288
|
-
# or
|
|
289
|
-
export GITHUB_TOKEN=your_token
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### "Vault not found"
|
|
293
|
-
|
|
294
|
-
```bash
|
|
295
|
-
keyway init
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
### "You do not have access to this repository"
|
|
299
|
-
|
|
300
|
-
Make sure you're a collaborator or admin on the GitHub repository.
|
|
301
|
-
|
|
302
|
-
## TL;DR
|
|
303
|
-
|
|
304
|
-
```bash
|
|
305
|
-
npm i -g @keywaysh/cli
|
|
306
|
-
keyway login
|
|
307
|
-
keyway init
|
|
308
|
-
keyway pull
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
No more Slack. No more outdated `.env`.
|
|
312
|
-
Your team stays perfectly in sync.
|
|
313
|
-
|
|
314
|
-
## Support
|
|
41
|
+
## Documentation
|
|
315
42
|
|
|
316
|
-
|
|
317
|
-
- **Email**: hello@keyway.sh
|
|
318
|
-
- **Website**: [keyway.sh](https://keyway.sh)
|
|
43
|
+
Visit [docs.keyway.sh](https://docs.keyway.sh) for full documentation.
|
|
319
44
|
|
|
320
45
|
## License
|
|
321
46
|
|
|
322
|
-
MIT
|
|
47
|
+
MIT
|
package/bin/keyway
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
const binaryName = process.platform === "win32" ? "keyway-bin.exe" : "keyway-bin";
|
|
8
|
+
const binaryPath = path.join(__dirname, binaryName);
|
|
9
|
+
|
|
10
|
+
if (!fs.existsSync(binaryPath)) {
|
|
11
|
+
console.error("keyway binary not found. Try reinstalling: npm install @keywaysh/cli");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const result = spawnSync(binaryPath, process.argv.slice(2), {
|
|
16
|
+
stdio: "inherit",
|
|
17
|
+
env: process.env,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (result.error) {
|
|
21
|
+
console.error("Failed to run keyway:", result.error.message);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.exit(result.status || 0);
|
package/package.json
CHANGED
|
@@ -1,70 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@keywaysh/cli",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
5
|
-
"type": "module",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "GitHub-native secrets management CLI",
|
|
6
5
|
"bin": {
|
|
7
|
-
"keyway": "./
|
|
6
|
+
"keyway": "./bin/keyway"
|
|
8
7
|
},
|
|
9
|
-
"main": "./dist/cli.js",
|
|
10
|
-
"files": [
|
|
11
|
-
"dist"
|
|
12
|
-
],
|
|
13
8
|
"scripts": {
|
|
14
|
-
"
|
|
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
|
-
"test:coverage": "pnpm exec vitest run --coverage",
|
|
22
|
-
"release": "npm version patch && git push && git push --tags",
|
|
23
|
-
"release:minor": "npm version minor && git push && git push --tags",
|
|
24
|
-
"release:major": "npm version major && git push && git push --tags"
|
|
9
|
+
"postinstall": "node scripts/install.js"
|
|
25
10
|
},
|
|
26
|
-
"keywords": [
|
|
27
|
-
"secrets",
|
|
28
|
-
"env",
|
|
29
|
-
"keyway",
|
|
30
|
-
"cli",
|
|
31
|
-
"devops"
|
|
32
|
-
],
|
|
33
|
-
"author": "Nicolas Ritouet",
|
|
34
|
-
"license": "MIT",
|
|
35
|
-
"homepage": "https://keyway.sh",
|
|
36
11
|
"repository": {
|
|
37
12
|
"type": "git",
|
|
38
|
-
"url": "https://github.com/keywaysh/cli.git"
|
|
13
|
+
"url": "git+https://github.com/keywaysh/cli-go.git"
|
|
39
14
|
},
|
|
15
|
+
"homepage": "https://keyway.sh",
|
|
40
16
|
"bugs": {
|
|
41
|
-
"url": "https://github.com/keywaysh/cli/issues"
|
|
17
|
+
"url": "https://github.com/keywaysh/cli-go/issues"
|
|
42
18
|
},
|
|
43
|
-
"
|
|
19
|
+
"keywords": [
|
|
20
|
+
"secrets",
|
|
21
|
+
"cli",
|
|
22
|
+
"github",
|
|
23
|
+
"env",
|
|
24
|
+
"environment-variables",
|
|
25
|
+
"dotenv",
|
|
26
|
+
"secrets-management"
|
|
27
|
+
],
|
|
28
|
+
"author": "Keyway <hello@keyway.sh>",
|
|
29
|
+
"license": "MIT",
|
|
44
30
|
"engines": {
|
|
45
|
-
"node": ">=
|
|
46
|
-
},
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"@octokit/rest": "^22.0.1",
|
|
49
|
-
"balanced-match": "^3.0.1",
|
|
50
|
-
"commander": "^14.0.0",
|
|
51
|
-
"conf": "^15.0.2",
|
|
52
|
-
"libsodium-wrappers": "^0.7.15",
|
|
53
|
-
"open": "^11.0.0",
|
|
54
|
-
"picocolors": "^1.1.1",
|
|
55
|
-
"posthog-node": "^3.5.0",
|
|
56
|
-
"prompts": "^2.4.2"
|
|
57
|
-
},
|
|
58
|
-
"devDependencies": {
|
|
59
|
-
"@types/balanced-match": "^3.0.2",
|
|
60
|
-
"@types/libsodium-wrappers": "^0.7.14",
|
|
61
|
-
"@types/node": "^24.2.0",
|
|
62
|
-
"@types/prompts": "^2.4.9",
|
|
63
|
-
"@vitest/coverage-v8": "^3.0.0",
|
|
64
|
-
"msw": "^2.12.4",
|
|
65
|
-
"tsup": "^8.5.0",
|
|
66
|
-
"tsx": "^4.20.3",
|
|
67
|
-
"typescript": "^5.9.2",
|
|
68
|
-
"vitest": "^3.2.4"
|
|
31
|
+
"node": ">=14"
|
|
69
32
|
}
|
|
70
33
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const https = require("https");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { execSync, spawnSync } = require("child_process");
|
|
7
|
+
const os = require("os");
|
|
8
|
+
const crypto = require("crypto");
|
|
9
|
+
|
|
10
|
+
const VERSION = require("../package.json").version;
|
|
11
|
+
const REPO = "keywaysh/cli-go";
|
|
12
|
+
const BINARY_NAME = "keyway";
|
|
13
|
+
|
|
14
|
+
const PLATFORMS = {
|
|
15
|
+
"darwin-arm64": "darwin_arm64",
|
|
16
|
+
"darwin-x64": "darwin_amd64",
|
|
17
|
+
"linux-arm64": "linux_arm64",
|
|
18
|
+
"linux-x64": "linux_amd64",
|
|
19
|
+
"win32-x64": "windows_amd64",
|
|
20
|
+
"win32-arm64": "windows_arm64",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function getPlatformKey() {
|
|
24
|
+
return `${process.platform}-${process.arch}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getBinaryPath() {
|
|
28
|
+
const binDir = path.join(__dirname, "..", "bin");
|
|
29
|
+
// Use keyway-bin to avoid conflict with the wrapper script
|
|
30
|
+
const binaryName = process.platform === "win32" ? `${BINARY_NAME}-bin.exe` : `${BINARY_NAME}-bin`;
|
|
31
|
+
return path.join(binDir, binaryName);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fetch(url) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const request = https.get(url, (response) => {
|
|
37
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
38
|
+
// Follow redirect
|
|
39
|
+
fetch(response.headers.location).then(resolve).catch(reject);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (response.statusCode !== 200) {
|
|
44
|
+
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const chunks = [];
|
|
49
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
50
|
+
response.on("end", () => resolve(Buffer.concat(chunks)));
|
|
51
|
+
response.on("error", reject);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
request.on("error", reject);
|
|
55
|
+
request.setTimeout(60000, () => {
|
|
56
|
+
request.destroy();
|
|
57
|
+
reject(new Error("Request timeout"));
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function downloadChecksum() {
|
|
63
|
+
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/checksums.txt`;
|
|
64
|
+
try {
|
|
65
|
+
const data = await fetch(url);
|
|
66
|
+
return data.toString();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.warn("Warning: Could not download checksums file");
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function verifyChecksum(buffer, checksums, filename) {
|
|
74
|
+
if (!checksums) return true;
|
|
75
|
+
|
|
76
|
+
const hash = crypto.createHash("sha256").update(buffer).digest("hex");
|
|
77
|
+
const expectedLine = checksums.split("\n").find((line) => line.includes(filename));
|
|
78
|
+
|
|
79
|
+
if (!expectedLine) {
|
|
80
|
+
console.warn(`Warning: No checksum found for ${filename}`);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const expectedHash = expectedLine.split(/\s+/)[0];
|
|
85
|
+
if (hash !== expectedHash) {
|
|
86
|
+
throw new Error(`Checksum mismatch for ${filename}\nExpected: ${expectedHash}\nGot: ${hash}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function extractTarGz(buffer, destDir) {
|
|
93
|
+
const tmpFile = path.join(os.tmpdir(), `keyway-${Date.now()}.tar.gz`);
|
|
94
|
+
fs.writeFileSync(tmpFile, buffer);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
execSync(`tar -xzf "${tmpFile}" -C "${destDir}"`, { stdio: "pipe" });
|
|
98
|
+
} finally {
|
|
99
|
+
fs.unlinkSync(tmpFile);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function extractZip(buffer, destDir) {
|
|
104
|
+
const tmpFile = path.join(os.tmpdir(), `keyway-${Date.now()}.zip`);
|
|
105
|
+
fs.writeFileSync(tmpFile, buffer);
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Try powershell on Windows
|
|
109
|
+
if (process.platform === "win32") {
|
|
110
|
+
execSync(
|
|
111
|
+
`powershell -Command "Expand-Archive -Path '${tmpFile}' -DestinationPath '${destDir}' -Force"`,
|
|
112
|
+
{ stdio: "pipe" }
|
|
113
|
+
);
|
|
114
|
+
} else {
|
|
115
|
+
execSync(`unzip -o "${tmpFile}" -d "${destDir}"`, { stdio: "pipe" });
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
fs.unlinkSync(tmpFile);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function install() {
|
|
123
|
+
const platformKey = getPlatformKey();
|
|
124
|
+
const target = PLATFORMS[platformKey];
|
|
125
|
+
|
|
126
|
+
if (!target) {
|
|
127
|
+
console.error(`\nUnsupported platform: ${platformKey}`);
|
|
128
|
+
console.error(`Supported platforms: ${Object.keys(PLATFORMS).join(", ")}`);
|
|
129
|
+
console.error(`\nYou can install manually from: https://github.com/${REPO}/releases`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const binDir = path.join(__dirname, "..", "bin");
|
|
134
|
+
const binaryPath = getBinaryPath();
|
|
135
|
+
|
|
136
|
+
// Check if binary already exists and is correct version
|
|
137
|
+
if (fs.existsSync(binaryPath)) {
|
|
138
|
+
try {
|
|
139
|
+
const result = spawnSync(binaryPath, ["--version"], { encoding: "utf8" });
|
|
140
|
+
if (result.stdout && result.stdout.includes(VERSION)) {
|
|
141
|
+
console.log(`keyway v${VERSION} already installed`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
// Binary exists but couldn't run, reinstall
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const isWindows = process.platform === "win32";
|
|
150
|
+
const ext = isWindows ? "zip" : "tar.gz";
|
|
151
|
+
const filename = `${BINARY_NAME}_${VERSION}_${target}.${ext}`;
|
|
152
|
+
const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${filename}`;
|
|
153
|
+
|
|
154
|
+
console.log(`Downloading keyway v${VERSION} for ${target}...`);
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
// Download checksums and binary in parallel
|
|
158
|
+
const [checksums, archiveBuffer] = await Promise.all([downloadChecksum(), fetch(url)]);
|
|
159
|
+
|
|
160
|
+
// Verify checksum
|
|
161
|
+
verifyChecksum(archiveBuffer, checksums, filename);
|
|
162
|
+
|
|
163
|
+
// Create bin directory
|
|
164
|
+
if (!fs.existsSync(binDir)) {
|
|
165
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Extract to temp dir first, then move binary
|
|
169
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "keyway-"));
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
if (isWindows) {
|
|
173
|
+
await extractZip(archiveBuffer, tmpDir);
|
|
174
|
+
} else {
|
|
175
|
+
await extractTarGz(archiveBuffer, tmpDir);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Find and move the binary (archive contains 'keyway', we save as 'keyway-bin')
|
|
179
|
+
const archiveBinaryName = isWindows ? `${BINARY_NAME}.exe` : BINARY_NAME;
|
|
180
|
+
const extractedBinary = path.join(tmpDir, archiveBinaryName);
|
|
181
|
+
|
|
182
|
+
if (!fs.existsSync(extractedBinary)) {
|
|
183
|
+
throw new Error(`Binary not found in archive: ${archiveBinaryName}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Remove old binary if exists
|
|
187
|
+
if (fs.existsSync(binaryPath)) {
|
|
188
|
+
fs.unlinkSync(binaryPath);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fs.copyFileSync(extractedBinary, binaryPath);
|
|
192
|
+
|
|
193
|
+
// Make executable on Unix
|
|
194
|
+
if (!isWindows) {
|
|
195
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(`Successfully installed keyway v${VERSION}`);
|
|
199
|
+
} finally {
|
|
200
|
+
// Cleanup temp directory
|
|
201
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.error(`\nFailed to install keyway: ${error.message}`);
|
|
205
|
+
console.error(`\nYou can install manually from: https://github.com/${REPO}/releases`);
|
|
206
|
+
console.error(`Or use: curl -fsSL https://keyway.sh/install.sh | sh`);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Only run if called directly (not required as module)
|
|
212
|
+
if (require.main === module) {
|
|
213
|
+
install();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
module.exports = { install, getBinaryPath };
|