@jacebenson/jsn 1.0.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 ADDED
@@ -0,0 +1,398 @@
1
+ # JSN - ServiceNow CLI
2
+
3
+ A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.
4
+
5
+ ## Installation
6
+
7
+ ### npm (Cross-platform โ€” recommended for Windows)
8
+
9
+ ```bash
10
+ npm install -g @jacebenson/jsn
11
+ ```
12
+
13
+ Works on macOS, Linux, and Windows. The correct binary for your platform is downloaded automatically during install.
14
+
15
+ ### Download Binary
16
+
17
+ ```bash
18
+ # Download the latest release
19
+ curl -L https://github.com/jacebenson/jsn/releases/latest/download/jsn-linux-amd64 -o jsn
20
+ chmod +x jsn
21
+ sudo mv jsn /usr/local/bin/
22
+ ```
23
+
24
+ ### Go Install
25
+
26
+ ```bash
27
+ go install github.com/jacebenson/jsn/cmd/jsn@latest
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Setup
33
+
34
+ Run the interactive setup to configure your first ServiceNow instance:
35
+
36
+ ```bash
37
+ jsn setup
38
+ ```
39
+
40
+ This will:
41
+ 1. Ask for your ServiceNow instance URL
42
+ 2. Open a browser for OAuth authentication
43
+ 3. Set the instance as your default
44
+
45
+ ### 2. Verify Authentication
46
+
47
+ ```bash
48
+ jsn auth status
49
+ ```
50
+
51
+ ### 3. Start Using
52
+
53
+ ```bash
54
+ # List all incidents
55
+ jsn incidents
56
+
57
+ # Show a specific incident
58
+ jsn incidents INC0010001
59
+
60
+ # Create a new incident
61
+ jsn incidents create --description "Server down" --priority 1
62
+
63
+ # List change requests
64
+ jsn changes
65
+
66
+ # Query any table
67
+ jsn records list --table incident --query "priority=1^active=true"
68
+ ```
69
+
70
+ ## Configuration
71
+
72
+ JSN uses a layered configuration system:
73
+
74
+ | Source | Priority | Description |
75
+ |--------|----------|-------------|
76
+ | Flags | Highest | `--instance`, `--profile`, `--format` |
77
+ | Environment | High | `SERVICENOW_INSTANCE_URL`, `SERVICENOW_FORMAT` |
78
+ | Local config | Medium | `./.servicenow/config.json` |
79
+ | Global config | Low | `~/.config/servicenow/config.json` |
80
+ | Defaults | Lowest | Built-in defaults |
81
+
82
+ ### Profiles
83
+
84
+ Work with multiple ServiceNow instances using profiles:
85
+
86
+ ```bash
87
+ # Login to a new instance
88
+ jsn auth login https://dev12345.service-now.com
89
+
90
+ # List all profiles
91
+ jsn profiles list
92
+
93
+ # Switch to a different profile
94
+ jsn profiles use dev12345
95
+
96
+ # Show current profile
97
+ jsn profiles show
98
+ ```
99
+
100
+ ## Commands
101
+
102
+ ### Work Commands (Day-to-day operations)
103
+
104
+ | Command | Aliases | Description |
105
+ |---------|---------|-------------|
106
+ | `incidents` | `incident`, `inc` | Manage IT incidents |
107
+ | `changes` | `change`, `chg` | Manage change requests |
108
+ | `requests` | `request`, `req`, `ritm` | Manage service catalog requests |
109
+ | `tasks` | `task`, `sctask` | Manage service catalog tasks |
110
+ | `users` | `user` | Manage users |
111
+ | `groups` | `group` | Manage user groups |
112
+ | `records` | - | Generic Table API access |
113
+
114
+ ### Dev Commands (Development artifacts)
115
+
116
+ | Category | Command | Description |
117
+ |----------|---------|-------------|
118
+ | **Automations** | `dev flows` | Manage Flow Designer flows |
119
+ | | `dev actions` | Manage action definitions |
120
+ | **Scripts** | `dev includes` | Manage script includes |
121
+ | | `dev rules` | Manage business rules |
122
+ | | `dev clientscripts` | Manage client scripts |
123
+ | | `dev uiactions` | Manage UI actions |
124
+ | | `dev uipolicies` | Manage UI policies |
125
+ | **Data** | `dev tables` | View table definitions |
126
+ | | `dev columns` | Manage column definitions |
127
+ | | `dev import` | Manage import sets |
128
+ | **Security** | `dev acls` | Manage access controls |
129
+ | | `dev roles` | Manage roles |
130
+ | **Platform** | `dev updatesets` | Manage update sets |
131
+ | | `dev scopes` | Manage application scopes |
132
+ | | `dev properties` | Manage system properties |
133
+ | | `dev logs` | Query system logs |
134
+ | | `dev rest` | Raw REST API calls |
135
+ | | `dev eval` | Execute background scripts |
136
+
137
+ ## Usage Examples
138
+
139
+ ### Incidents
140
+
141
+ ```bash
142
+ # List all incidents
143
+ jsn incidents
144
+
145
+ # List critical incidents
146
+ jsn incidents list --query "priority=1"
147
+
148
+ # Show specific incident
149
+ jsn incidents INC0010001
150
+
151
+ # Create incident
152
+ jsn incidents create --description "Server down" --priority 1
153
+
154
+ # Update incident
155
+ jsn incidents update INC0010001 --data '{"state": "6"}'
156
+
157
+ # Delete incident
158
+ jsn incidents delete INC0010001
159
+ ```
160
+
161
+ ### Changes
162
+
163
+ ```bash
164
+ # List all change requests
165
+ jsn changes
166
+
167
+ # List high-risk changes
168
+ jsn changes list --query "risk=high"
169
+
170
+ # Create change request
171
+ jsn changes create --description "Deploy feature" --risk medium
172
+
173
+ # Update change
174
+ jsn changes update CHG0010001 --data '{"state": "3"}'
175
+
176
+ # Delete change
177
+ jsn changes delete CHG0010001
178
+ ```
179
+
180
+ ### Development Artifacts
181
+
182
+ ```bash
183
+ # AUTOMATIONS
184
+ # List Flow Designer flows
185
+ jsn dev flows
186
+
187
+ # List action definitions
188
+ jsn dev actions
189
+
190
+ # SCRIPTS
191
+ # List script includes
192
+ jsn dev includes
193
+
194
+ # Get a specific script include
195
+ jsn dev includes MyScriptInclude
196
+
197
+ # List business rules
198
+ jsn dev rules
199
+
200
+ # List client scripts
201
+ jsn dev clientscripts
202
+
203
+ # List UI actions
204
+ jsn dev uiactions
205
+
206
+ # List UI policies
207
+ jsn dev uipolicies
208
+
209
+ # DATA
210
+ # View table definition
211
+ jsn dev tables incident
212
+
213
+ # List table columns
214
+ jsn dev columns --table incident
215
+
216
+ # SECURITY
217
+ # List access controls (ACLs)
218
+ jsn dev acls
219
+
220
+ # List roles
221
+ jsn dev roles
222
+
223
+ # PLATFORM
224
+ # List update sets
225
+ jsn dev updatesets
226
+
227
+ # Set current update set
228
+ jsn dev updatesets set "My Update Set"
229
+
230
+ # List application scopes
231
+ jsn dev scopes
232
+
233
+ # Query system properties
234
+ jsn dev properties
235
+
236
+ # Query system logs
237
+ jsn dev logs --level error
238
+ jsn dev logs --source "Business Rule" --level warn
239
+
240
+ # Execute background script
241
+ jsn dev eval "gs.info('Hello World')"
242
+ ```
243
+
244
+ ### Generic Table API
245
+
246
+ ```bash
247
+ # List any table
248
+ jsn records list --table incident --limit 10
249
+
250
+ # Query with encoded query
251
+ jsn records list --table incident --query "priority=1^active=true"
252
+
253
+ # Show specific columns
254
+ jsn records list --table incident --columns "number,short_description,priority"
255
+
256
+ # Get a record by sys_id
257
+ jsn records get --table incident --sys-id abc123
258
+
259
+ # Create a record
260
+ jsn records create --table incident --data '{"short_description": "Test"}'
261
+
262
+ # Update a record
263
+ jsn records update --table incident --sys-id abc123 --data '{"priority": "1"}'
264
+
265
+ # Delete a record
266
+ jsn records delete --table incident --sys-id abc123
267
+ ```
268
+
269
+ ## Output Formats
270
+
271
+ JSN supports multiple output formats:
272
+
273
+ | Format | Flag | Description |
274
+ |--------|------|-------------|
275
+ | Auto (default) | `--format=auto` | JSON for pipes, styled for TTY |
276
+ | JSON | `--json` or `--format=json` | Machine-readable JSON |
277
+ | Styled | `--styled` | ANSI-styled tables (for humans) |
278
+ | Markdown | `--markdown` | Markdown tables |
279
+ | Quiet | `--quiet` or `-q` | Data only, no envelope |
280
+
281
+ ```bash
282
+ # JSON output
283
+ jsn incidents --json
284
+
285
+ # Styled table output
286
+ jsn incidents --styled
287
+
288
+ # Markdown output for documentation
289
+ jsn incidents --markdown
290
+
291
+ # Quiet mode for piping
292
+ jsn incidents -q | jq '.[].number'
293
+ ```
294
+
295
+ ## Authentication
296
+
297
+ JSN uses OAuth 2.0 with PKCE for secure authentication:
298
+
299
+ ```bash
300
+ # Login to an instance
301
+ jsn auth login https://dev12345.service-now.com
302
+
303
+ # Check authentication status
304
+ jsn auth status
305
+
306
+ # Logout
307
+ jsn auth logout
308
+ ```
309
+
310
+ Credentials are securely stored in your OS keychain (or file fallback at `~/.config/servicenow/credentials/`).
311
+
312
+ ## Environment Variables
313
+
314
+ | Variable | Description |
315
+ |----------|-------------|
316
+ | `SERVICENOW_INSTANCE_URL` | Default instance URL |
317
+ | `SERVICENOW_FORMAT` | Default output format |
318
+ | `SERVICENOW_OAUTH_TOKEN` | OAuth token (for CI/CD) |
319
+
320
+ ## CI/CD Integration
321
+
322
+ For automated environments, use the OAuth token:
323
+
324
+ ```bash
325
+ export SERVICENOW_INSTANCE_URL="https://dev12345.service-now.com"
326
+ export SERVICENOW_OAUTH_TOKEN="your-oauth-token"
327
+
328
+ # Now run commands without interactive auth
329
+ jsn incidents list
330
+ ```
331
+
332
+ ## Shell Completion
333
+
334
+ ```bash
335
+ # Bash
336
+ source <(jsn completion bash)
337
+
338
+ # Zsh
339
+ source <(jsn completion zsh)
340
+
341
+ # Fish
342
+ jsn completion fish | source
343
+ ```
344
+
345
+ ## Getting Help
346
+
347
+ ```bash
348
+ # General help
349
+ jsn --help
350
+
351
+ # Command help
352
+ jsn incidents --help
353
+
354
+ # Subcommand help
355
+ jsn incidents create --help
356
+ ```
357
+
358
+ ## Troubleshooting
359
+
360
+ ### Not authenticated
361
+
362
+ ```bash
363
+ โš ๏ธ Not authenticated to https://dev12345.service-now.com
364
+
365
+ To get started, run:
366
+ jsn setup # Interactive setup
367
+ jsn auth login # Login to instance
368
+ ```
369
+
370
+ **Solution**: Run `jsn setup` or `jsn auth login <instance>`
371
+
372
+ ### Instance URL required
373
+
374
+ ```bash
375
+ Error (usage): Instance URL required. Set via --instance flag, SERVICENOW_INSTANCE_URL env, or config file.
376
+ ```
377
+
378
+ **Solution**: Set the instance with one of:
379
+ - `jsn setup`
380
+ - `jsn auth login <instance>`
381
+ - `--instance` flag
382
+ - `SERVICENOW_INSTANCE_URL` environment variable
383
+
384
+ ## Contributing
385
+
386
+ 1. Fork the repository
387
+ 2. Create a feature branch
388
+ 3. Make your changes
389
+ 4. Add tests
390
+ 5. Submit a pull request
391
+
392
+ ## License
393
+
394
+ MIT License - see LICENSE file for details
395
+
396
+ ## Acknowledgments
397
+
398
+ This project follows the architectural patterns from [basecamp-cli](https://github.com/basecamp/basecamp-cli).
package/bin/jsn.js ADDED
@@ -0,0 +1,60 @@
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
+ */
8
+
9
+ const { spawn } = require('child_process');
10
+ const path = require('path');
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+
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 ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@jacebenson/jsn",
3
+ "version": "1.0.1",
4
+ "description": "A command-line interface for ServiceNow that follows the Unix philosophy: simple, composable, and scriptable.",
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
+ ],
22
+ "bin": {
23
+ "jsn": "bin/jsn.js"
24
+ },
25
+ "scripts": {
26
+ "postinstall": "node scripts/npm-install.js"
27
+ },
28
+ "engines": {
29
+ "node": ">=14"
30
+ },
31
+ "os": [
32
+ "darwin",
33
+ "linux",
34
+ "win32"
35
+ ],
36
+ "cpu": [
37
+ "x64",
38
+ "arm64"
39
+ ]
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
+ });
@@ -0,0 +1,61 @@
1
+ #!/bin/bash
2
+ # pre-commit-check.sh - Run this before committing to check architecture compliance
3
+
4
+ echo "๐Ÿ” Checking architecture patterns..."
5
+
6
+ # Check for forbidden SDK patterns
7
+ FORBIDDEN_PATTERNS=(
8
+ "func.*Client.*ListForm"
9
+ "func.*Client.*ListList"
10
+ "func.*Client.*GetSP"
11
+ "func.*Client.*ListSP"
12
+ )
13
+
14
+ ERRORS=0
15
+
16
+ for pattern in "${FORBIDDEN_PATTERNS[@]}"; do
17
+ matches=$(grep -r "$pattern" internal/sdk/*.go 2>/dev/null | grep -v "_test.go" | grep -v "^Binary")
18
+ if [ ! -z "$matches" ]; then
19
+ echo "โŒ Found forbidden SDK pattern: $pattern"
20
+ echo "$matches"
21
+ ERRORS=$((ERRORS + 1))
22
+ fi
23
+ done
24
+
25
+ # Check that commands don't import old SDK types
26
+ BAD_IMPORTS=(
27
+ "sdk.FormSection"
28
+ "sdk.FormElement"
29
+ "sdk.ListLayout"
30
+ "sdk.ListElement"
31
+ "sdk.SPPage"
32
+ "sdk.SPWidgetInstance"
33
+ )
34
+
35
+ for pattern in "${BAD_IMPORTS[@]}"; do
36
+ matches=$(grep -r "$pattern" internal/commands/**/*.go 2>/dev/null)
37
+ if [ ! -z "$matches" ]; then
38
+ echo "โŒ Command using SDK type instead of local type: $pattern"
39
+ echo "$matches"
40
+ ERRORS=$((ERRORS + 1))
41
+ fi
42
+ done
43
+
44
+ # Run architecture tests
45
+ echo "๐Ÿงช Running architecture tests..."
46
+ if ! go test ./internal/sdk -run TestNoSDKHelperMethods -run TestCommandsUseDirectSDKList > /dev/null 2>&1; then
47
+ echo "โŒ Architecture tests failed"
48
+ go test ./internal/sdk -v -run "TestNoSDKHelperMethods|TestCommandsUseDirectSDKList"
49
+ ERRORS=$((ERRORS + 1))
50
+ fi
51
+
52
+ if [ $ERRORS -eq 0 ]; then
53
+ echo "โœ… All architecture checks passed!"
54
+ exit 0
55
+ else
56
+ echo ""
57
+ echo "โš ๏ธ Architecture violations found!"
58
+ echo " Remember: Commands should call app.SDK.List() directly with local types."
59
+ echo " See internal/commands/dev/forms.go for the correct pattern."
60
+ exit 1
61
+ fi