@jetbrains/junie-cli 576.1.0 → 704.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": "576.1.0",
4
- "junieVersion": "576.1",
3
+ "version": "704.1.0",
4
+ "junieVersion": "704.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,7 @@ 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
9
 
10
10
  // Check for Windows early and exit gracefully
11
11
  if (os.platform() === 'win32') {
@@ -22,6 +22,13 @@ if (os.platform() === 'win32') {
22
22
  process.exit(0)
23
23
  }
24
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')
31
+
25
32
  const ARCH_MAP = {
26
33
  x64: 'amd64',
27
34
  amd64: 'amd64',
@@ -86,15 +93,68 @@ function chmodRecursive(dirPath) {
86
93
  }
87
94
  }
88
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
+
89
129
  async function downloadAndInstall() {
90
130
  const {arch, osName} = resolveTarget()
91
131
  const url = buildUrl({arch, osName})
92
- const workDir = path.resolve(__dirname, 'bin', 'junie')
93
- const markerFile = path.resolve(__dirname, 'bin', 'junie.download')
132
+ const version = require("./package.json").junieVersion
133
+ const targetDir = path.join(VERSIONS_DIR, version)
94
134
 
95
- fs.mkdirSync(workDir, {recursive: true})
135
+ // Create directories
136
+ fs.mkdirSync(JUNIE_BIN, {recursive: true})
137
+ fs.mkdirSync(targetDir, {recursive: true})
138
+ fs.mkdirSync(UPDATES_DIR, {recursive: true})
96
139
 
97
- const zipPath = path.join(workDir, 'junie.zip')
140
+ // Install shim
141
+ installShim()
142
+
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')
98
158
  console.log(`[Junie] Downloading from ${url}`)
99
159
 
100
160
  const res = await fetch(url)
@@ -102,31 +162,34 @@ async function downloadAndInstall() {
102
162
 
103
163
  await pipeline(res.body, fs.createWriteStream(zipPath))
104
164
 
105
- await fs.createReadStream(zipPath).pipe(unzipper.Extract({ path: workDir })).promise()
106
-
107
- chmodRecursive(workDir)
108
- stripQuarantine(workDir)
165
+ console.log('[Junie] Extracting...')
166
+ await fs.createReadStream(zipPath).pipe(unzipper.Extract({ path: targetDir })).promise()
109
167
 
110
- const binaryPath = getExpectedBinaryPath()
111
- // Re-assert main binary is executable (cheap, idempotent)
112
- try { fs.chmodSync(binaryPath, 0o755); } catch {}
168
+ chmodRecursive(targetDir)
169
+ stripQuarantine(targetDir)
113
170
 
114
171
  fs.rmSync(zipPath)
115
172
 
116
- 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)
117
178
 
118
- console.log('[Junie] Installation complete.')
179
+ console.log(`[Junie] Installed version ${version}`)
180
+ printPathInstructions()
119
181
 
120
- return binaryPath
182
+ return path.join(targetDir, 'junie')
121
183
  }
122
184
 
123
185
  async function main() {
124
186
  try {
125
187
  await downloadAndInstall()
188
+ console.log('[Junie] Installation complete.')
126
189
  } catch (error) {
127
190
  console.error('[Junie] Post-install error:', error)
128
191
  process.exit(1)
129
192
  }
130
193
  }
131
194
 
132
- 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 "$@"