@jetbrains/junie-cli 562.3.0 → 624.1.0

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/bin/index.js CHANGED
@@ -3,14 +3,13 @@ const {spawnSync} = require('child_process')
3
3
  const {getExecutable} = require('../getExecutable')
4
4
 
5
5
  function main() {
6
- try {
7
- const exe = getExecutable()
8
- const result = spawnSync(exe, process.argv.slice(2), {stdio: 'inherit'})
9
- process.exit(result.status ?? 0)
10
- } catch (error) {
11
- console.error('[Junie] Error:', error.message)
6
+ const exe = getExecutable()
7
+ if (!exe) {
12
8
  process.exit(1)
13
9
  }
10
+
11
+ const result = spawnSync(exe, process.argv.slice(2), {stdio: 'inherit'})
12
+ process.exit(result.status ?? 0)
14
13
  }
15
14
 
16
- main()
15
+ main()
package/getExecutable.js CHANGED
@@ -1,34 +1,21 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
- const { platform } = require("node:os")
4
-
5
- function getExpectedBinaryPath() {
6
- const workDir = path.resolve(__dirname, 'bin', 'junie')
7
-
8
- return platform() === 'darwin'
9
- ? path.join(workDir, 'Applications', 'junie.app', 'Contents', 'MacOS', 'junie')
10
- : path.join(workDir, 'junie', 'bin', 'junie')
11
- }
3
+ const os = require('os')
12
4
 
5
+ const JUNIE_BIN = path.join(os.homedir(), '.local', 'bin')
13
6
 
14
7
  function getExecutable() {
15
- const markerFile = path.resolve(__dirname, 'bin', 'junie.download')
16
-
17
- if (fs.existsSync(markerFile)) {
18
- const binaryPath = getExpectedBinaryPath()
8
+ const shimPath = path.join(JUNIE_BIN, 'junie')
19
9
 
20
- if (fs.existsSync(binaryPath)) {
21
- return binaryPath
22
- } else {
23
- console.error('Junie binary not found. Please re-install the package')
24
- }
25
- } else {
26
- console.error('Junie download is corrupted. Please re-install the package')
10
+ if (fs.existsSync(shimPath)) {
11
+ return shimPath
27
12
  }
28
- }
29
13
 
14
+ console.error('[Junie] Shim not found at', shimPath)
15
+ console.error('[Junie] Please reinstall: npm install @jetbrains/junie-cli')
16
+ return null
17
+ }
30
18
 
31
19
  module.exports = {
32
- getExpectedBinaryPath,
33
20
  getExecutable,
34
- };
21
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jetbrains/junie-cli",
3
- "version": "562.3.0",
4
- "junieVersion": "562.3",
3
+ "version": "624.1.0",
4
+ "junieVersion": "624.1",
5
5
  "description": "Junie command‑line client",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
7
7
  "repository": "https://github.com/jetbrains-junie/junie",
@@ -21,6 +21,7 @@
21
21
  },
22
22
  "files": [
23
23
  "bin/",
24
+ "shim/",
24
25
  "postinstall.js",
25
26
  "getExecutable.js"
26
27
  ],
package/postinstall.js CHANGED
@@ -5,7 +5,29 @@ const fs = require("fs")
5
5
  const { pipeline } = require("stream/promises")
6
6
  const unzipper = require('unzipper')
7
7
  const os = require("os")
8
- const { getExpectedBinaryPath } = require("./getExecutable")
8
+ const { execSync } = require("child_process")
9
+
10
+ // Check for Windows early and exit gracefully
11
+ if (os.platform() === 'win32') {
12
+ console.log('')
13
+ console.log('╭──────────────────────────────────────────────────────╮')
14
+ console.log('│ │')
15
+ console.log('│ Junie CLI does not support Windows yet. │')
16
+ console.log('│ Supported platforms: macOS, Linux │')
17
+ console.log('│ │')
18
+ console.log('│ Stay tuned for Windows support in future releases! │')
19
+ console.log('│ │')
20
+ console.log('╰──────────────────────────────────────────────────────╯')
21
+ console.log('')
22
+ process.exit(0)
23
+ }
24
+
25
+ // === Configuration ===
26
+ const JUNIE_BIN = path.join(os.homedir(), '.local', 'bin')
27
+ const JUNIE_DATA = path.join(os.homedir(), '.local', 'share', 'junie')
28
+ const VERSIONS_DIR = path.join(JUNIE_DATA, 'versions')
29
+ const UPDATES_DIR = path.join(JUNIE_DATA, 'updates')
30
+ const CURRENT_LINK = path.join(JUNIE_DATA, 'current')
9
31
 
10
32
  const ARCH_MAP = {
11
33
  x64: 'amd64',
@@ -71,15 +93,68 @@ function chmodRecursive(dirPath) {
71
93
  }
72
94
  }
73
95
 
96
+ function installShim() {
97
+ const shimPath = path.join(JUNIE_BIN, 'junie')
98
+ const shimSource = path.join(__dirname, 'shim', 'junie')
99
+
100
+ // Check if shim already exists
101
+ if (fs.existsSync(shimPath)) {
102
+ console.log('[Junie] Updating shim at', shimPath)
103
+ } else {
104
+ console.log('[Junie] Installing shim to', shimPath)
105
+ }
106
+
107
+ // Copy shim.sh to ~/.local/bin/junie
108
+ const shimContent = fs.readFileSync(shimSource, 'utf8')
109
+ fs.writeFileSync(shimPath, shimContent, { mode: 0o755 })
110
+ }
111
+
112
+ function printPathInstructions() {
113
+ const currentPath = process.env.PATH || ''
114
+ if (!currentPath.includes(JUNIE_BIN)) {
115
+ console.log('')
116
+ console.log('╭──────────────────────────────────────────────────────╮')
117
+ console.log('│ │')
118
+ console.log('│ Add ~/.local/bin to your PATH to use junie directly │')
119
+ console.log('│ │')
120
+ console.log('│ Run: export PATH="$HOME/.local/bin:$PATH" │')
121
+ console.log('│ │')
122
+ console.log('│ Or add to your shell profile (~/.bashrc, ~/.zshrc) │')
123
+ console.log('│ │')
124
+ console.log('╰──────────────────────────────────────────────────────╯')
125
+ console.log('')
126
+ }
127
+ }
128
+
74
129
  async function downloadAndInstall() {
75
130
  const {arch, osName} = resolveTarget()
76
131
  const url = buildUrl({arch, osName})
77
- const workDir = path.resolve(__dirname, 'bin', 'junie')
78
- const markerFile = path.resolve(__dirname, 'bin', 'junie.download')
132
+ const version = require("./package.json").junieVersion
133
+ const targetDir = path.join(VERSIONS_DIR, version)
134
+
135
+ // Create directories
136
+ fs.mkdirSync(JUNIE_BIN, {recursive: true})
137
+ fs.mkdirSync(targetDir, {recursive: true})
138
+ fs.mkdirSync(UPDATES_DIR, {recursive: true})
79
139
 
80
- fs.mkdirSync(workDir, {recursive: true})
140
+ // Install shim
141
+ installShim()
81
142
 
82
- const zipPath = path.join(workDir, 'junie.zip')
143
+ // Check if version already installed
144
+ if (fs.existsSync(CURRENT_LINK)) {
145
+ try {
146
+ const currentVersion = path.basename(fs.readlinkSync(CURRENT_LINK))
147
+ if (currentVersion === version && fs.readdirSync(targetDir).length > 0) {
148
+ console.log(`[Junie] Version ${version} already installed`)
149
+ printPathInstructions()
150
+ return path.join(targetDir, 'junie')
151
+ }
152
+ } catch {
153
+ // Continue with installation
154
+ }
155
+ }
156
+
157
+ const zipPath = path.join(targetDir, 'junie.zip')
83
158
  console.log(`[Junie] Downloading from ${url}`)
84
159
 
85
160
  const res = await fetch(url)
@@ -87,31 +162,34 @@ async function downloadAndInstall() {
87
162
 
88
163
  await pipeline(res.body, fs.createWriteStream(zipPath))
89
164
 
90
- await fs.createReadStream(zipPath).pipe(unzipper.Extract({ path: workDir })).promise()
91
-
92
- chmodRecursive(workDir)
93
- stripQuarantine(workDir)
165
+ console.log('[Junie] Extracting...')
166
+ await fs.createReadStream(zipPath).pipe(unzipper.Extract({ path: targetDir })).promise()
94
167
 
95
- const binaryPath = getExpectedBinaryPath()
96
- // Re-assert main binary is executable (cheap, idempotent)
97
- try { fs.chmodSync(binaryPath, 0o755); } catch {}
168
+ chmodRecursive(targetDir)
169
+ stripQuarantine(targetDir)
98
170
 
99
171
  fs.rmSync(zipPath)
100
172
 
101
- fs.writeFileSync(markerFile, binaryPath, 'utf8')
173
+ // Update current symlink atomically
174
+ const tempLink = path.join(JUNIE_DATA, 'current.tmp')
175
+ try { fs.unlinkSync(tempLink) } catch {}
176
+ fs.symlinkSync(targetDir, tempLink)
177
+ fs.renameSync(tempLink, CURRENT_LINK)
102
178
 
103
- console.log('[Junie] Installation complete.')
179
+ console.log(`[Junie] Installed version ${version}`)
180
+ printPathInstructions()
104
181
 
105
- return binaryPath
182
+ return path.join(targetDir, 'junie')
106
183
  }
107
184
 
108
185
  async function main() {
109
186
  try {
110
187
  await downloadAndInstall()
188
+ console.log('[Junie] Installation complete.')
111
189
  } catch (error) {
112
190
  console.error('[Junie] Post-install error:', error)
113
191
  process.exit(1)
114
192
  }
115
193
  }
116
194
 
117
- main()
195
+ main()
package/shim/junie ADDED
@@ -0,0 +1,295 @@
1
+ #!/bin/bash
2
+ #
3
+ # Junie CLI Shim
4
+ #
5
+ # This script is the entry point for Junie CLI. It handles:
6
+ # 1. Applying pending updates before launching
7
+ # 2. Version selection via JUNIE_VERSION env or --use-version flag
8
+ # 3. Executing the appropriate version binary
9
+ #
10
+ # Installation locations:
11
+ # Shim: ~/.local/bin/junie
12
+ # Data: ~/.local/share/junie/
13
+ # Versions: ~/.local/share/junie/versions/<version>/junie
14
+ # Updates: ~/.local/share/junie/updates/
15
+
16
+ set -euo pipefail
17
+
18
+ # === Configuration ===
19
+ JUNIE_DATA="${JUNIE_DATA:-$HOME/.local/share/junie}"
20
+ VERSIONS_DIR="$JUNIE_DATA/versions"
21
+ UPDATES_DIR="$JUNIE_DATA/updates"
22
+ CURRENT_LINK="$JUNIE_DATA/current"
23
+ PENDING_UPDATE="$UPDATES_DIR/pending-update.json"
24
+
25
+ # === Utility Functions ===
26
+
27
+ # Log message to stderr
28
+ log() {
29
+ echo "[Junie] $*" >&2
30
+ }
31
+
32
+ # Check if a command exists
33
+ has_command() {
34
+ command -v "$1" > /dev/null 2>&1
35
+ }
36
+
37
+ # Parse JSON field (basic, works without jq)
38
+ # Usage: parse_json "field" < file.json
39
+ parse_json_field() {
40
+ local field="$1"
41
+ # Extract value for "field": "value" or "field": number
42
+ grep -o "\"$field\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:[[:space:]]*"\([^"]*\)"/\1/' || true
43
+ }
44
+
45
+ # Parse JSON field with jq if available, fallback to grep
46
+ get_json_field() {
47
+ local file="$1"
48
+ local field="$2"
49
+
50
+ if has_command jq; then
51
+ jq -r ".$field // empty" "$file" 2>/dev/null || true
52
+ else
53
+ parse_json_field "$field" < "$file"
54
+ fi
55
+ }
56
+
57
+ # Calculate SHA-256 checksum
58
+ sha256sum_file() {
59
+ local file="$1"
60
+ if has_command shasum; then
61
+ shasum -a 256 "$file" | cut -d' ' -f1
62
+ elif has_command sha256sum; then
63
+ sha256sum "$file" | cut -d' ' -f1
64
+ else
65
+ log "Warning: No SHA-256 tool available, skipping checksum verification"
66
+ echo ""
67
+ fi
68
+ }
69
+
70
+ # Get binary path for a given version
71
+ # Handles different package structures (macOS app bundle, Linux, direct binary)
72
+ get_binary_path() {
73
+ local version="$1"
74
+ local version_dir="$VERSIONS_DIR/$version"
75
+
76
+ # macOS: look for .app bundle
77
+ if [[ -d "$version_dir/Applications/junie.app" ]]; then
78
+ echo "$version_dir/Applications/junie.app/Contents/MacOS/junie"
79
+ # Linux: look for junie/bin/junie
80
+ elif [[ -f "$version_dir/junie/bin/junie" ]]; then
81
+ echo "$version_dir/junie/bin/junie"
82
+ # Fallback: direct junie binary
83
+ elif [[ -f "$version_dir/junie" ]]; then
84
+ echo "$version_dir/junie"
85
+ else
86
+ echo ""
87
+ fi
88
+ }
89
+
90
+ # === Apply Pending Update ===
91
+ apply_pending_update() {
92
+ if [[ ! -f "$PENDING_UPDATE" ]]; then
93
+ return 0
94
+ fi
95
+
96
+ log "Applying pending update..."
97
+
98
+ # Parse manifest
99
+ local version zip_path sha256
100
+ version=$(get_json_field "$PENDING_UPDATE" "version")
101
+ zip_path=$(get_json_field "$PENDING_UPDATE" "zipPath")
102
+ sha256=$(get_json_field "$PENDING_UPDATE" "sha256")
103
+
104
+ if [[ -z "$version" || -z "$zip_path" ]]; then
105
+ log "Invalid pending update manifest, skipping"
106
+ rm -f "$PENDING_UPDATE"
107
+ return 1
108
+ fi
109
+
110
+ if [[ ! -f "$zip_path" ]]; then
111
+ log "Update file not found: $zip_path"
112
+ rm -f "$PENDING_UPDATE"
113
+ return 1
114
+ fi
115
+
116
+ # Verify checksum if available
117
+ if [[ -n "$sha256" ]]; then
118
+ local actual_sha256
119
+ actual_sha256=$(sha256sum_file "$zip_path")
120
+
121
+ # Case-insensitive comparison (compatible with bash 3.x on macOS)
122
+ if [[ -n "$actual_sha256" ]] && ! echo "$actual_sha256" | grep -qi "^${sha256}$"; then
123
+ log "Checksum mismatch, skipping update"
124
+ log "Expected: $sha256"
125
+ log "Got: $actual_sha256"
126
+ rm -f "$PENDING_UPDATE" "$zip_path"
127
+ return 1
128
+ fi
129
+ fi
130
+
131
+ # Extract to versions directory
132
+ local target_dir="$VERSIONS_DIR/$version"
133
+ mkdir -p "$target_dir"
134
+
135
+ log "Extracting to $target_dir..."
136
+
137
+ if has_command unzip; then
138
+ unzip -q -o "$zip_path" -d "$target_dir"
139
+ elif has_command tar; then
140
+ # Fallback for .tar.gz files
141
+ tar -xzf "$zip_path" -C "$target_dir"
142
+ else
143
+ log "Error: No extraction tool available (unzip or tar)"
144
+ return 1
145
+ fi
146
+
147
+ # Make binary executable
148
+ chmod +x "$target_dir/junie" 2>/dev/null || true
149
+
150
+ # Remove quarantine on macOS
151
+ xattr -dr com.apple.quarantine "$target_dir" 2>/dev/null || true
152
+
153
+ # Update current symlink atomically
154
+ ln -sfn "$target_dir" "$CURRENT_LINK"
155
+
156
+ # Cleanup
157
+ rm -f "$zip_path" "$PENDING_UPDATE"
158
+
159
+ log "Updated to version $version"
160
+ }
161
+
162
+ # === Resolve Version ===
163
+ resolve_version() {
164
+ local version=""
165
+
166
+ # Priority 1: --use-version flag
167
+ for arg in "$@"; do
168
+ case "$arg" in
169
+ --use-version=*)
170
+ version="${arg#--use-version=}"
171
+ break
172
+ ;;
173
+ esac
174
+ done
175
+
176
+ # Priority 2: JUNIE_VERSION environment variable
177
+ if [[ -z "$version" && -n "${JUNIE_VERSION:-}" ]]; then
178
+ version="$JUNIE_VERSION"
179
+ fi
180
+
181
+ # Priority 3: current symlink
182
+ if [[ -z "$version" ]]; then
183
+ if [[ -L "$CURRENT_LINK" ]]; then
184
+ version=$(basename "$(readlink "$CURRENT_LINK")")
185
+ elif [[ -d "$CURRENT_LINK" ]]; then
186
+ # current might be a directory in some setups
187
+ version=$(basename "$CURRENT_LINK")
188
+ fi
189
+ fi
190
+
191
+ if [[ -z "$version" ]]; then
192
+ log "Error: No version found. Please reinstall Junie."
193
+ log "Run: curl -fsSL https://junie.jetbrains.com/install.sh | bash"
194
+ exit 1
195
+ fi
196
+
197
+ # Verify version exists
198
+ if [[ ! -d "$VERSIONS_DIR/$version" ]]; then
199
+ log "Error: Version $version not found in $VERSIONS_DIR"
200
+ log "Available versions:"
201
+ ls -1 "$VERSIONS_DIR" 2>/dev/null || log " (none)"
202
+ exit 1
203
+ fi
204
+
205
+ echo "$version"
206
+ }
207
+
208
+ # === Filter Shim-Specific Arguments ===
209
+ # Global array to store filtered args (needed because bash can't return arrays)
210
+ FILTERED_ARGS=()
211
+
212
+ filter_args() {
213
+ FILTERED_ARGS=()
214
+ for arg in "$@"; do
215
+ case "$arg" in
216
+ --use-version=*) ;; # Skip shim-specific flag
217
+ *) FILTERED_ARGS+=("$arg") ;;
218
+ esac
219
+ done
220
+ }
221
+
222
+ # === Handle Shim Commands ===
223
+ handle_shim_commands() {
224
+ case "${1:-}" in
225
+ --shim-version)
226
+ echo "junie-shim 1.0.0"
227
+ exit 0
228
+ ;;
229
+ --list-versions)
230
+ echo "Installed versions:"
231
+ if [[ -d "$VERSIONS_DIR" ]]; then
232
+ local current_version=""
233
+ if [[ -L "$CURRENT_LINK" ]]; then
234
+ current_version=$(basename "$(readlink "$CURRENT_LINK")")
235
+ fi
236
+ for v in "$VERSIONS_DIR"/*/; do
237
+ local vname=$(basename "$v")
238
+ if [[ "$vname" == "$current_version" ]]; then
239
+ echo " $vname (current)"
240
+ else
241
+ echo " $vname"
242
+ fi
243
+ done
244
+ else
245
+ echo " (none)"
246
+ fi
247
+ exit 0
248
+ ;;
249
+ --switch-version=*)
250
+ local new_version="${1#--switch-version=}"
251
+ if [[ ! -d "$VERSIONS_DIR/$new_version" ]]; then
252
+ log "Error: Version $new_version not found"
253
+ exit 1
254
+ fi
255
+ ln -sfn "$VERSIONS_DIR/$new_version" "$CURRENT_LINK"
256
+ log "Switched to version $new_version"
257
+ exit 0
258
+ ;;
259
+ esac
260
+ }
261
+
262
+ # === Main ===
263
+ main() {
264
+ # Handle shim-specific commands
265
+ handle_shim_commands "$@"
266
+
267
+ # Apply pending update if exists
268
+ apply_pending_update || true
269
+
270
+ # Resolve which version to run
271
+ local version
272
+ version=$(resolve_version "$@")
273
+
274
+ # Get binary path (handles macOS app bundle, Linux, direct binary)
275
+ local binary
276
+ binary=$(get_binary_path "$version")
277
+
278
+ if [[ -z "$binary" || ! -x "$binary" ]]; then
279
+ log "Error: Binary not found or not executable for version $version"
280
+ log "Looked in: $VERSIONS_DIR/$version"
281
+ exit 1
282
+ fi
283
+
284
+ # Set required environment variable for Junie
285
+ export EJ_RUNNER_PWD="${EJ_RUNNER_PWD:-$(pwd)}"
286
+
287
+ # Set JUNIE_DATA for the app to know where data is stored
288
+ export JUNIE_DATA="$JUNIE_DATA"
289
+
290
+ # Filter out shim-specific args and execute
291
+ filter_args "$@"
292
+ exec "$binary" ${FILTERED_ARGS[@]+"${FILTERED_ARGS[@]}"}
293
+ }
294
+
295
+ main "$@"