dex-termux-cli 0.2.3 → 0.3.0-beta.2
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 +58 -61
- package/data/commands/explain.json +63 -0
- package/package.json +9 -3
- package/src/app/main.js +11 -1
- package/src/commands/android-shell.js +220 -27
- package/src/commands/context.js +22 -0
- package/src/commands/explain.js +11 -2
- package/src/commands/install.js +279 -28
- package/src/commands/menu.js +38 -5
- package/src/commands/safe-shell.js +211 -10
- package/src/commands/search.js +5 -1
- package/src/commands/tree.js +5 -1
- package/src/commands/version.js +84 -11
- package/src/core/args.js +13 -3
- package/src/core/config.js +43 -4
- package/src/core/scopes.js +22 -12
- package/src/ui/output.js +10 -7
- package/src/ui/prompt.js +77 -14
- package/src/utils/platform.js +116 -0
- package/src/utils/project-context.js +183 -31
- package/src/utils/shell.js +33 -7
package/src/ui/prompt.js
CHANGED
|
@@ -3,14 +3,14 @@ import { moveCursor, cursorTo } from 'node:readline';
|
|
|
3
3
|
import { stdin as input, stdout as output } from 'node:process';
|
|
4
4
|
|
|
5
5
|
const ANSI = {
|
|
6
|
-
clearDown: '
|
|
7
|
-
cyan: '
|
|
8
|
-
dim: '
|
|
9
|
-
green: '
|
|
10
|
-
red: '
|
|
11
|
-
reset: '
|
|
12
|
-
yellow: '
|
|
13
|
-
bold: '
|
|
6
|
+
clearDown: '[0J',
|
|
7
|
+
cyan: '[36m',
|
|
8
|
+
dim: '[2m',
|
|
9
|
+
green: '[32m',
|
|
10
|
+
red: '[31m',
|
|
11
|
+
reset: '[0m',
|
|
12
|
+
yellow: '[33m',
|
|
13
|
+
bold: '[1m',
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export async function chooseSearchScope(options) {
|
|
@@ -20,7 +20,7 @@ export async function chooseSearchScope(options) {
|
|
|
20
20
|
getValue: (option) => option.key,
|
|
21
21
|
getLines: (option) => [option.label, option.description, option.root],
|
|
22
22
|
prompt: 'Scope [1-' + options.length + ', Enter=1]: ',
|
|
23
|
-
errorMessage: 'Opcion no valida. Usa un numero o
|
|
23
|
+
errorMessage: 'Opcion no valida. Usa un numero o una clave valida.',
|
|
24
24
|
directMatch: (answer) => options.find((option) => option.key === answer.toLowerCase())?.key,
|
|
25
25
|
});
|
|
26
26
|
}
|
|
@@ -47,7 +47,7 @@ export async function chooseMenuAction() {
|
|
|
47
47
|
{ key: 'safe-shell', label: 'Entrar al modo seguro', hint: 'abre el proyecto con el entorno correcto' },
|
|
48
48
|
{ key: 'version', label: 'Ver version de Dex', hint: 'compara local contra la version publicada' },
|
|
49
49
|
{ key: 'context', label: 'Ver contexto del proyecto', hint: 'detecta el lenguaje del proyecto actual' },
|
|
50
|
-
{ key: 'settings', label: 'Ajustes y funciones', hint: 'activa extras y
|
|
50
|
+
{ key: 'settings', label: 'Ajustes y funciones', hint: 'activa extras, plataforma y prompt' },
|
|
51
51
|
{ key: 'help', label: 'Ver ayuda', hint: 'muestra comandos y ejemplos' },
|
|
52
52
|
{ key: 'exit', label: 'Salir', hint: 'cerrar el menu' },
|
|
53
53
|
];
|
|
@@ -67,11 +67,17 @@ export async function chooseMenuAction() {
|
|
|
67
67
|
|
|
68
68
|
export async function chooseSettingsAction(config) {
|
|
69
69
|
const options = [
|
|
70
|
+
{
|
|
71
|
+
key: 'platform-mode',
|
|
72
|
+
label: 'Modo de plataforma',
|
|
73
|
+
status: formatPlatformMode(config.runtime?.platformMode || 'auto'),
|
|
74
|
+
usage: 'elige auto, termux o linux',
|
|
75
|
+
},
|
|
70
76
|
{
|
|
71
77
|
key: 'toggle-android-shortcut',
|
|
72
|
-
label: 'Acceso rapido
|
|
78
|
+
label: 'Acceso rapido',
|
|
73
79
|
status: formatStatus(config.features.androidShortcut),
|
|
74
|
-
usage: 'permite dex -a',
|
|
80
|
+
usage: 'permite dex -a en Termux o Linux',
|
|
75
81
|
},
|
|
76
82
|
{
|
|
77
83
|
key: 'toggle-project-badge',
|
|
@@ -89,7 +95,7 @@ export async function chooseSettingsAction(config) {
|
|
|
89
95
|
key: 'toggle-smart-project-install',
|
|
90
96
|
label: 'Instalacion segura de proyectos',
|
|
91
97
|
status: formatStatus(config.features.smartProjectInstall),
|
|
92
|
-
usage: 'rescata instalaciones y
|
|
98
|
+
usage: 'rescata instalaciones y usa modo seguro por lenguaje',
|
|
93
99
|
},
|
|
94
100
|
{
|
|
95
101
|
key: 'back',
|
|
@@ -113,7 +119,7 @@ export async function chooseSettingsAction(config) {
|
|
|
113
119
|
];
|
|
114
120
|
},
|
|
115
121
|
prompt: 'Ajuste [1-' + options.length + ', Enter=1]: ',
|
|
116
|
-
errorMessage: 'Opcion no valida. Usa 1, 2, 3, 4 o
|
|
122
|
+
errorMessage: 'Opcion no valida. Usa 1, 2, 3, 4, 5 o 6.',
|
|
117
123
|
directMatch: (answer) => options.find((option) => option.key === answer.toLowerCase())?.key,
|
|
118
124
|
style: 'card',
|
|
119
125
|
introLines: ['Activa solo lo que quieras ver todos los dias.'],
|
|
@@ -165,6 +171,51 @@ export async function choosePromptContextPosition(currentPosition) {
|
|
|
165
171
|
});
|
|
166
172
|
}
|
|
167
173
|
|
|
174
|
+
export async function choosePlatformMode(currentMode) {
|
|
175
|
+
const options = [
|
|
176
|
+
{
|
|
177
|
+
key: 'auto',
|
|
178
|
+
label: 'Auto',
|
|
179
|
+
usage: 'detecta si estas en Termux o Linux segun el entorno real',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
key: 'termux',
|
|
183
|
+
label: 'Modo Termux',
|
|
184
|
+
usage: 'activa comportamiento centrado en Android storage y Termux',
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: 'linux',
|
|
188
|
+
label: 'Modo Linux',
|
|
189
|
+
usage: 'desactiva Android y asume un entorno Linux puro',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
key: 'back',
|
|
193
|
+
label: 'Volver',
|
|
194
|
+
},
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
return chooseNumericOption({
|
|
198
|
+
title: 'Modo de plataforma',
|
|
199
|
+
options,
|
|
200
|
+
getValue: (option) => option.key,
|
|
201
|
+
getLines: (option) => {
|
|
202
|
+
if (option.key === 'back') {
|
|
203
|
+
return [option.label];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return [
|
|
207
|
+
option.label,
|
|
208
|
+
'Uso : ' + option.usage,
|
|
209
|
+
];
|
|
210
|
+
},
|
|
211
|
+
prompt: 'Modo [1-' + options.length + ', Enter=1]: ',
|
|
212
|
+
errorMessage: 'Opcion no valida. Usa 1, 2, 3 o 4.',
|
|
213
|
+
directMatch: (answer) => options.find((option) => option.key === answer.toLowerCase())?.key,
|
|
214
|
+
style: 'card',
|
|
215
|
+
introLines: ['Actual: ' + formatPlatformMode(currentMode)],
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
168
219
|
export async function chooseFeatureToggle(currentValue, featureName, description) {
|
|
169
220
|
const options = [
|
|
170
221
|
{
|
|
@@ -371,6 +422,18 @@ function formatPromptPosition(position) {
|
|
|
371
422
|
return 'derecha';
|
|
372
423
|
}
|
|
373
424
|
|
|
425
|
+
function formatPlatformMode(mode) {
|
|
426
|
+
if (mode === 'linux') {
|
|
427
|
+
return 'linux';
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (mode === 'termux') {
|
|
431
|
+
return 'termux';
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return 'auto';
|
|
435
|
+
}
|
|
436
|
+
|
|
374
437
|
function colorize(text, color, weight = '') {
|
|
375
438
|
if (!output.isTTY) {
|
|
376
439
|
return text;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
const TERMUX_HOME = '/data/data/com.termux/files/home';
|
|
4
|
+
const ANDROID_STORAGE = '/sdcard';
|
|
5
|
+
|
|
6
|
+
export function getHomeDirectory() {
|
|
7
|
+
return process.env.HOME || TERMUX_HOME;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isHostTermux() {
|
|
11
|
+
const home = getHomeDirectory();
|
|
12
|
+
return Boolean(process.env.TERMUX_VERSION) || home === TERMUX_HOME || home.startsWith(TERMUX_HOME + path.sep);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizePlatformMode(mode) {
|
|
16
|
+
return ['auto', 'termux', 'linux'].includes(mode) ? mode : 'auto';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolvePlatformMode(config) {
|
|
20
|
+
const configured = normalizePlatformMode(config?.runtime?.platformMode || 'auto');
|
|
21
|
+
|
|
22
|
+
if (configured !== 'auto') {
|
|
23
|
+
return configured;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return isHostTermux() ? 'termux' : 'linux';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function formatPlatformMode(mode) {
|
|
30
|
+
if (mode === 'linux') {
|
|
31
|
+
return 'linux';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (mode === 'termux') {
|
|
35
|
+
return 'termux';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return 'auto';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getHomeScopeLabel(platformMode) {
|
|
42
|
+
return platformMode === 'linux' ? 'Home de Linux' : 'Home de Termux';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getHomeScopeDescription(platformMode) {
|
|
46
|
+
return platformMode === 'linux'
|
|
47
|
+
? 'Busca dentro de tu entorno principal de Linux.'
|
|
48
|
+
: 'Busca dentro de tu entorno principal de Termux.';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function canUseAndroidFeatures(platformMode) {
|
|
52
|
+
return platformMode === 'termux';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getAndroidStorageRoot(platformMode) {
|
|
56
|
+
return canUseAndroidFeatures(platformMode) ? ANDROID_STORAGE : '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getQuickAccessRoot(platformMode) {
|
|
60
|
+
return platformMode === 'termux' ? ANDROID_STORAGE : getHomeDirectory();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getQuickAccessTitle(platformMode) {
|
|
64
|
+
return platformMode === 'termux' ? 'Dex Android' : 'Dex Linux';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getQuickAccessModeDescription(platformMode) {
|
|
68
|
+
return platformMode === 'termux'
|
|
69
|
+
? 'interfaz Android redisenada'
|
|
70
|
+
: 'interfaz Linux redisenada';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getQuickAccessLabel(platformMode) {
|
|
74
|
+
return platformMode === 'termux' ? 'ANDROID STORAGE' : 'LINUX HOME';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function getQuickAccessShortcutSummary(platformMode) {
|
|
78
|
+
return platformMode === 'termux'
|
|
79
|
+
? 'dl, docs, dcim, pics, music, movies, shared'
|
|
80
|
+
: 'home, dl, docs, desk, pics, music, vids, tmp, shared';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function getQuickAccessAliases(platformMode) {
|
|
84
|
+
if (platformMode === 'termux') {
|
|
85
|
+
return {
|
|
86
|
+
shared: '/sdcard',
|
|
87
|
+
dl: '/sdcard/Download',
|
|
88
|
+
docs: '/sdcard/Documents',
|
|
89
|
+
dcim: '/sdcard/DCIM',
|
|
90
|
+
pics: '/sdcard/Pictures',
|
|
91
|
+
music: '/sdcard/Music',
|
|
92
|
+
movies: '/sdcard/Movies',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const home = getHomeDirectory();
|
|
97
|
+
return {
|
|
98
|
+
home,
|
|
99
|
+
shared: home,
|
|
100
|
+
dl: path.join(home, 'Downloads'),
|
|
101
|
+
docs: path.join(home, 'Documents'),
|
|
102
|
+
desk: path.join(home, 'Desktop'),
|
|
103
|
+
pics: path.join(home, 'Pictures'),
|
|
104
|
+
music: path.join(home, 'Music'),
|
|
105
|
+
vids: path.join(home, 'Videos'),
|
|
106
|
+
tmp: '/tmp',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function shouldRestrictProjectToAndroidStorage(platformMode) {
|
|
111
|
+
return platformMode === 'termux';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function isAndroidStoragePath(targetPath) {
|
|
115
|
+
return targetPath.startsWith('/sdcard') || targetPath.startsWith('/storage/emulated/0');
|
|
116
|
+
}
|
|
@@ -23,8 +23,65 @@ const LANGUAGE_META = {
|
|
|
23
23
|
ruby: { label: 'RUBY' },
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
const MAX_PARENT_LOOKUP = 16;
|
|
27
|
+
|
|
26
28
|
export async function detectProjectContext(cwd = process.cwd()) {
|
|
27
|
-
const
|
|
29
|
+
const searchChain = buildSearchChain(cwd);
|
|
30
|
+
const detections = [];
|
|
31
|
+
|
|
32
|
+
for (const directoryPath of searchChain) {
|
|
33
|
+
const detected = await detectProjectContextAt(directoryPath, cwd);
|
|
34
|
+
if (detected) {
|
|
35
|
+
detections.push(detected);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!detections.length) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const explicit = detections.filter((item) => item.detectionKind === 'explicit');
|
|
44
|
+
if (explicit.length) {
|
|
45
|
+
return pickBestDetection(explicit);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return pickBestDetection(detections);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function formatProjectContext(context) {
|
|
52
|
+
const folderName = path.basename(context.projectRoot || context.cwd) || context.cwd;
|
|
53
|
+
const segment = buildContextSegment(context);
|
|
54
|
+
const badge = process.stdout.isTTY
|
|
55
|
+
? `${COLORS[context.type] || COLORS.generic}[${segment}]${COLORS.reset}`
|
|
56
|
+
: `[${segment}]`;
|
|
57
|
+
|
|
58
|
+
return `Contexto: ${folderName} ${badge}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatPromptContext(context) {
|
|
62
|
+
return buildContextSegment(context);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatPromptProjectRoot(context) {
|
|
66
|
+
return context.projectRoot || context.cwd;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function formatPromptProjectPath(context) {
|
|
70
|
+
return context.projectRoot || context.cwd;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function formatProjectContextDetails(context) {
|
|
74
|
+
return [
|
|
75
|
+
'Carpeta : ' + context.cwd,
|
|
76
|
+
'Proyecto: ' + (context.projectRoot || context.cwd),
|
|
77
|
+
'Tipo : ' + context.label,
|
|
78
|
+
'Version : ' + (context.version || 'sin detectar'),
|
|
79
|
+
'Prompt : [' + formatPromptContext(context) + ']',
|
|
80
|
+
].join('\n');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function detectProjectContextAt(targetDir, cwd) {
|
|
84
|
+
const entries = await safeReadDir(targetDir);
|
|
28
85
|
if (!entries.length) {
|
|
29
86
|
return null;
|
|
30
87
|
}
|
|
@@ -35,67 +92,114 @@ export async function detectProjectContext(cwd = process.cwd()) {
|
|
|
35
92
|
const hasPhpFiles = entries.some((entry) => !entry.isDirectory() && entry.name.endsWith('.php'));
|
|
36
93
|
const hasRubyFiles = entries.some((entry) => !entry.isDirectory() && entry.name.endsWith('.rb'));
|
|
37
94
|
|
|
38
|
-
if (names.has('pyproject.toml') || names.has('requirements.txt') || names.has('Pipfile') || names.has('setup.py')
|
|
39
|
-
return createContext('python', cwd, await detectPythonVersion(
|
|
95
|
+
if (names.has('pyproject.toml') || names.has('requirements.txt') || names.has('Pipfile') || names.has('setup.py')) {
|
|
96
|
+
return createContext('python', cwd, targetDir, await detectPythonVersion(targetDir), 'explicit');
|
|
40
97
|
}
|
|
41
98
|
|
|
42
|
-
if (names.has('package.json') || names.has('node_modules')
|
|
43
|
-
return createContext('node', cwd, await detectNodeVersion(
|
|
99
|
+
if (names.has('package.json') || names.has('node_modules')) {
|
|
100
|
+
return createContext('node', cwd, targetDir, await detectNodeVersion(targetDir), 'explicit');
|
|
44
101
|
}
|
|
45
102
|
|
|
46
103
|
if (names.has('Cargo.toml')) {
|
|
47
|
-
return createContext('rust', cwd, await detectRustVersion(
|
|
104
|
+
return createContext('rust', cwd, targetDir, await detectRustVersion(targetDir), 'explicit');
|
|
48
105
|
}
|
|
49
106
|
|
|
50
107
|
if (names.has('go.mod')) {
|
|
51
|
-
return createContext('go', cwd, await detectGoVersion(
|
|
108
|
+
return createContext('go', cwd, targetDir, await detectGoVersion(targetDir), 'explicit');
|
|
52
109
|
}
|
|
53
110
|
|
|
54
111
|
if (names.has('pom.xml') || names.has('build.gradle') || names.has('build.gradle.kts')) {
|
|
55
|
-
return createContext('java', cwd, await detectJavaVersion(
|
|
112
|
+
return createContext('java', cwd, targetDir, await detectJavaVersion(targetDir), 'explicit');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (names.has('composer.json')) {
|
|
116
|
+
return createContext('php', cwd, targetDir, await detectPhpVersion(targetDir), 'explicit');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (names.has('Gemfile')) {
|
|
120
|
+
return createContext('ruby', cwd, targetDir, await detectRubyVersion(targetDir), 'explicit');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (hasPyFiles) {
|
|
124
|
+
return createContext('python', cwd, targetDir, await detectPythonVersion(targetDir), 'implicit');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (hasJsFiles) {
|
|
128
|
+
return createContext('node', cwd, targetDir, await detectNodeVersion(targetDir), 'implicit');
|
|
56
129
|
}
|
|
57
130
|
|
|
58
|
-
if (
|
|
59
|
-
return createContext('php', cwd, await detectPhpVersion(
|
|
131
|
+
if (hasPhpFiles) {
|
|
132
|
+
return createContext('php', cwd, targetDir, await detectPhpVersion(targetDir), 'implicit');
|
|
60
133
|
}
|
|
61
134
|
|
|
62
|
-
if (
|
|
63
|
-
return createContext('ruby', cwd, await detectRubyVersion(
|
|
135
|
+
if (hasRubyFiles) {
|
|
136
|
+
return createContext('ruby', cwd, targetDir, await detectRubyVersion(targetDir), 'implicit');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const nestedImplicitType = await detectImplicitLanguageFromChildren(targetDir, entries);
|
|
140
|
+
if (nestedImplicitType) {
|
|
141
|
+
return createContext(nestedImplicitType, cwd, targetDir, await detectVersionForType(nestedImplicitType, targetDir), 'implicit');
|
|
64
142
|
}
|
|
65
143
|
|
|
66
144
|
return null;
|
|
67
145
|
}
|
|
68
146
|
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
? `${COLORS[context.type] || COLORS.generic}[${segment}]${COLORS.reset}`
|
|
74
|
-
: `[${segment}]`;
|
|
147
|
+
async function detectImplicitLanguageFromChildren(targetDir, entries) {
|
|
148
|
+
const childDirectories = entries
|
|
149
|
+
.filter((entry) => entry.isDirectory())
|
|
150
|
+
.slice(0, 8);
|
|
75
151
|
|
|
76
|
-
|
|
77
|
-
|
|
152
|
+
for (const entry of childDirectories) {
|
|
153
|
+
const nestedEntries = await safeReadDir(path.join(targetDir, entry.name));
|
|
154
|
+
if (!nestedEntries.length) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
78
157
|
|
|
79
|
-
|
|
80
|
-
|
|
158
|
+
if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.py'))) {
|
|
159
|
+
return 'python';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (nestedEntries.some((item) => !item.isDirectory() && isNodeScript(item.name))) {
|
|
163
|
+
return 'node';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.php'))) {
|
|
167
|
+
return 'php';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (nestedEntries.some((item) => !item.isDirectory() && item.name.endsWith('.rb'))) {
|
|
171
|
+
return 'ruby';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return '';
|
|
81
176
|
}
|
|
82
177
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
178
|
+
function pickBestDetection(detections) {
|
|
179
|
+
let bestMatch = detections[0];
|
|
180
|
+
|
|
181
|
+
for (let index = 1; index < detections.length; index += 1) {
|
|
182
|
+
const current = detections[index];
|
|
183
|
+
|
|
184
|
+
if (current.type !== bestMatch.type) {
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
bestMatch = current;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return bestMatch;
|
|
90
192
|
}
|
|
91
193
|
|
|
92
|
-
function createContext(type, cwd, version) {
|
|
194
|
+
function createContext(type, cwd, projectRoot, version, detectionKind) {
|
|
93
195
|
const meta = LANGUAGE_META[type] || { label: type.toUpperCase() };
|
|
94
196
|
return {
|
|
95
197
|
type,
|
|
96
198
|
cwd,
|
|
199
|
+
projectRoot,
|
|
97
200
|
label: meta.label,
|
|
98
201
|
version,
|
|
202
|
+
detectionKind,
|
|
99
203
|
};
|
|
100
204
|
}
|
|
101
205
|
|
|
@@ -103,6 +207,22 @@ function buildContextSegment(context) {
|
|
|
103
207
|
return context.label;
|
|
104
208
|
}
|
|
105
209
|
|
|
210
|
+
function buildSearchChain(cwd) {
|
|
211
|
+
const chain = [];
|
|
212
|
+
let current = path.resolve(cwd);
|
|
213
|
+
|
|
214
|
+
for (let depth = 0; depth < MAX_PARENT_LOOKUP; depth += 1) {
|
|
215
|
+
chain.push(current);
|
|
216
|
+
const parent = path.dirname(current);
|
|
217
|
+
if (parent === current) {
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
current = parent;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return chain;
|
|
224
|
+
}
|
|
225
|
+
|
|
106
226
|
async function detectPythonVersion(cwd) {
|
|
107
227
|
const pythonVersionFile = await readFileIfExists(path.join(cwd, '.python-version'));
|
|
108
228
|
const pipfile = await readFileIfExists(path.join(cwd, 'Pipfile'));
|
|
@@ -190,11 +310,43 @@ async function detectJavaVersion(cwd) {
|
|
|
190
310
|
return compactVersion(
|
|
191
311
|
matchVersion(pom, /<java.version>([^<]+)<\/java.version>/i) ||
|
|
192
312
|
matchVersion(pom, /<maven.compiler.release>([^<]+)<\/maven.compiler.release>/i) ||
|
|
193
|
-
matchVersion(gradle, /sourceCompatibility\s*=\s*['"]?([^\s'"]+)/i) ||
|
|
313
|
+
matchVersion(gradle, /sourceCompatibility\s*=\s*['\"]?([^\s'\"]+)/i) ||
|
|
194
314
|
matchVersion(gradleKts, /JavaLanguageVersion\.of\((\d+)\)/i),
|
|
195
315
|
);
|
|
196
316
|
}
|
|
197
317
|
|
|
318
|
+
async function detectVersionForType(type, cwd) {
|
|
319
|
+
if (type === 'python') {
|
|
320
|
+
return detectPythonVersion(cwd);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (type === 'node') {
|
|
324
|
+
return detectNodeVersion(cwd);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (type === 'php') {
|
|
328
|
+
return detectPhpVersion(cwd);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (type === 'ruby') {
|
|
332
|
+
return detectRubyVersion(cwd);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (type === 'go') {
|
|
336
|
+
return detectGoVersion(cwd);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (type === 'rust') {
|
|
340
|
+
return detectRustVersion(cwd);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (type === 'java') {
|
|
344
|
+
return detectJavaVersion(cwd);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
198
350
|
async function safeReadDir(directoryPath) {
|
|
199
351
|
try {
|
|
200
352
|
return await fs.readdir(directoryPath, { withFileTypes: true });
|
package/src/utils/shell.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { isHostTermux } from './platform.js';
|
|
3
4
|
|
|
4
5
|
const TERMUX_ZSH = '/data/data/com.termux/files/usr/bin/zsh';
|
|
5
6
|
const TERMUX_BASH = '/data/data/com.termux/files/usr/bin/bash';
|
|
6
7
|
const TERMUX_SH = '/data/data/com.termux/files/usr/bin/sh';
|
|
8
|
+
const LINUX_ZSH = '/bin/zsh';
|
|
9
|
+
const LINUX_BASH = '/bin/bash';
|
|
10
|
+
const LINUX_SH = '/bin/sh';
|
|
7
11
|
|
|
8
|
-
export async function resolveInteractiveShell() {
|
|
12
|
+
export async function resolveInteractiveShell(platformMode = '') {
|
|
9
13
|
const preferredShell = process.env.DEX_PREFERRED_SHELL || process.env.SHELL || '';
|
|
10
|
-
const candidates = buildShellCandidates(preferredShell);
|
|
14
|
+
const candidates = buildShellCandidates(preferredShell, platformMode);
|
|
11
15
|
|
|
12
16
|
for (const shellPath of candidates) {
|
|
13
17
|
if (!shellPath) {
|
|
@@ -24,20 +28,42 @@ export async function resolveInteractiveShell() {
|
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
return {
|
|
27
|
-
shellPath:
|
|
31
|
+
shellPath: LINUX_SH,
|
|
28
32
|
shellArgs: ['-i'],
|
|
29
33
|
shellName: 'sh',
|
|
30
34
|
};
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
function buildShellCandidates(preferredShell) {
|
|
37
|
+
function buildShellCandidates(preferredShell, platformMode = '') {
|
|
34
38
|
const shellName = path.basename(preferredShell || '');
|
|
39
|
+
const preferredFirst = preferredShell && shellName && shellName !== 'sh'
|
|
40
|
+
? [preferredShell]
|
|
41
|
+
: [];
|
|
42
|
+
const preferTermuxShells = platformMode ? platformMode !== 'linux' : isHostTermux();
|
|
35
43
|
|
|
36
|
-
if (
|
|
37
|
-
return dedupe([
|
|
44
|
+
if (preferTermuxShells) {
|
|
45
|
+
return dedupe([
|
|
46
|
+
...preferredFirst,
|
|
47
|
+
TERMUX_ZSH,
|
|
48
|
+
TERMUX_BASH,
|
|
49
|
+
LINUX_ZSH,
|
|
50
|
+
LINUX_BASH,
|
|
51
|
+
preferredShell,
|
|
52
|
+
TERMUX_SH,
|
|
53
|
+
LINUX_SH,
|
|
54
|
+
]);
|
|
38
55
|
}
|
|
39
56
|
|
|
40
|
-
return dedupe([
|
|
57
|
+
return dedupe([
|
|
58
|
+
...preferredFirst,
|
|
59
|
+
LINUX_ZSH,
|
|
60
|
+
LINUX_BASH,
|
|
61
|
+
preferredShell,
|
|
62
|
+
LINUX_SH,
|
|
63
|
+
TERMUX_ZSH,
|
|
64
|
+
TERMUX_BASH,
|
|
65
|
+
TERMUX_SH,
|
|
66
|
+
]);
|
|
41
67
|
}
|
|
42
68
|
|
|
43
69
|
async function shellExists(shellPath) {
|