@jacebenson/jsn 0.0.10 → 1.0.2
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 +7 -49
- package/bin/jsn.js +57 -2
- package/package.json +28 -32
- package/scripts/install.sh +227 -0
- package/scripts/npm-install.js +235 -0
- package/scripts/pre-commit-check.sh +61 -0
- package/src/app.js +0 -157
- package/src/auth.js +0 -283
- package/src/cli.js +0 -144
- package/src/commands/_ticket.js +0 -256
- package/src/commands/auth.js +0 -62
- package/src/commands/changes.js +0 -7
- package/src/commands/dev/_generic.js +0 -223
- package/src/commands/dev/_simple.js +0 -89
- package/src/commands/dev/eval.js +0 -17
- package/src/commands/dev/flows.js +0 -528
- package/src/commands/dev/forms.js +0 -313
- package/src/commands/dev/lists.js +0 -233
- package/src/commands/dev/logs.js +0 -51
- package/src/commands/dev/rest.js +0 -64
- package/src/commands/dev/scopes.js +0 -96
- package/src/commands/dev/updatesets.js +0 -97
- package/src/commands/dev.js +0 -53
- package/src/commands/groupmembers.js +0 -39
- package/src/commands/grouproles.js +0 -39
- package/src/commands/groups.js +0 -57
- package/src/commands/incidents.js +0 -7
- package/src/commands/profiles.js +0 -79
- package/src/commands/records.js +0 -137
- package/src/commands/requests.js +0 -7
- package/src/commands/setup.js +0 -39
- package/src/commands/tasks.js +0 -7
- package/src/commands/tickets.js +0 -121
- package/src/commands/users.js +0 -57
- package/src/commands/version.js +0 -25
- package/src/config.js +0 -154
- package/src/context.js +0 -62
- package/src/errors.js +0 -101
- package/src/helpers.js +0 -60
- package/src/output.js +0 -410
- package/src/sdk.js +0 -357
package/README.md
CHANGED
|
@@ -2,28 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.
|
|
4
4
|
|
|
5
|
-
## Versions
|
|
6
|
-
|
|
7
|
-
| Version | Language | Branch | Install | Status |
|
|
8
|
-
|---------|----------|--------|---------|--------|
|
|
9
|
-
| Go | Go | `main` | Binary / `go install` | Stable |
|
|
10
|
-
| Node.js | JavaScript (Node 18+) | `nodejs` | `npm install -g` | Active development |
|
|
11
|
-
|
|
12
|
-
Both versions share the same CLI interface and are tested against the same ServiceNow PDI.
|
|
13
|
-
|
|
14
5
|
## Installation
|
|
15
6
|
|
|
16
|
-
### npm (
|
|
7
|
+
### npm (Cross-platform — recommended for Windows)
|
|
17
8
|
|
|
18
9
|
```bash
|
|
19
|
-
npm install -g @jacebenson/jsn
|
|
10
|
+
npm install -g @jacebenson/jsn
|
|
20
11
|
```
|
|
21
12
|
|
|
22
|
-
|
|
13
|
+
Works on macOS, Linux, and Windows. The correct binary for your platform is downloaded automatically during install.
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### Download Binary (Go version)
|
|
15
|
+
### Download Binary
|
|
27
16
|
|
|
28
17
|
```bash
|
|
29
18
|
# Download the latest release
|
|
@@ -32,7 +21,7 @@ chmod +x jsn
|
|
|
32
21
|
sudo mv jsn /usr/local/bin/
|
|
33
22
|
```
|
|
34
23
|
|
|
35
|
-
### Go Install
|
|
24
|
+
### Go Install
|
|
36
25
|
|
|
37
26
|
```bash
|
|
38
27
|
go install github.com/jacebenson/jsn/cmd/jsn@latest
|
|
@@ -318,7 +307,7 @@ jsn auth status
|
|
|
318
307
|
jsn auth logout
|
|
319
308
|
```
|
|
320
309
|
|
|
321
|
-
Credentials are stored in `~/.config/servicenow/credentials/`
|
|
310
|
+
Credentials are securely stored in your OS keychain (or file fallback at `~/.config/servicenow/credentials/`).
|
|
322
311
|
|
|
323
312
|
## Environment Variables
|
|
324
313
|
|
|
@@ -342,8 +331,6 @@ jsn incidents list
|
|
|
342
331
|
|
|
343
332
|
## Shell Completion
|
|
344
333
|
|
|
345
|
-
> **Note:** Shell completion is available in the Go version only.
|
|
346
|
-
|
|
347
334
|
```bash
|
|
348
335
|
# Bash
|
|
349
336
|
source <(jsn completion bash)
|
|
@@ -394,39 +381,10 @@ Error (usage): Instance URL required. Set via --instance flag, SERVICENOW_INSTAN
|
|
|
394
381
|
- `--instance` flag
|
|
395
382
|
- `SERVICENOW_INSTANCE_URL` environment variable
|
|
396
383
|
|
|
397
|
-
## Development
|
|
398
|
-
|
|
399
|
-
This repository maintains two parallel implementations:
|
|
400
|
-
|
|
401
|
-
- **`main`** — Go implementation (stable)
|
|
402
|
-
- **`nodejs`** — Node.js implementation (active development)
|
|
403
|
-
|
|
404
|
-
Both branches share the same CLI interface and are kept in sync for feature parity.
|
|
405
|
-
|
|
406
|
-
### Node.js version
|
|
407
|
-
|
|
408
|
-
```bash
|
|
409
|
-
git checkout nodejs
|
|
410
|
-
npm install
|
|
411
|
-
npm test # Run tests
|
|
412
|
-
npm run lint # Run ESLint
|
|
413
|
-
npm run start # Run CLI locally
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### Releasing
|
|
417
|
-
|
|
418
|
-
```bash
|
|
419
|
-
# From nodejs branch — creates node-v* tag and publishes to npm
|
|
420
|
-
npm run release -- patch
|
|
421
|
-
|
|
422
|
-
# From main branch — creates go-v* tag and builds binaries
|
|
423
|
-
npm run release -- patch
|
|
424
|
-
```
|
|
425
|
-
|
|
426
384
|
## Contributing
|
|
427
385
|
|
|
428
386
|
1. Fork the repository
|
|
429
|
-
2. Create a feature branch
|
|
387
|
+
2. Create a feature branch
|
|
430
388
|
3. Make your changes
|
|
431
389
|
4. Add tests
|
|
432
390
|
5. Submit a pull request
|
package/bin/jsn.js
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* JSN CLI binary wrapper
|
|
4
|
+
*
|
|
5
|
+
* This script finds the downloaded platform-specific binary and spawns it,
|
|
6
|
+
* forwarding all arguments, stdin, stdout, and stderr.
|
|
7
|
+
*/
|
|
2
8
|
|
|
3
|
-
|
|
9
|
+
const { spawn } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
4
13
|
|
|
5
|
-
|
|
14
|
+
function getBinaryPath() {
|
|
15
|
+
const binaryDir = path.join(__dirname, '..', 'binary');
|
|
16
|
+
const binaryName = os.platform() === 'win32' ? 'jsn.exe' : 'jsn';
|
|
17
|
+
const binaryPath = path.join(binaryDir, binaryName);
|
|
18
|
+
|
|
19
|
+
if (fs.existsSync(binaryPath)) {
|
|
20
|
+
return binaryPath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Fallback: check if running from source/build
|
|
24
|
+
const devPath = path.join(__dirname, '..', 'jsn');
|
|
25
|
+
if (fs.existsSync(devPath)) {
|
|
26
|
+
return devPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Another fallback for dev
|
|
30
|
+
const devPath2 = path.join(__dirname, '..', 'jsn.exe');
|
|
31
|
+
if (fs.existsSync(devPath2)) {
|
|
32
|
+
return devPath2;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.error('Error: JSN binary not found.');
|
|
36
|
+
console.error('');
|
|
37
|
+
console.error('The binary should have been downloaded during npm install.');
|
|
38
|
+
console.error('Try reinstalling the package:');
|
|
39
|
+
console.error(' npm uninstall -g @jacebenson/jsn');
|
|
40
|
+
console.error(' npm install -g @jacebenson/jsn');
|
|
41
|
+
console.error('');
|
|
42
|
+
console.error('Or download manually from:');
|
|
43
|
+
console.error(' https://github.com/jacebenson/jsn/releases');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const binaryPath = getBinaryPath();
|
|
48
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
49
|
+
stdio: 'inherit',
|
|
50
|
+
windowsHide: true,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
child.on('exit', (code) => {
|
|
54
|
+
process.exit(code ?? 0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
child.on('error', (err) => {
|
|
58
|
+
console.error(`Failed to start JSN: ${err.message}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
package/package.json
CHANGED
|
@@ -1,44 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jacebenson/jsn",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.",
|
|
5
|
-
"
|
|
5
|
+
"homepage": "https://github.com/jacebenson/jsn#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/jacebenson/jsn/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/jacebenson/jsn.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "Jace Benson",
|
|
15
|
+
"type": "commonjs",
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"scripts",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
6
22
|
"bin": {
|
|
7
23
|
"jsn": "bin/jsn.js"
|
|
8
24
|
},
|
|
9
|
-
"main": "src/cli.js",
|
|
10
25
|
"scripts": {
|
|
11
|
-
"
|
|
12
|
-
"lint": "npx eslint src/ bin/ test/",
|
|
13
|
-
"start": "node bin/jsn.js",
|
|
14
|
-
"tag-reminder": "node scripts/tag-reminder.js",
|
|
15
|
-
"release": "node scripts/release.js"
|
|
26
|
+
"postinstall": "node scripts/npm-install.js"
|
|
16
27
|
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"servicenow",
|
|
19
|
-
"cli",
|
|
20
|
-
"rest-api",
|
|
21
|
-
"automation"
|
|
22
|
-
],
|
|
23
|
-
"author": "Jace Benson",
|
|
24
|
-
"license": "MIT",
|
|
25
28
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
27
|
-
},
|
|
28
|
-
"dependencies": {
|
|
29
|
-
"@inquirer/prompts": "^7.5.1",
|
|
30
|
-
"chalk": "^5.4.1",
|
|
31
|
-
"cli-table3": "^0.6.5",
|
|
32
|
-
"yargs": "^17.7.2"
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@eslint/js": "^10.0.1",
|
|
36
|
-
"globals": "^17.6.0"
|
|
29
|
+
"node": ">=14"
|
|
37
30
|
},
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
31
|
+
"os": [
|
|
32
|
+
"darwin",
|
|
33
|
+
"linux",
|
|
34
|
+
"win32"
|
|
35
|
+
],
|
|
36
|
+
"cpu": [
|
|
37
|
+
"x64",
|
|
38
|
+
"arm64"
|
|
43
39
|
]
|
|
44
40
|
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# JSN CLI Installer
|
|
3
|
+
# Installs the latest JSN release from GitHub
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# curl -fsSL https://jsn.jace.pro/install | bash
|
|
7
|
+
#
|
|
8
|
+
# Environment:
|
|
9
|
+
# JSN_VERSION Install specific version (e.g., 0.4.1)
|
|
10
|
+
# NO_COLOR Disable colored output (https://no-color.org)
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
REPO="jacebenson/jsn"
|
|
15
|
+
BINARY_NAME="jsn"
|
|
16
|
+
INSTALL_DIR="${JSN_INSTALL_DIR:-}"
|
|
17
|
+
VERSION="${JSN_VERSION:-}"
|
|
18
|
+
|
|
19
|
+
# Colors - respect NO_COLOR (https://no-color.org)
|
|
20
|
+
if [[ -z "${NO_COLOR:-}" ]] && [[ -t 1 ]]; then
|
|
21
|
+
RED='\033[0;31m'
|
|
22
|
+
GREEN='\033[0;32m'
|
|
23
|
+
YELLOW='\033[1;33m'
|
|
24
|
+
NC='\033[0m'
|
|
25
|
+
BOLD='\033[1m'
|
|
26
|
+
else
|
|
27
|
+
RED=''
|
|
28
|
+
GREEN=''
|
|
29
|
+
YELLOW=''
|
|
30
|
+
NC=''
|
|
31
|
+
BOLD=''
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
info() { echo -e "${GREEN}✓${NC} $1"; }
|
|
35
|
+
step() { echo -e "${BOLD}→${NC} $1"; }
|
|
36
|
+
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
|
|
37
|
+
error() { echo -e "${RED}✗${NC} $1" >&2; exit 1; }
|
|
38
|
+
|
|
39
|
+
detect_platform() {
|
|
40
|
+
local os arch
|
|
41
|
+
|
|
42
|
+
os=$(uname -s | tr '[:upper:]' '[:lower:]')
|
|
43
|
+
case "$os" in
|
|
44
|
+
linux) PLATFORM="linux" ;;
|
|
45
|
+
darwin) PLATFORM="darwin" ;;
|
|
46
|
+
mingw*|msys*|cygwin*)
|
|
47
|
+
PLATFORM="windows"
|
|
48
|
+
BINARY_NAME="jsn.exe"
|
|
49
|
+
;;
|
|
50
|
+
*) error "Unsupported OS: $os" ;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
arch=$(uname -m)
|
|
54
|
+
case "$arch" in
|
|
55
|
+
x86_64|amd64) ARCH="amd64" ;;
|
|
56
|
+
arm64|aarch64) ARCH="arm64" ;;
|
|
57
|
+
armv7l) ARCH="arm" ;;
|
|
58
|
+
*) error "Unsupported architecture: $arch" ;;
|
|
59
|
+
esac
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get_latest_version() {
|
|
63
|
+
# Use redirect URL instead of API to avoid rate limits
|
|
64
|
+
local url
|
|
65
|
+
url=$(curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/${REPO}/releases/latest" 2>/dev/null) || true
|
|
66
|
+
local version="${url##*/}"
|
|
67
|
+
version="${version#v}"
|
|
68
|
+
|
|
69
|
+
if [[ ! $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
70
|
+
error "Could not determine latest version. Check your network connection."
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
echo "$version"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setup_install_dir() {
|
|
77
|
+
if [[ -z "$INSTALL_DIR" ]]; then
|
|
78
|
+
if [[ -w "/usr/local/bin" ]]; then
|
|
79
|
+
INSTALL_DIR="/usr/local/bin"
|
|
80
|
+
else
|
|
81
|
+
INSTALL_DIR="$HOME/.local/bin"
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
mkdir -p "$INSTALL_DIR"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
download_and_install() {
|
|
88
|
+
local version="$1"
|
|
89
|
+
local tmp_dir
|
|
90
|
+
tmp_dir=$(mktemp -d)
|
|
91
|
+
trap "rm -rf '$tmp_dir'" EXIT
|
|
92
|
+
|
|
93
|
+
# Determine archive extension
|
|
94
|
+
local ext
|
|
95
|
+
if [[ "$PLATFORM" == "windows" ]]; then
|
|
96
|
+
ext="zip"
|
|
97
|
+
else
|
|
98
|
+
ext="tar.gz"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
local filename="jsn_v${version}_${PLATFORM}_${ARCH}.${ext}"
|
|
102
|
+
local url="https://github.com/${REPO}/releases/download/v${version}/${filename}"
|
|
103
|
+
|
|
104
|
+
step "Downloading JSN v${version} for ${PLATFORM}/${ARCH}..."
|
|
105
|
+
if ! curl -fsSL "$url" -o "${tmp_dir}/${filename}"; then
|
|
106
|
+
error "Download failed from $url"
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
step "Extracting..."
|
|
110
|
+
cd "$tmp_dir"
|
|
111
|
+
if [[ "$PLATFORM" == "windows" ]]; then
|
|
112
|
+
unzip -q "$filename"
|
|
113
|
+
else
|
|
114
|
+
tar -xzf "$filename"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# Find binary using shell globbing (Windows find is different)
|
|
118
|
+
local binary_file=""
|
|
119
|
+
for f in jsn jsn.exe jsn_*; do
|
|
120
|
+
if [[ -f "$f" ]] && [[ "$f" != "*.tar.gz" ]] && [[ "$f" != "*.zip" ]]; then
|
|
121
|
+
binary_file="$f"
|
|
122
|
+
break
|
|
123
|
+
fi
|
|
124
|
+
done
|
|
125
|
+
|
|
126
|
+
if [[ -z "$binary_file" ]]; then
|
|
127
|
+
error "Could not find binary in archive"
|
|
128
|
+
ls -la
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
step "Installing to $INSTALL_DIR..."
|
|
133
|
+
if [[ -w "$INSTALL_DIR" ]]; then
|
|
134
|
+
mv "$binary_file" "$INSTALL_DIR/$BINARY_NAME"
|
|
135
|
+
else
|
|
136
|
+
mv "$binary_file" "$INSTALL_DIR/$BINARY_NAME" 2>/dev/null || {
|
|
137
|
+
warn "Need sudo access to install to $INSTALL_DIR"
|
|
138
|
+
sudo mv "$binary_file" "$INSTALL_DIR/$BINARY_NAME"
|
|
139
|
+
}
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
chmod +x "$INSTALL_DIR/$BINARY_NAME" 2>/dev/null || true
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setup_path() {
|
|
146
|
+
# Check if already in PATH
|
|
147
|
+
if [[ ":$PATH:" == *":$INSTALL_DIR:"* ]]; then
|
|
148
|
+
return 0
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Determine shell config file
|
|
152
|
+
local shell_rc=""
|
|
153
|
+
if [[ "$PLATFORM" == "windows" ]]; then
|
|
154
|
+
# On Windows, always use .bashrc for Git Bash
|
|
155
|
+
shell_rc="$HOME/.bashrc"
|
|
156
|
+
else
|
|
157
|
+
case "${SHELL:-}" in
|
|
158
|
+
*/zsh) shell_rc="$HOME/.zshrc" ;;
|
|
159
|
+
*/bash) shell_rc="$HOME/.bashrc" ;;
|
|
160
|
+
*) shell_rc="$HOME/.profile" ;;
|
|
161
|
+
esac
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Check if already in config
|
|
165
|
+
if [[ -f "$shell_rc" ]] && grep -qF "$INSTALL_DIR" "$shell_rc" 2>/dev/null; then
|
|
166
|
+
return 0
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
step "Adding $INSTALL_DIR to PATH in $shell_rc"
|
|
170
|
+
echo "" >> "$shell_rc"
|
|
171
|
+
echo "# Added by JSN installer" >> "$shell_rc"
|
|
172
|
+
echo "export PATH=\"$INSTALL_DIR:\$PATH\"" >> "$shell_rc"
|
|
173
|
+
info "Added to $shell_rc"
|
|
174
|
+
warn "Run: source $shell_rc"
|
|
175
|
+
|
|
176
|
+
# On Windows, also warn about CMD/PowerShell
|
|
177
|
+
if [[ "$PLATFORM" == "windows" ]]; then
|
|
178
|
+
echo ""
|
|
179
|
+
warn "For Command Prompt or PowerShell, add this to your PATH manually:"
|
|
180
|
+
echo " $INSTALL_DIR"
|
|
181
|
+
echo " setx PATH \"%PATH%;$INSTALL_DIR\""
|
|
182
|
+
fi
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
verify_install() {
|
|
186
|
+
local installed_version
|
|
187
|
+
if installed_version=$("$INSTALL_DIR/$BINARY_NAME" version 2>/dev/null | tail -1); then
|
|
188
|
+
info "${installed_version}"
|
|
189
|
+
return 0
|
|
190
|
+
fi
|
|
191
|
+
error "Installation failed - JSN not working"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main() {
|
|
195
|
+
step "Installing JSN..."
|
|
196
|
+
|
|
197
|
+
detect_platform
|
|
198
|
+
|
|
199
|
+
if [[ -n "$VERSION" ]]; then
|
|
200
|
+
# Strip leading 'v' if present
|
|
201
|
+
VERSION="${VERSION#v}"
|
|
202
|
+
if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
203
|
+
error "Invalid version format: $VERSION (expected: 0.4.1)"
|
|
204
|
+
fi
|
|
205
|
+
else
|
|
206
|
+
VERSION=$(get_latest_version)
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
setup_install_dir
|
|
210
|
+
download_and_install "$VERSION"
|
|
211
|
+
setup_path
|
|
212
|
+
verify_install "$PLATFORM"
|
|
213
|
+
|
|
214
|
+
echo ""
|
|
215
|
+
info "JSN installed successfully!"
|
|
216
|
+
echo ""
|
|
217
|
+
echo " Run 'jsn' to get started (setup will run automatically on first use)"
|
|
218
|
+
echo ""
|
|
219
|
+
|
|
220
|
+
if [[ "$PLATFORM" == "windows" ]]; then
|
|
221
|
+
warn "Windows users: Add $INSTALL_DIR to your PATH manually:"
|
|
222
|
+
echo " setx PATH \"%PATH%;$INSTALL_DIR\""
|
|
223
|
+
echo ""
|
|
224
|
+
fi
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
main "$@"
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* JSN npm postinstall script
|
|
4
|
+
*
|
|
5
|
+
* Downloads the correct platform-specific binary from GitHub releases
|
|
6
|
+
* and places it in the `binary/` directory for the wrapper to find.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
|
|
15
|
+
const REPO = 'jacebenson/jsn';
|
|
16
|
+
const BINARY_DIR = path.join(__dirname, '..', 'binary');
|
|
17
|
+
|
|
18
|
+
const PLATFORM_MAP = {
|
|
19
|
+
darwin: 'darwin',
|
|
20
|
+
linux: 'linux',
|
|
21
|
+
win32: 'windows',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const ARCH_MAP = {
|
|
25
|
+
x64: 'amd64',
|
|
26
|
+
arm64: 'arm64',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function detectPlatform() {
|
|
30
|
+
const platform = os.platform();
|
|
31
|
+
const arch = os.arch();
|
|
32
|
+
|
|
33
|
+
const mappedPlatform = PLATFORM_MAP[platform];
|
|
34
|
+
const mappedArch = ARCH_MAP[arch];
|
|
35
|
+
|
|
36
|
+
if (!mappedPlatform) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Unsupported platform: ${platform}. ` +
|
|
39
|
+
`Supported platforms: ${Object.keys(PLATFORM_MAP).join(', ')}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!mappedArch) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Unsupported architecture: ${arch}. ` +
|
|
46
|
+
`Supported architectures: ${Object.keys(ARCH_MAP).join(', ')}`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Windows arm64 can run amd64 binaries, so fall back
|
|
51
|
+
if (mappedPlatform === 'windows' && mappedArch === 'arm64') {
|
|
52
|
+
console.log(
|
|
53
|
+
'Warning: Windows arm64 build not available, using amd64 binary.'
|
|
54
|
+
);
|
|
55
|
+
return { platform: 'windows', arch: 'amd64', fallback: true };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { platform: mappedPlatform, arch: mappedArch };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getVersion() {
|
|
62
|
+
// npm sets this during install
|
|
63
|
+
if (process.env.npm_package_version) {
|
|
64
|
+
return process.env.npm_package_version;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Fallback: read package.json
|
|
68
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
69
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
70
|
+
return pkg.version;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function downloadFile(url, dest) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const file = fs.createWriteStream(dest);
|
|
76
|
+
https
|
|
77
|
+
.get(url, { timeout: 30000 }, (response) => {
|
|
78
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
79
|
+
// Follow redirect
|
|
80
|
+
return downloadFile(response.headers.location, dest)
|
|
81
|
+
.then(resolve)
|
|
82
|
+
.catch(reject);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (response.statusCode !== 200) {
|
|
86
|
+
reject(
|
|
87
|
+
new Error(
|
|
88
|
+
`Download failed: HTTP ${response.statusCode} for ${url}`
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
response.pipe(file);
|
|
95
|
+
file.on('finish', () => {
|
|
96
|
+
file.close(resolve);
|
|
97
|
+
});
|
|
98
|
+
})
|
|
99
|
+
.on('error', (err) => {
|
|
100
|
+
fs.unlink(dest, () => {});
|
|
101
|
+
reject(err);
|
|
102
|
+
})
|
|
103
|
+
.on('timeout', () => {
|
|
104
|
+
fs.unlink(dest, () => {});
|
|
105
|
+
reject(new Error('Download timeout'));
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function extractArchive(archivePath, destDir) {
|
|
111
|
+
const ext = path.extname(archivePath);
|
|
112
|
+
const isWindows = os.platform() === 'win32';
|
|
113
|
+
|
|
114
|
+
if (archivePath.endsWith('.zip')) {
|
|
115
|
+
// Use PowerShell on Windows, unzip elsewhere
|
|
116
|
+
if (isWindows) {
|
|
117
|
+
execSync(
|
|
118
|
+
`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force"`,
|
|
119
|
+
{ stdio: 'inherit' }
|
|
120
|
+
);
|
|
121
|
+
} else {
|
|
122
|
+
execSync(`unzip -q -o "${archivePath}" -d "${destDir}"`, {
|
|
123
|
+
stdio: 'inherit',
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} else if (archivePath.endsWith('.tar.gz')) {
|
|
127
|
+
execSync(`tar -xzf "${archivePath}" -C "${destDir}"`, {
|
|
128
|
+
stdio: 'inherit',
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
throw new Error(`Unknown archive format: ${archivePath}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function findBinary(extractDir) {
|
|
136
|
+
const binaryName = os.platform() === 'win32' ? 'jsn.exe' : 'jsn';
|
|
137
|
+
|
|
138
|
+
// The archive contains just the binary directly
|
|
139
|
+
const directPath = path.join(extractDir, binaryName);
|
|
140
|
+
if (fs.existsSync(directPath)) {
|
|
141
|
+
return directPath;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Sometimes archives have nested directories
|
|
145
|
+
const entries = fs.readdirSync(extractDir);
|
|
146
|
+
for (const entry of entries) {
|
|
147
|
+
const entryPath = path.join(extractDir, entry);
|
|
148
|
+
const stat = fs.statSync(entryPath);
|
|
149
|
+
if (stat.isDirectory()) {
|
|
150
|
+
const nestedPath = path.join(entryPath, binaryName);
|
|
151
|
+
if (fs.existsSync(nestedPath)) {
|
|
152
|
+
return nestedPath;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Could not find ${binaryName} in extracted archive. Contents: ${entries.join(', ')}`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function main() {
|
|
163
|
+
// Skip if running in development (from git repo)
|
|
164
|
+
if (fs.existsSync(path.join(__dirname, '..', 'go.mod'))) {
|
|
165
|
+
console.log('Development environment detected. Skipping binary download.');
|
|
166
|
+
console.log('Build the binary manually: go build ./cmd/jsn');
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { platform, arch } = detectPlatform();
|
|
171
|
+
const version = getVersion();
|
|
172
|
+
|
|
173
|
+
// Development placeholder version
|
|
174
|
+
if (version === '0.0.0') {
|
|
175
|
+
console.log('Warning: version is 0.0.0, skipping binary download.');
|
|
176
|
+
console.log('This is expected when installing from source.');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const ext = platform === 'windows' ? 'zip' : 'tar.gz';
|
|
181
|
+
const filename = `jsn_v${version}_${platform}_${arch}.${ext}`;
|
|
182
|
+
const url = `https://github.com/${REPO}/releases/download/v${version}/${filename}`;
|
|
183
|
+
|
|
184
|
+
console.log(`Downloading JSN v${version} for ${platform}/${arch}...`);
|
|
185
|
+
console.log(` ${url}`);
|
|
186
|
+
|
|
187
|
+
// Ensure binary directory exists
|
|
188
|
+
if (!fs.existsSync(BINARY_DIR)) {
|
|
189
|
+
fs.mkdirSync(BINARY_DIR, { recursive: true });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jsn-install-'));
|
|
193
|
+
const archivePath = path.join(tmpDir, filename);
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
await downloadFile(url, archivePath);
|
|
197
|
+
console.log('Extracting...');
|
|
198
|
+
extractArchive(archivePath, tmpDir);
|
|
199
|
+
|
|
200
|
+
const extractedBinary = findBinary(tmpDir);
|
|
201
|
+
const binaryName = platform === 'windows' ? 'jsn.exe' : 'jsn';
|
|
202
|
+
const finalPath = path.join(BINARY_DIR, binaryName);
|
|
203
|
+
|
|
204
|
+
// Move binary to final location
|
|
205
|
+
fs.copyFileSync(extractedBinary, finalPath);
|
|
206
|
+
|
|
207
|
+
// Make executable on Unix
|
|
208
|
+
if (platform !== 'windows') {
|
|
209
|
+
fs.chmodSync(finalPath, 0o755);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(`Installed JSN binary to ${finalPath}`);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.error(`Error: ${err.message}`);
|
|
215
|
+
console.error('');
|
|
216
|
+
console.error('Failed to download the JSN binary.');
|
|
217
|
+
console.error('You can still use the CLI if you have a binary installed manually.');
|
|
218
|
+
console.error('Download from: https://github.com/jacebenson/jsn/releases');
|
|
219
|
+
|
|
220
|
+
// Don't fail the npm install — the user can fix this later
|
|
221
|
+
process.exit(0);
|
|
222
|
+
} finally {
|
|
223
|
+
// Cleanup temp directory
|
|
224
|
+
try {
|
|
225
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
226
|
+
} catch {
|
|
227
|
+
// Ignore cleanup errors
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
main().catch((err) => {
|
|
233
|
+
console.error(`Unexpected error: ${err.message}`);
|
|
234
|
+
process.exit(0); // Don't fail npm install
|
|
235
|
+
});
|