diff --git a/linux-features/open-target-discovery/patch.js b/linux-features/open-target-discovery/patch.js index 28321b11..b7b7432e 100644 --- a/linux-features/open-target-discovery/patch.js +++ b/linux-features/open-target-discovery/patch.js @@ -188,15 +188,30 @@ function insertTerminalHelpers(currentSource, { fsVar, pathVar }) { const helpers = `function codexLinuxTerminalCommand(){for(let e of [\`x-terminal-emulator\`,\`gnome-terminal\`,\`kgx\`,\`konsole\`,\`xfce4-terminal\`,\`mate-terminal\`,\`lxterminal\`,\`tilix\`,\`alacritty\`,\`kitty\`,\`ghostty\`,\`wezterm\`,\`foot\`,\`terminology\`,\`xterm\`]){let t=codexLinuxFindExecutable(e);if(t)return t}return null}` + - `function codexLinuxTerminalSplitDesktopExec(e){let t=[],n=\`\`,r=null,i=!1;for(let a=0;a0?process.env.XDG_DATA_DIRS:\`/usr/local/share:/usr/share\`).split(\`:\`).filter(Boolean),r=[...t,...n,(0,${pathVar}.join)(e,\`.local/share/flatpak/exports/share\`),\`/var/lib/flatpak/exports/share\`,\`/var/lib/snapd/desktop\`],a=new Set;return r.map(e=>(0,${pathVar}.join)(e,\`applications\`)).filter(e=>e&&${pathVar}.isAbsolute(e)&&!a.has(e)&&(a.add(e),!0))}` + - `function codexLinuxTerminalDesktopEntryFiles(e,t=0){let n=[];if(t>4)return n;try{for(let r of (0,${fsVar}.readdirSync)(e,{withFileTypes:!0})){let a=(0,${pathVar}.join)(e,r.name);r.isDirectory()?n.push(...codexLinuxTerminalDesktopEntryFiles(a,t+1)):r.isFile()&&r.name.endsWith(\`.desktop\`)&&n.push(a)}}catch{}return n}` + + `function codexLinuxTerminalDesktopEntryFiles(e,t=0){let n=[];if(t>4)return n;try{for(let r of (0,${fsVar}.readdirSync)(e,{withFileTypes:!0})){let a=(0,${pathVar}.join)(e,r.name);r.isDirectory()?n.push(...codexLinuxTerminalDesktopEntryFiles(a,t+1)):(r.isFile()||r.isSymbolicLink())&&r.name.endsWith(\`.desktop\`)&&n.push(a)}}catch{}return n}` + `function codexLinuxParseTerminalDesktopEntry(e){let t={Id:(0,${pathVar}.basename)(e).replace(/\\.desktop$/u,\`\`)},n=\`\`;try{for(let r of (0,${fsVar}.readFileSync)(e,\`utf8\`).split(/\\r?\\n/u)){let e=r.trim();if(!e||e.startsWith(\`#\`))continue;if(e.startsWith(\`[\`)&&e.endsWith(\`]\`)){n=e.slice(1,-1);continue}if(n&&n!==\`Desktop Entry\`)continue;let i=e.indexOf(\`=\`);if(i<1)continue;let a=e.slice(0,i).replace(/\\[.*\\]$/u,\`\`),o=e.slice(i+1);t[a]??=o}}catch{return null}let r=e=>(e||\`\`).trim().toLowerCase()===\`true\`;return(t.Type&&t.Type!==\`Application\`)||r(t.NoDisplay)||r(t.Hidden)||!t.Exec||!t.Name?null:t}` + `function codexLinuxLooksLikeTerminal(e){let t=(e.Categories||\`\`).toLowerCase(),n=[e.Name,e.GenericName,e.Comment,e.Keywords,e.Exec,e.Id].filter(Boolean).join(\` \`).toLowerCase();return/(^|;)terminalemulator(;|$)/u.test(t)||/\\b(terminal|console|shell|pty|ghostty|wezterm|konsole|alacritty|kitty|foot|xterm)\\b/u.test(n)}` + `function codexLinuxTerminalExecutablePath(e){if(!e)return null;if(!(0,${pathVar}.isAbsolute)(e))return codexLinuxFindExecutable(e);try{if((0,${fsVar}.existsSync)(e)){let t=(0,${fsVar}.statSync)(e);if(t.isFile())try{(0,${fsVar}.accessSync)(e,${fsVar}.constants.X_OK);return e}catch{}}}catch{}return null}` + `function codexLinuxTerminalCleanDesktopArgs(e){return e.map(e=>e.replace(/%%/gu,\`%\`)).filter(e=>!/^%[fFuUdDnNickvm]$/u.test(e))}` + - `function codexLinuxResolveTerminalDesktopExec(e){let t=codexLinuxTerminalSplitDesktopExec(e);if(t.length===0)return null;for(;;){if(t[0]===\`env\`){t.shift();continue}if(t[0]&&/^[A-Za-z_][A-Za-z0-9_]*=/u.test(t[0])){t.shift();continue}if(t[0]===\`-u\`||t[0]===\`--unset\`){t.splice(0,2);continue}break}let n=t.shift();if(!n)return null;let r=codexLinuxTerminalExecutablePath(n);return r?{command:r,args:codexLinuxTerminalCleanDesktopArgs(t),base:(0,${pathVar}.basename)(n).replace(/\\.(sh|bin)$/u,\`\`).toLowerCase()}:null}` + - `function codexLinuxDiscoveredTerminalInfo(){for(let e of codexLinuxTerminalDesktopDirs())for(let t of codexLinuxTerminalDesktopEntryFiles(e)){let e=codexLinuxParseTerminalDesktopEntry(t);if(!e||!codexLinuxLooksLikeTerminal(e))continue;if(e.TryExec&&!codexLinuxTerminalExecutablePath(codexLinuxTerminalSplitDesktopExec(e.TryExec)[0]))continue;let n=codexLinuxResolveTerminalDesktopExec(e.Exec);if(!n)continue;return{command:n.command,args:n.args,dirArg:e[\`X-TerminalArgDir\`]||null}}return null}` + + `function codexLinuxResolveTerminalDesktopExec(e){let t=codexLinuxTerminalSplitDesktopExec(e);if(t.length===0)return null;for(;;){if((0,${pathVar}.basename)(t[0]||\`\`)===\`env\`){t.shift();continue}if(t[0]&&/^[A-Za-z_][A-Za-z0-9_]*=/u.test(t[0])){t.shift();continue}if(t[0]===\`-u\`||t[0]===\`--unset\`){t.splice(0,2);continue}break}let n=t.shift();if(!n)return null;let r=codexLinuxTerminalExecutablePath(n);return r?{command:r,args:codexLinuxTerminalCleanDesktopArgs(t),base:(0,${pathVar}.basename)(n).replace(/\\.(sh|bin)$/u,\`\`).toLowerCase()}:null}` + + `function codexLinuxTerminalShellArg(e){if(!e)return\`\`;let t=e[0],n=e[e.length-1],r=(t===\`"\`||t===\`'\`)&&n===t?t:null;r&&(e=e.slice(1,-1));let i=process.env.HOME||\`\`;r==null&&(e=e.replace(/^~(?=\\/|$)/u,i).replace(/\\\\([^$])/gu,\`$1\`));r!==\`'\`&&(e=e.replace(/\\$\\{HOME\\}|\\$HOME/gu,i));return e}` + + `function codexLinuxTerminalFlatpakRoots(){let e=process.env.HOME||\`/nonexistent\`,t=[{root:(0,${pathVar}.join)(e,\`.local/share/flatpak\`),scope:\`user\`},{root:\`/var/lib/flatpak\`,scope:\`system\`}];try{for(let e of (0,${fsVar}.readdirSync)(\`/etc/flatpak/installations.d\`)){try{let n=(0,${fsVar}.readFileSync)((0,${pathVar}.join)(\`/etc/flatpak/installations.d\`,e),\`utf8\`).match(/^\\s*Path\\s*=\\s*(.+?)\\s*$/mu)?.[1]?.replace(/^["']|["']$/gu,\`\`);n&&(0,${pathVar}.isAbsolute)(n)&&t.push({root:n,scope:\`installation\`,name:e.replace(/\\.conf$/u,\`\`)})}catch{}}}catch{}return t}` + + `function codexLinuxTerminalFlatpakAvailable(e,t){for(let n of codexLinuxTerminalFlatpakRoots())try{if(t?.scope&&n.scope!==t.scope)continue;if(t?.name&&n.name!==t.name)continue;if((0,${fsVar}.existsSync)((0,${pathVar}.join)(n.root,\`app\`,e)))return!0}catch{}return!1}` + + `function codexLinuxTerminalShellProbeTail(e){return/^(?:\\s*(?:(?:\\d*>>?|\\d*<|&>)\\s*\\S+|\\d*>&\\d+))*\\s*$/u.test(e)}` + + `function codexLinuxTerminalFlatpakScope(e){let t=e.match(/(?:^|\\s)--installation(?:=((?:"[^"]*"|'[^']*'|[^\\s;&|()<>]+))|\\s+((?:"[^"]*"|'[^']*'|[^\\s;&|()<>]+)))/u);return t?{scope:\`installation\`,name:codexLinuxTerminalShellArg(t[1]||t[2])}:/(?:^|\\s)--user(?:\\s|$)/u.test(e)?{scope:\`user\`}:/(?:^|\\s)--system(?:\\s|$)/u.test(e)?{scope:\`system\`}:null}` + + `function codexLinuxTerminalFlatpakInfoAvailable(e){let t=e.match(/^\\s*((?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+))\\s+(?:(?:--(?:system|user|show-location)|--installation(?:=(?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+)|\\s+(?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+))?)\\s+)*info\\s+(?:(?:--(?:system|user|show-location)|--installation(?:=(?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+)|\\s+(?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+))?)\\s+)*((?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+))/u);if(!t)return null;let n=codexLinuxTerminalShellArg(t[1]);return(0,${pathVar}.basename)(n)!==\`flatpak\`?null:codexLinuxTerminalShellProbeTail(e.slice(t[0].length))?codexLinuxTerminalExecutablePath(n)!=null&&codexLinuxTerminalFlatpakAvailable(codexLinuxTerminalShellArg(t[2]),codexLinuxTerminalFlatpakScope(t[0])):!1}` + + `function codexLinuxTerminalFlatpakInfoTokensAvailable(e){let t=0;for(;;){let n=e[t];if((0,${pathVar}.basename)(n||\`\`)===\`env\`){if(codexLinuxTerminalExecutablePath(n)==null)return!1;t++;continue}if(n&&/^[A-Za-z_][A-Za-z0-9_]*=/u.test(n)){t++;continue}if(n===\`-i\`||n===\`--ignore-environment\`||n?.startsWith(\`--unset=\`)){t++;continue}if(n===\`-u\`||n===\`--unset\`){t+=2;continue}break}if((0,${pathVar}.basename)(e[t]||\`\`)!==\`flatpak\`)return null;if(codexLinuxTerminalExecutablePath(e[t])==null)return!1;let n=t+1,r=null,a=e=>e===\`--user\`?{scope:\`user\`}:e===\`--system\`?{scope:\`system\`}:null;for(;;){let t=e[n];if(t===\`--user\`||t===\`--system\`){r=a(t),n++;continue}if(t?.startsWith(\`--installation=\`)){r={scope:\`installation\`,name:t.slice(15)},n++;continue}if(t===\`--installation\`){r={scope:\`installation\`,name:e[n+1]},n+=2;continue}if(t===\`--verbose\`){n++;continue}if(t?.startsWith(\`-\`))return!1;break}if(e[n]!==\`info\`)return null;n++;for(;n])+))/u);if(n&&codexLinuxTerminalShellProbeTail(e.slice(n[0].length)))return n[1]===\`which\`&&codexLinuxTerminalExecutablePath(\`which\`)==null||n[1]===\`hash\`&&t===\`fish\`?!1:codexLinuxTerminalExecutablePath(codexLinuxTerminalShellArg(n[2]))!=null;n=e.match(/^\\s*(test\\s+-x|\\[\\s+-x|\\[\\[\\s+-x)\\s+((?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()\\]])+))/u);if(n&&(!n[1].startsWith(\`[[\`)||[\`bash\`,\`zsh\`].includes(t))&&codexLinuxTerminalShellProbeTail(e.slice(n[0].length).replace(/^\\s*\\]{1,2}/u,\`\`)))return codexLinuxTerminalExecutablePath(codexLinuxTerminalShellArg(n[2]))!=null;return codexLinuxTerminalFlatpakInfoAvailable(e)}` + + `function codexLinuxTerminalShellSegments(e){let t=[],n=\`\`,r=null,i=!1;for(let a=0;a0?codexLinuxTerminalFlatpakTryExecShellAvailable(_codexArgs[_codexShellIndex]??\`\`):!1}` + + `function codexLinuxTerminalTryExecAvailable(e){let t=codexLinuxTerminalSplitDesktopExec(e),n=!1,skipControls=new Set([\`&&\`,\`||\`,\`;\`,\`|\`,\`!\`,\`then\`,\`fi\`,\`do\`,\`done\`]),skipCommands=new Set([\`env\`,\`test\`,\`[\`,\`]\`,\`exec\`,\`command\`,\`which\`,\`type\`,\`hash\`]),shells=new Set([\`sh\`,\`bash\`,\`dash\`,\`zsh\`,\`fish\`]),q=codexLinuxTerminalFlatpakInfoAvailable(e);if(q!=null)return q;q=codexLinuxTerminalFlatpakInfoTokensAvailable(t);if(q!=null)return q;let r=codexLinuxTerminalFlatpakShellAvailable(t,shells);if(r!=null)return r;for(var f=0;;){let e=t[f];if((0,${pathVar}.basename)(e||\`\`)===\`env\`||e&&/^[A-Za-z_][A-Za-z0-9_]*=/u.test(e)||e===\`-i\`||e===\`--ignore-environment\`||e?.startsWith(\`--unset=\`)){f++;continue}if(e===\`-u\`||e===\`--unset\`){f+=2;continue}break}if((0,${pathVar}.basename)(t[f]||\`\`)===\`flatpak\`&&f===t.length-1)return codexLinuxTerminalExecutablePath(t[f])!=null;for(let e=0;e0)return a&&codexLinuxTerminalExecutablePath(t[e])?codexLinuxTerminalTryExecShellAvailable(t[e],t[e+l-1],t[e+l]??\`\`):!1}}for(let e=0;ei,args:r}:void 0}`; const dynamicDiscoveryHelpers = patchedSource.includes("function codexLinuxDiscoveredIdeTargets(") ? "" - : `function codexLinuxSplitDesktopExec(e){let t=[],n=\`\`,r=null,i=!1;for(let a=0;a0?process.env.XDG_DATA_DIRS:\`/usr/local/share:/usr/share\`).split(\`:\`).filter(Boolean),r=[...t,...n,(0,${pathVar}.join)(e,\`.local/share/flatpak/exports/share\`),\`/var/lib/flatpak/exports/share\`,\`/var/lib/snapd/desktop\`],a=new Set;return r.map(e=>(0,${pathVar}.join)(e,\`applications\`)).filter(e=>e&&(0,${pathVar}.isAbsolute)(e)&&!a.has(e)&&(a.add(e),!0))}` + - `function codexLinuxDesktopEntryFiles(e,t=0){let n=[];if(t>4)return n;try{for(let r of (0,${fsVar}.readdirSync)(e,{withFileTypes:!0})){let a=(0,${pathVar}.join)(e,r.name);r.isDirectory()?n.push(...codexLinuxDesktopEntryFiles(a,t+1)):r.isFile()&&r.name.endsWith(\`.desktop\`)&&n.push(a)}}catch{}return n}` + + `function codexLinuxDesktopEntryFiles(e,t=0){let n=[];if(t>4)return n;try{for(let r of (0,${fsVar}.readdirSync)(e,{withFileTypes:!0})){let a=(0,${pathVar}.join)(e,r.name);r.isDirectory()?n.push(...codexLinuxDesktopEntryFiles(a,t+1)):(r.isFile()||r.isSymbolicLink())&&r.name.endsWith(\`.desktop\`)&&n.push(a)}}catch{}return n}` + `function codexLinuxParseDesktopEntry(e){let t={Id:(0,${pathVar}.basename)(e).replace(/\\.desktop$/u,\`\`)},n=\`\`;try{for(let r of (0,${fsVar}.readFileSync)(e,\`utf8\`).split(/\\r?\\n/u)){let e=r.trim();if(!e||e.startsWith(\`#\`))continue;if(e.startsWith(\`[\`)&&e.endsWith(\`]\`)){n=e.slice(1,-1);continue}if(n&&n!==\`Desktop Entry\`)continue;let i=e.indexOf(\`=\`);if(i<1)continue;let a=e.slice(0,i).replace(/\\[.*\\]$/u,\`\`),o=e.slice(i+1);t[a]??=o}}catch{return null}let r=e=>(e||\`\`).trim().toLowerCase()===\`true\`;return(t.Type&&t.Type!==\`Application\`)||r(t.NoDisplay)||r(t.Terminal)||!r(t.Hidden)&&(!t.Exec||!t.Name)?null:t}` + `function codexLinuxLooksLikeIde(e){let t=\`;\${(e.Categories||\`\`).toLowerCase()};\`,n=[e.Name,e.GenericName,e.Comment,e.Keywords,e.Exec].filter(Boolean).join(\` \`).toLowerCase(),r=/(;)(office|wordprocessor|spreadsheet|presentation|graphics|audiovideo|audiovideoediting|building)(;)/u.test(t),i=/;ide;/u.test(t);if(r&&!i)return!1;if(i)return!0;return/(;)(development|texteditor)(;)/u.test(t)&&/\\b(code|coding|codium|cursor|zed|ide|jetbrains|sublime|emacs|vim|neovim|neovide|kate|builder|windsurf|antigravity|rstudio|positron|agent|agents|agentic|workspace|workspaces)\\b/u.test(n)}` + `function codexLinuxExecutablePath(e){if(!e)return null;if(!(0,${pathVar}.isAbsolute)(e))return codexLinuxFindExecutable(e);try{if((0,${fsVar}.existsSync)(e)){let t=(0,${fsVar}.statSync)(e);if(t.isFile())try{(0,${fsVar}.accessSync)(e,${fsVar}.constants.X_OK);return e}catch{}}}catch{}return null}` + - `function codexLinuxResolveDesktopExec(e){let t=codexLinuxSplitDesktopExec(e);if(t.length===0)return null;for(;;){if(t[0]===\`env\`){t.shift();continue}if(t[0]&&/^[A-Za-z_][A-Za-z0-9_]*=/u.test(t[0])){t.shift();continue}if(t[0]===\`-u\`||t[0]===\`--unset\`){t.splice(0,2);continue}break}let n=t.shift();if(!n)return null;let r=codexLinuxExecutablePath(n);return r?{command:r,args:t,base:(0,${pathVar}.basename)(n).replace(/\\.(sh|bin)$/u,\`\`).toLowerCase()}:null}` + + `function codexLinuxResolveDesktopExec(e){let t=codexLinuxSplitDesktopExec(e);if(t.length===0)return null;for(;;){if((0,${pathVar}.basename)(t[0]||\`\`)===\`env\`){t.shift();continue}if(t[0]&&/^[A-Za-z_][A-Za-z0-9_]*=/u.test(t[0])){t.shift();continue}if(t[0]===\`-u\`||t[0]===\`--unset\`){t.splice(0,2);continue}break}let n=t.shift();if(!n)return null;let r=codexLinuxExecutablePath(n);return r?{command:r,args:t,base:(0,${pathVar}.basename)(n).replace(/\\.(sh|bin)$/u,\`\`).toLowerCase()}:null}` + + `function codexLinuxDesktopShellArg(e){if(!e)return\`\`;let t=e[0],n=e[e.length-1],r=(t===\`"\`||t===\`'\`)&&n===t?t:null;r&&(e=e.slice(1,-1));let i=process.env.HOME||\`\`;r==null&&(e=e.replace(/^~(?=\\/|$)/u,i).replace(/\\\\([^$])/gu,\`$1\`));r!==\`'\`&&(e=e.replace(/\\$\\{HOME\\}|\\$HOME/gu,i));return e}` + + `function codexLinuxDesktopFlatpakRoots(){let e=process.env.HOME||\`/nonexistent\`,t=[{root:(0,${pathVar}.join)(e,\`.local/share/flatpak\`),scope:\`user\`},{root:\`/var/lib/flatpak\`,scope:\`system\`}];try{for(let e of (0,${fsVar}.readdirSync)(\`/etc/flatpak/installations.d\`)){try{let n=(0,${fsVar}.readFileSync)((0,${pathVar}.join)(\`/etc/flatpak/installations.d\`,e),\`utf8\`).match(/^\\s*Path\\s*=\\s*(.+?)\\s*$/mu)?.[1]?.replace(/^["']|["']$/gu,\`\`);n&&(0,${pathVar}.isAbsolute)(n)&&t.push({root:n,scope:\`installation\`,name:e.replace(/\\.conf$/u,\`\`)})}catch{}}}catch{}return t}` + + `function codexLinuxDesktopFlatpakAvailable(e,t){for(let n of codexLinuxDesktopFlatpakRoots())try{if(t?.scope&&n.scope!==t.scope)continue;if(t?.name&&n.name!==t.name)continue;if((0,${fsVar}.existsSync)((0,${pathVar}.join)(n.root,\`app\`,e)))return!0}catch{}return!1}` + + `function codexLinuxDesktopShellProbeTail(e){return/^(?:\\s*(?:(?:\\d*>>?|\\d*<|&>)\\s*\\S+|\\d*>&\\d+))*\\s*$/u.test(e)}` + + `function codexLinuxDesktopFlatpakScope(e){let t=e.match(/(?:^|\\s)--installation(?:=((?:"[^"]*"|'[^']*'|[^\\s;&|()<>]+))|\\s+((?:"[^"]*"|'[^']*'|[^\\s;&|()<>]+)))/u);return t?{scope:\`installation\`,name:codexLinuxDesktopShellArg(t[1]||t[2])}:/(?:^|\\s)--user(?:\\s|$)/u.test(e)?{scope:\`user\`}:/(?:^|\\s)--system(?:\\s|$)/u.test(e)?{scope:\`system\`}:null}` + + `function codexLinuxDesktopFlatpakInfoAvailable(e){let t=e.match(/^\\s*((?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+))\\s+(?:(?:--(?:system|user|show-location)|--installation(?:=(?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+)|\\s+(?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+))?)\\s+)*info\\s+(?:(?:--(?:system|user|show-location)|--installation(?:=(?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+)|\\s+(?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+))?)\\s+)*((?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()<>])+))/u);if(!t)return null;let n=codexLinuxDesktopShellArg(t[1]);return(0,${pathVar}.basename)(n)!==\`flatpak\`?null:codexLinuxDesktopShellProbeTail(e.slice(t[0].length))?codexLinuxExecutablePath(n)!=null&&codexLinuxDesktopFlatpakAvailable(codexLinuxDesktopShellArg(t[2]),codexLinuxDesktopFlatpakScope(t[0])):!1}` + + `function codexLinuxDesktopFlatpakInfoTokensAvailable(e){let t=0;for(;;){let n=e[t];if((0,${pathVar}.basename)(n||\`\`)===\`env\`){if(codexLinuxExecutablePath(n)==null)return!1;t++;continue}if(n&&/^[A-Za-z_][A-Za-z0-9_]*=/u.test(n)){t++;continue}if(n===\`-i\`||n===\`--ignore-environment\`||n?.startsWith(\`--unset=\`)){t++;continue}if(n===\`-u\`||n===\`--unset\`){t+=2;continue}break}if((0,${pathVar}.basename)(e[t]||\`\`)!==\`flatpak\`)return null;if(codexLinuxExecutablePath(e[t])==null)return!1;let n=t+1,r=null,a=e=>e===\`--user\`?{scope:\`user\`}:e===\`--system\`?{scope:\`system\`}:null;for(;;){let t=e[n];if(t===\`--user\`||t===\`--system\`){r=a(t),n++;continue}if(t?.startsWith(\`--installation=\`)){r={scope:\`installation\`,name:t.slice(15)},n++;continue}if(t===\`--installation\`){r={scope:\`installation\`,name:e[n+1]},n+=2;continue}if(t===\`--verbose\`){n++;continue}if(t?.startsWith(\`-\`))return!1;break}if(e[n]!==\`info\`)return null;n++;for(;n])+))/u);if(n&&codexLinuxDesktopShellProbeTail(e.slice(n[0].length)))return n[1]===\`which\`&&codexLinuxExecutablePath(\`which\`)==null||n[1]===\`hash\`&&t===\`fish\`?!1:codexLinuxExecutablePath(codexLinuxDesktopShellArg(n[2]))!=null;n=e.match(/^\\s*(test\\s+-x|\\[\\s+-x|\\[\\[\\s+-x)\\s+((?:"[^"]*"|'[^']*'|(?:\\\\.|[^\\s;&|()\\]])+))/u);if(n&&(!n[1].startsWith(\`[[\`)||[\`bash\`,\`zsh\`].includes(t))&&codexLinuxDesktopShellProbeTail(e.slice(n[0].length).replace(/^\\s*\\]{1,2}/u,\`\`)))return codexLinuxExecutablePath(codexLinuxDesktopShellArg(n[2]))!=null;return codexLinuxDesktopFlatpakInfoAvailable(e)}` + + `function codexLinuxDesktopShellSegments(e){let t=[],n=\`\`,r=null,i=!1;for(let a=0;a0?codexLinuxDesktopFlatpakTryExecShellAvailable(_codexArgs[_codexShellIndex]??\`\`):!1}` + + `function codexLinuxDesktopTryExecAvailable(e){let t=codexLinuxSplitDesktopExec(e),n=!1,skipControls=new Set([\`&&\`,\`||\`,\`;\`,\`|\`,\`!\`,\`then\`,\`fi\`,\`do\`,\`done\`]),skipCommands=new Set([\`env\`,\`test\`,\`[\`,\`]\`,\`exec\`,\`command\`,\`which\`,\`type\`,\`hash\`]),shells=new Set([\`sh\`,\`bash\`,\`dash\`,\`zsh\`,\`fish\`]),q=codexLinuxDesktopFlatpakInfoAvailable(e);if(q!=null)return q;q=codexLinuxDesktopFlatpakInfoTokensAvailable(t);if(q!=null)return q;let r=codexLinuxDesktopFlatpakShellAvailable(t,shells);if(r!=null)return r;for(var f=0;;){let e=t[f];if((0,${pathVar}.basename)(e||\`\`)===\`env\`||e&&/^[A-Za-z_][A-Za-z0-9_]*=/u.test(e)||e===\`-i\`||e===\`--ignore-environment\`||e?.startsWith(\`--unset=\`)){f++;continue}if(e===\`-u\`||e===\`--unset\`){f+=2;continue}break}if((0,${pathVar}.basename)(t[f]||\`\`)===\`flatpak\`&&f===t.length-1)return codexLinuxExecutablePath(t[f])!=null;for(let e=0;e0)return a&&codexLinuxExecutablePath(t[e])?codexLinuxDesktopTryExecShellAvailable(t[e],t[e+l-1],t[e+l]??\`\`):!1}}for(let e=0;ef:void 0,kind:\`editor\`,detect:()=>i.command,args:e=>codexLinuxDesktopArgs(i.args,e),open:async({command:e,path:t})=>{await codexLinuxLaunchDesktopEntry(o,t,e,i.args)}}}})}return e}`; + `function codexLinuxDiscoveredIdeTargets(){if(process.platform!==\`linux\`)return[];let e=[],t=new Set,n=new Set,r=new Set;for(let a of codexLinuxDesktopDirs())for(let o of codexLinuxDesktopEntryFiles(a)){let a=codexLinuxParseDesktopEntry(o),s=a?.Id?.toLowerCase();if(!a)continue;if((a.Hidden||\`\`).trim().toLowerCase()===\`true\`){s&&r.add(s);continue}if(s&&r.has(s)||!codexLinuxLooksLikeIde(a))continue;if(a.TryExec&&!codexLinuxDesktopTryExecAvailable(a.TryExec))continue;let i=codexLinuxResolveDesktopExec(a.Exec);if(!i||codexLinuxKnownIdeDesktopDuplicate(i))continue;let c=\`\${a.Name}|${"${i.command}"}|${"${i.args.join(` `)}"}\`.toLowerCase();if(t.has(c))continue;t.add(c);let l=a.Name.trim(),u=codexLinuxDesktopIdeIcon(a,i),d=codexLinuxUniqueDesktopIdeId(a,n),f=codexLinuxDesktopIconPath(a);e.push({id:d,platforms:{linux:{label:l,icon:u,iconPath:f?()=>f:void 0,kind:\`editor\`,detect:()=>i.command,args:e=>codexLinuxDesktopArgs(i.args,e),open:async({command:e,path:t})=>{await codexLinuxLaunchDesktopEntry(o,t,e,i.args)}}}})}return e}`; const helpers = ideCoreHelpers + dynamicDiscoveryHelpers; if (helpers.length > 0) { diff --git a/linux-features/open-target-discovery/test.js b/linux-features/open-target-discovery/test.js index 819f85d1..d33daa7c 100644 --- a/linux-features/open-target-discovery/test.js +++ b/linux-features/open-target-discovery/test.js @@ -75,11 +75,15 @@ function withTempDir(fn) { } } -function createSpawnRecorder({ failCommands = [], recordOptions = false } = {}) { +function createSpawnRecorder({ failCommands = [], recordOptions = false, execFileSync } = {}) { const calls = []; const failures = new Set(failCommands); return { calls, + execFileSync(command, args, options) { + if (execFileSync) return execFileSync(command, args, options); + throw new Error(`unexpected execFileSync: ${command} ${args.join(" ")}`); + }, spawn(command, args, options) { calls.push(recordOptions ? { command, args, options } : { command, args }); const child = new EventEmitter(); @@ -238,6 +242,148 @@ test("open-target discovery finds IDEs from desktop entries", () => { }); }); +test("open-target discovery finds IDEs from symlinked desktop entries", () => { + withTempDir((tmp) => { + const dataHome = path.join(tmp, "share"); + const appsDir = path.join(dataHome, "applications"); + const linkedAppsDir = path.join(tmp, "linked-applications"); + const editorCommand = makeExecutable(path.join(tmp, "toolbox", "bin"), "zed-appimage"); + const desktopFile = path.join(linkedAppsDir, "dev.zed.Zed.desktop"); + fs.mkdirSync(appsDir, { recursive: true }); + fs.mkdirSync(linkedAppsDir, { recursive: true }); + fs.writeFileSync( + desktopFile, + [ + "[Desktop Entry]", + "Type=Application", + "Name=Zed", + `Exec=${editorCommand} %U`, + "Categories=Development;IDE;", + ].join("\n"), + ); + fs.symlinkSync(desktopFile, path.join(appsDir, "dev.zed.Zed.desktop")); + + const targets = evaluatePatched( + openTargetsBundle, + { HOME: tmp, PATH: path.join(tmp, "bin"), XDG_DATA_HOME: dataHome, XDG_DATA_DIRS: path.join(tmp, "empty") }, + "Xg.flatMap((target)=>{let platform=target.platforms.linux;return platform?[{id:target.id,label:platform.label,command:platform.detect?.()}]:[]})", + ); + + assert.ok(targets.some((target) => target.id === "linux-desktop-dev-zed-zed" && target.command === editorCommand)); + }); +}); + +function writeDesktopEntry(appsDir, fileName, lines) { + fs.mkdirSync(appsDir, { recursive: true }); + fs.writeFileSync(path.join(appsDir, fileName), ["[Desktop Entry]", "Type=Application", ...lines].join("\n")); +} + +test("open-target discovery applies TryExec filters to desktop entries", () => { + withTempDir((tmp) => { + const dataHome = path.join(tmp, "share"); + const appsDir = path.join(dataHome, "applications"); + const binDir = path.join(tmp, "bin"); + const cursor = makeExecutable(binDir, "cursor"); + const terminal = makeExecutable(binDir, "workspace-terminal"); + makeExecutable(binDir, "env"); + makeExecutable(binDir, "flatpak"); + makeExecutable(binDir, "sh"); + fs.mkdirSync(path.join(tmp, ".local", "share", "flatpak", "app", "com.example.Terminal"), { recursive: true }); + fs.mkdirSync(path.join(tmp, ".local", "share", "flatpak", "app", "it.mijorus.gearlever"), { recursive: true }); + writeDesktopEntry(appsDir, "a-broken-terminal.desktop", [ + "Name=Broken Terminal", + "TryExec=sh -c 'command -v missing-terminal >/dev/null 2>&1'", + `Exec=${path.join(tmp, "missing-terminal")} --cwd %D`, + "Categories=System;TerminalEmulator;", + ]); + writeDesktopEntry(appsDir, "b-workspace-terminal.desktop", [ + "Name=Workspace Terminal", + "TryExec=sh -c 'flatpak info com.example.Terminal > /dev/null 2>&1'", + `Exec=${terminal} --cwd %D`, + "Categories=System;TerminalEmulator;", + ]); + writeDesktopEntry(appsDir, "broken-cursor.desktop", [ + "Name=Broken Cursor", + "TryExec=sh -c 'command -v missing-cursor >/dev/null 2>&1'", + `Exec=${path.join(tmp, "missing-cursor")} %U`, + "Categories=Development;IDE;", + ]); + writeDesktopEntry(appsDir, "cursor.desktop", [ + "Name=Cursor", + "TryExec=env -i flatpak info --show-location it.mijorus.gearlever", + `Exec=${cursor} %U`, + "Categories=Development;IDE;", + ]); + + const result = evaluatePatched( + openTargetsBundle, + { HOME: tmp, PATH: binDir, XDG_DATA_HOME: dataHome, XDG_DATA_DIRS: path.join(tmp, "empty") }, + "({terminal:uh.platforms.linux.detect(),ides:Xg.flatMap((target)=>{let platform=target.platforms.linux;return platform?[{label:platform.label,command:platform.detect?.()}]:[]})})", + ); + + assert.equal(result.terminal, terminal); + assert.ok(result.ides.some((target) => target.label === "Cursor" && target.command === cursor)); + assert.ok(!result.ides.some((target) => target.label === "Broken Cursor")); + }); +}); + +function tryExecEnv(tmp, { binNames = [], flatpakApps = [], setup } = {}) { + const binDir = path.join(tmp, "bin"); + for (const executable of binNames) makeExecutable(binDir, executable); + for (const appId of flatpakApps) fs.mkdirSync(path.join(tmp, ".local", "share", "flatpak", "app", appId), { recursive: true }); + setup?.({ tmp, binDir }); + return { HOME: tmp, PATH: binDir, XDG_DATA_HOME: path.join(tmp, "share"), XDG_DATA_DIRS: path.join(tmp, "empty") }; +} + +const tryExecCases = [ + [false, "env /missing/Cursor.AppImage", ["env"]], + [false, "sh -c '/missing/Cursor.AppImage'", ["sh"]], + [true, "env -u GTK_USE_PORTAL bash -lc 'command -v cursor >/dev/null 2>&1 && exec cursor'", ["env", "bash", "cursor"]], + [true, "bash --login -c 'command -v cursor >/dev/null 2>&1 && : >/dev/null'", ["bash", "cursor"]], + [true, "sh -c 'test -x \"$HOME/AppImages/Cursor.AppImage\" && exec \"$HOME/AppImages/Cursor.AppImage\"'", ["sh"], ({ tmp }) => makeExecutable(path.join(tmp, "AppImages"), "Cursor.AppImage")], + [true, "bash -lc 'test -x $HOME/Tools\\ Beta/Cursor\\ AppImage'", ["bash"], ({ tmp }) => makeExecutable(path.join(tmp, "Tools Beta"), "Cursor AppImage")], + [true, "bash -lc '[[ -x \"$HOME/AppImages/Cursor.AppImage\" ]]'", ["bash"], ({ tmp }) => makeExecutable(path.join(tmp, "AppImages"), "Cursor.AppImage")], + [false, "sh -c '[[ -x \"$HOME/AppImages/Cursor.AppImage\" ]]'", ["sh"], ({ tmp }) => makeExecutable(path.join(tmp, "AppImages"), "Cursor.AppImage")], + [false, "bash -lc 'test -x \"~/.local/bin/cursor\"'", ["bash"], ({ tmp }) => makeExecutable(path.join(tmp, ".local", "bin"), "cursor")], + [true, "sh -c 'command -v cursor >/dev/null 2>&1 || command -v codium >/dev/null 2>&1'", ["sh", "codium"]], + [false, "sh -c 'command -v workspace-terminal >/dev/null 2>&1 || command -v fallback-terminal >/dev/null 2>&1 && test -x /missing/workspace-terminal'", ["sh", "workspace-terminal", "fallback-terminal"]], + [false, "sh -c 'command -v cursor >/dev/null 2>&1 && test -x /missing/Cursor.AppImage'", ["sh", "cursor"]], + [true, "sh -c 'test -x \"$HOME/Terminal && Tools/Workspace Terminal\"'", ["sh"], ({ tmp }) => makeExecutable(path.join(tmp, "Terminal && Tools"), "Workspace Terminal")], + [false, "sh -c 'false # comment'", ["sh", "false"]], + [false, "sh -c 'command -v cursor >/dev/null 2>&1; exit 1'", ["sh", "cursor"]], + [false, "sh -c '! command -v cursor >/dev/null 2>&1'", ["sh", "cursor"]], + [false, "sh -c 'which /bin/ls >/dev/null 2>&1'", ["sh"]], + [false, "bash", []], + [true, "sh -c 'exec /bin/true && false'", ["sh"]], + [false, "sh -c 'exec /missing/cursor || true'", ["sh"]], + [false, "missing-wrapper bash -lc 'command -v cursor >/dev/null 2>&1'", ["bash", "cursor"]], + [false, "fish -C 'hash cursor >/dev/null 2>&1'", ["fish", "cursor"]], + [true, "env -i flatpak info --show-location it.mijorus.gearlever", ["env", "flatpak"], null, ["it.mijorus.gearlever"]], + [true, "sh -c 'flatpak info com.example.Terminal > /dev/null 2>&1'", ["sh", "flatpak"], null, ["com.example.Terminal"]], + [false, "sh -c 'flatpak info it.mijorus.gearlever > /dev/null 2>&1'", ["sh"], null, ["it.mijorus.gearlever"]], + [false, "flatpak --verbose info com.example.MissingIde", ["flatpak"], null, ["it.mijorus.gearlever"]], + [false, "flatpak --installation=extra info it.mijorus.gearlever", ["flatpak"], null, ["it.mijorus.gearlever"]], + [true, "flatpak run com.visualstudio.code", ["flatpak"], null, ["com.visualstudio.code"]], + [true, "flatpak", ["flatpak"]], + [false, "flatpak --installation=extra run com.visualstudio.code", ["flatpak"], null, ["com.visualstudio.code"]], + [false, "flatpak run --command=missing-helper com.visualstudio.code", ["flatpak"], null, ["com.visualstudio.code"]], + [false, "flatpak run --command=sh com.visualstudio.code -c 'command -v missing-helper >/dev/null 2>&1'", ["flatpak"], null, ["com.visualstudio.code"]], + [false, "flatpak run --command=bash com.visualstudio.code -c true", ["flatpak"], null, ["com.visualstudio.code"]], + [false, "env -i --unset=GTK_USE_PORTAL GTK_USE_PORTAL=0 flatpak run --command=sh com.visualstudio.code -c 'command -v cursor >/dev/null 2>&1'", ["env", "flatpak", "cursor"]], +]; + +test("open-target discovery evaluates TryExec parser", () => { + for (const [expected, command, binNames, setup, flatpakApps] of tryExecCases) withTempDir((tmp) => { + const quoted = JSON.stringify(command); + const result = evaluatePatched( + openTargetsBundle, + tryExecEnv(tmp, { binNames, flatpakApps, setup }), + "[codexLinuxDesktopTryExecAvailable(" + quoted + "),codexLinuxTerminalTryExecAvailable(" + quoted + ")]", + ); + assert.deepEqual(result, [expected, expected], command); + }); +}); + test("open-target discovery launches desktop entries through gio when available", async () => { await withTempDir(async (tmp) => { const dataHome = path.join(tmp, "share");