1- import { ArrayStatefulParameter , getPty } from 'codify-plugin-lib' ;
1+ import { ArrayParameterSetting , ArrayStatefulParameter , getPty } from 'codify-plugin-lib' ;
2+ import fs from 'node:fs/promises' ;
3+ import semver from 'semver' ;
24
35import { SpawnStatus , codifySpawn } from '../../../utils/codify-spawn.js' ;
6+ import { FileUtils } from '../../../utils/file-utils.js' ;
47import { Utils } from '../../../utils/index.js' ;
58import { JenvConfig } from './jenv.js' ;
9+ import { nanoid } from 'nanoid' ;
610
711export const OPENJDK_SUPPORTED_VERSIONS = [ 8 , 11 , 17 , 21 , 22 ]
812export const JAVA_VERSION_INTEGER = / ^ \d + $ / ;
913
1014export class JenvAddParameter extends ArrayStatefulParameter < JenvConfig , string > {
11- override async refresh ( desired : null | string [ ] ) : Promise < null | string [ ] > {
15+ getSettings ( ) : ArrayParameterSetting {
16+ return {
17+ type : 'array' ,
18+ itemType : 'directory' ,
19+ isElementEqual : ( a , b ) => b . includes ( a ) ,
20+ transformation : {
21+ to : ( input : string [ ] ) =>
22+ input . map ( ( i ) => {
23+ if ( OPENJDK_SUPPORTED_VERSIONS . includes ( Number . parseInt ( i , 10 ) ) ) {
24+ return `/opt/homebrew/Cellar/openjdk@${ Number . parseInt ( i , 10 ) } `
25+ }
26+
27+ return i ;
28+ } ) ,
29+ from : ( output : string [ ] ) => output . map ( ( i ) => {
30+ if ( i . startsWith ( '/opt/homebrew/Cellar/openjdk@' ) ) {
31+ return i . split ( '/' ) . at ( 4 ) ?. split ( '@' ) . at ( 1 )
32+ }
33+
34+ return i ;
35+ } ) ,
36+ }
37+ }
38+ }
39+
40+ override async refresh ( params : string [ ] ) : Promise < null | string [ ] > {
1241 const $ = getPty ( ) ;
1342
14- const { data } = await $ . spawn ( 'jenv versions' )
15-
16- /** Example:
17- * system
18- * * 17 (set by /Users/kevinwang/.jenv/version)
19- * 17.0
20- * 17.0.11
21- * openjdk64-17.0.11
22- */
23- return [ ...new Set (
24- data
25- . split ( / \n / )
26- // Regex to split out the version part
27- . map ( ( v ) => this . getFirstRegexGroup ( / ^ [ * ] ( [ \d . A - Z a - z - ] + ) [ \\ n ] ? / g, v ) )
28- . filter ( Boolean ) as string [ ]
29- ) ]
30- }
43+ const { data : jenvRoot } = await $ . spawn ( 'jenv root' )
44+ const versions = ( await fs . readdir ( `${ jenvRoot } /versions` ) ) . filter ( ( v ) => v !== '.DS_store' ) ;
3145
32- override async addItem ( param : string ) : Promise < void > {
33-
34- const isHomebrewInstalled = await Utils . isHomebrewInstalled ( ) ;
35-
36- // Add special handling if the user specified an integer version. We add special functionality to automatically
37- // install java if a lts version is specified and homebrew is installed.
38- if ( JAVA_VERSION_INTEGER . test ( param ) ) {
39- if ( ! isHomebrewInstalled ) {
40- throw new Error ( 'Homebrew not detected. Cannot automatically install java version. Jenv does not automatically install' +
41- ' java versions, see the jenv docs: https://www.jenv.be. Please manually install a version of java and provide a path to the jenv resource' )
42- }
43-
44- const parsedVersion = Number . parseInt ( param , 10 ) ;
45- if ( ! OPENJDK_SUPPORTED_VERSIONS . includes ( parsedVersion ) ) {
46- throw new Error ( `Unsupported version of java specified. Only [${ OPENJDK_SUPPORTED_VERSIONS . join ( ', ' ) } ] is supported` )
47- }
46+ // We use a set because jenv sets an alias for 11.0.24, 11.0 and 11. We only care about the original location here
47+ const versionPaths = new Set (
48+ await Promise . all ( versions . map ( ( v ) =>
49+ fs . readlink ( `${ jenvRoot } /versions/${ v } ` )
50+ ) )
51+ )
4852
49- const openjdkName = ( parsedVersion === 22 ) ? 'openjdk' : `openjdk@${ param } ` ;
50- const { status } = await codifySpawn ( `brew list --formula -1 ${ openjdkName } ` , { throws : false } ) ;
53+ const installedVersions = ( await $ . spawn ( 'jenv versions --bare' ) )
54+ . data
55+ . split ( / \n / )
5156
52- // That version is not currently installed with homebrew. Let's install it
53- if ( status === SpawnStatus . ERROR ) {
54- console . log ( `Homebrew detected. Attempting to install java version ${ openjdkName } automatically using homebrew` )
55- await codifySpawn ( `brew install ${ openjdkName } ` )
56- }
57+ return [ ...versionPaths ]
58+ // Re-map the path back to what was provided in the config
59+ . map ( ( v ) => {
60+ const matched = params . find ( ( p ) => v . includes ( p ) ) ;
61+ return matched === undefined
62+ ? v
63+ : matched ;
64+ } )
65+ . filter ( ( v ) => {
66+ const versionStr = v . split ( '/' ) . at ( 4 ) ! . split ( '@' ) . at ( 1 ) ! ;
67+ return installedVersions . includes ( versionStr ) ;
68+ } ) ;
69+ }
5770
58- const location = await this . getHomebrewInstallLocation ( openjdkName ) ;
59- if ( ! location ) {
60- throw new Error ( 'Unable to determine location of jdk installed by homebrew. Please report this to the Codify team' ) ;
71+ override async addItem ( param : string ) : Promise < void > {
72+ let location = param ;
73+
74+ // Check if we should auto install it from homebrew first
75+ if ( param . startsWith ( '/opt/homebrew/Cellar/openjdk@' ) ) {
76+
77+ // Doesn't currently exist on the file system, let's parse and install from homebrew before adding
78+ if ( ! ( await FileUtils . exists ( param ) ) ) {
79+ const isHomebrewInstalled = await Utils . isHomebrewInstalled ( ) ;
80+ if ( ! isHomebrewInstalled ) {
81+ throw new Error ( 'Homebrew not detected. Cannot automatically install java version. Jenv does not automatically install' +
82+ ' java versions, see the jenv docs: https://www.jenv.be. Please manually install a version of java and provide a path to the jenv resource' )
83+ }
84+
85+ const versionStr = param . split ( '/' ) . at ( 4 ) ?. split ( '@' ) . at ( 1 ) ;
86+ if ( ! versionStr ) {
87+ throw new Error ( `jenv: malformed version str: ${ versionStr } ` )
88+ }
89+
90+ const parsedVersion = Number . parseInt ( versionStr , 10 )
91+ if ( ! OPENJDK_SUPPORTED_VERSIONS . includes ( parsedVersion ) ) {
92+ throw new Error ( `Unsupported version of java specified. Only [${ OPENJDK_SUPPORTED_VERSIONS . join ( ', ' ) } ] is supported` )
93+ }
94+
95+ const openjdkName = ( parsedVersion === 22 ) ? 'openjdk' : `openjdk@${ parsedVersion } ` ;
96+ const { status } = await codifySpawn ( `brew list --formula -1 ${ openjdkName } ` , { throws : false } ) ;
97+
98+ // That version is not currently installed with homebrew. Let's install it
99+ if ( status === SpawnStatus . ERROR ) {
100+ console . log ( `Homebrew detected. Attempting to install java version ${ openjdkName } automatically using homebrew` )
101+ await codifySpawn ( `brew install ${ openjdkName } ` )
102+ }
103+
104+ location = ( await this . getHomebrewInstallLocation ( openjdkName ) ) ! ;
105+ if ( ! location ) {
106+ throw new Error ( 'Unable to determine location of jdk installed by homebrew. Please report this to the Codify team' ) ;
107+ }
108+
109+ // Already exists on the file system let's re-map to the actual path
110+ } else if ( ! param . endsWith ( 'libexec/openjdk.jdk/Contents/Home' ) ) {
111+ const versions = ( await fs . readdir ( param ) ) . filter ( ( v ) => v !== '.DS_Store' )
112+ const sortedVersions = semver . sort ( versions ) ;
113+
114+ const latestVersion = sortedVersions . at ( - 1 ) ;
115+ location = `${ param } /${ latestVersion } /libexec/openjdk.jdk/Contents/Home`
61116 }
117+ }
62118
63- await codifySpawn ( `jenv add ${ location } ` )
119+ try {
120+ await codifySpawn ( `jenv add ${ location } ` , { throws : true } ) ;
121+ } catch ( error : Error ) {
122+ if ( error . message . includes ( 'jenv: cannot rehash' ) ) {
123+ await this . rehash ( ) ;
124+ return ;
125+ }
64126
65- return ;
127+ throw error ;
66128 }
67-
68- await codifySpawn ( `jenv add ${ param } ` ) ;
69129 }
70130
71131 override async removeItem ( param : string ) : Promise < void > {
72132 const isHomebrewInstalled = await Utils . isHomebrewInstalled ( ) ;
73133
74- if ( JAVA_VERSION_INTEGER . test ( param ) && isHomebrewInstalled ) {
75- const parsedVersion = Number . parseInt ( param , 10 ) ;
76- const openjdkName = ( parsedVersion === 22 ) ? 'openjdk' : `openjdk@${ param } ` ;
77-
134+ if ( isHomebrewInstalled && param . startsWith ( '/opt/homebrew/Cellar/openjdk@' ) ) {
135+ const versionStr = param . split ( '/' ) . at ( 4 ) ?. split ( '@' ) . at ( 1 ) ;
136+ if ( ! versionStr ) {
137+ throw new Error ( `jenv: malformed version str: ${ versionStr } ` )
138+ }
139+
140+ const parsedVersion = Number . parseInt ( versionStr , 10 )
141+ const openjdkName = ( parsedVersion === 22 ) ? 'openjdk' : `openjdk@${ parsedVersion } ` ;
142+
78143 const location = await this . getHomebrewInstallLocation ( openjdkName ) ;
79144 if ( location ) {
80145 await codifySpawn ( `jenv remove ${ location } ` )
81146 await codifySpawn ( `brew uninstall ${ openjdkName } ` )
82147 }
83-
148+
84149 return
85150 }
86151
87- await codifySpawn ( `jenv uninstall ${ param } ` ) ;
152+ await codifySpawn ( `jenv remove ${ param } ` ) ;
88153 }
89154
90- private async getHomebrewInstallLocation ( openjdkName : string ) : Promise < string | null > {
155+ private async getHomebrewInstallLocation ( openjdkName : string ) : Promise < null | string > {
91156 const { data : installInfo } = await codifySpawn ( `brew list --formula -1 ${ openjdkName } ` )
92157
93158 // Example: /opt/homebrew/Cellar/openjdk@17/17.0.11/libexec/
@@ -104,7 +169,17 @@ export class JenvAddParameter extends ArrayStatefulParameter<JenvConfig, string>
104169 return libexec + 'openjdk.jdk/Contents/Home' ;
105170 }
106171
107- private getFirstRegexGroup ( regexp : RegExp , str : string ) : null | string {
108- return Array . from ( str . matchAll ( regexp ) , m => m [ 1 ] ) ?. at ( 0 ) ?? null ;
172+ private async rehash ( ) : Promise < void > {
173+ const { data : output } = await codifySpawn ( 'jenv rehash' , { throws : false } )
174+
175+ if ( output . includes ( 'jenv: cannot rehash' ) ) {
176+ const existingShims = output . match ( / j e n v : c a n n o t r e h a s h : ( .* ) e x i s t s / ) ?. at ( 1 ) ;
177+ if ( ! existingShims ) {
178+ return ;
179+ }
180+
181+ await fs . rename ( existingShims , `${ existingShims } -${ nanoid ( 4 ) } ` ) ;
182+ await codifySpawn ( 'jenv rehash' , { throws : true } )
183+ }
109184 }
110185}
0 commit comments