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
|
@@ -5,6 +5,7 @@ import { spawn } from 'node:child_process';
|
|
|
5
5
|
import { loadUserConfig } from '../core/config.js';
|
|
6
6
|
import { detectProjectContext } from '../utils/project-context.js';
|
|
7
7
|
import { resolveInteractiveShell } from '../utils/shell.js';
|
|
8
|
+
import { isAndroidStoragePath, resolvePlatformMode, shouldRestrictProjectToAndroidStorage } from '../utils/platform.js';
|
|
8
9
|
|
|
9
10
|
export async function runSafeShellCommand() {
|
|
10
11
|
const config = await loadUserConfig();
|
|
@@ -13,20 +14,23 @@ export async function runSafeShellCommand() {
|
|
|
13
14
|
throw new Error('La instalacion segura de proyectos esta desactivada. Activa esa funcion antes de abrir el modo seguro.');
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const currentDir = process.cwd();
|
|
17
18
|
const projectContext = await detectProjectContext();
|
|
19
|
+
const platformMode = resolvePlatformMode(config);
|
|
18
20
|
|
|
19
21
|
if (!projectContext || !supportsSafeMode(projectContext.type)) {
|
|
20
|
-
throw new Error('El modo seguro de Dex por ahora solo existe para proyectos Python, Node y
|
|
22
|
+
throw new Error('El modo seguro de Dex por ahora solo existe para proyectos Python, Node, PHP, Ruby, Go, Rust y Java.');
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
const projectRoot = projectContext.projectRoot || currentDir;
|
|
26
|
+
|
|
27
|
+
if (shouldRestrictProjectToAndroidStorage(platformMode) && !isAndroidStoragePath(projectRoot)) {
|
|
28
|
+
throw new Error('El modo seguro de Dex en Termux por ahora se usa en proyectos Python, Node, PHP, Ruby, Go, Rust o Java dentro de Android storage.');
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
const projectKey = getProjectKey(projectRoot);
|
|
28
32
|
const projectState = config.projects && config.projects[projectKey] ? config.projects[projectKey] : null;
|
|
29
|
-
const interactiveShell = await resolveInteractiveShell();
|
|
33
|
+
const interactiveShell = await resolveInteractiveShell(platformMode);
|
|
30
34
|
const reason = projectState && projectState.preferSafeInstall
|
|
31
35
|
? 'Este proyecto ya quedo marcado para modo seguro.'
|
|
32
36
|
: 'Entrando al entorno seguro manual de Dex.';
|
|
@@ -46,6 +50,9 @@ export async function runSafeShellCommand() {
|
|
|
46
50
|
|
|
47
51
|
console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
|
|
48
52
|
console.log('Proyecto : ' + projectRoot);
|
|
53
|
+
if (currentDir !== projectRoot) {
|
|
54
|
+
console.log('Desde : ' + currentDir);
|
|
55
|
+
}
|
|
49
56
|
console.log('Entorno : ' + venvDir);
|
|
50
57
|
console.log('Python : ' + rescuePython);
|
|
51
58
|
console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
|
|
@@ -61,6 +68,9 @@ export async function runSafeShellCommand() {
|
|
|
61
68
|
DEX_CONTEXT: 'safe-shell',
|
|
62
69
|
DEX_SAFE_PROJECT: projectRoot,
|
|
63
70
|
DEX_SAFE_PROJECT_KEY: projectKey,
|
|
71
|
+
DEX_SAFE_WORKSPACE: projectRoot,
|
|
72
|
+
DEX_SAFE_LABEL: 'PYTHON SAFE',
|
|
73
|
+
DEX_SAFE_RUNTIME: 'python',
|
|
64
74
|
});
|
|
65
75
|
return;
|
|
66
76
|
}
|
|
@@ -74,14 +84,18 @@ export async function runSafeShellCommand() {
|
|
|
74
84
|
try {
|
|
75
85
|
await fs.access(packageJsonPath);
|
|
76
86
|
await fs.access(nodeModulesDir);
|
|
87
|
+
await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
|
|
77
88
|
} catch {
|
|
78
|
-
throw new Error('No encontre
|
|
89
|
+
throw new Error('No encontre un espacio seguro valido para este proyecto Node. Corre dex -i primero para recrearlo.');
|
|
79
90
|
}
|
|
80
91
|
|
|
81
92
|
const nextPath = nodeBinDir + ':' + (process.env.PATH || '');
|
|
82
93
|
|
|
83
94
|
console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
|
|
84
95
|
console.log('Proyecto : ' + projectRoot);
|
|
96
|
+
if (currentDir !== projectRoot) {
|
|
97
|
+
console.log('Desde : ' + currentDir);
|
|
98
|
+
}
|
|
85
99
|
console.log('Seguro : ' + safeProjectDir);
|
|
86
100
|
console.log('Modulos : ' + nodeModulesDir);
|
|
87
101
|
console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
|
|
@@ -97,6 +111,8 @@ export async function runSafeShellCommand() {
|
|
|
97
111
|
DEX_SAFE_PROJECT: projectRoot,
|
|
98
112
|
DEX_SAFE_PROJECT_KEY: projectKey,
|
|
99
113
|
DEX_SAFE_WORKSPACE: safeProjectDir,
|
|
114
|
+
DEX_SAFE_LABEL: 'NODE SAFE',
|
|
115
|
+
DEX_SAFE_RUNTIME: 'node',
|
|
100
116
|
});
|
|
101
117
|
return;
|
|
102
118
|
}
|
|
@@ -111,14 +127,18 @@ export async function runSafeShellCommand() {
|
|
|
111
127
|
try {
|
|
112
128
|
await fs.access(composerJsonPath);
|
|
113
129
|
await fs.access(vendorDir);
|
|
130
|
+
await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
|
|
114
131
|
} catch {
|
|
115
|
-
throw new Error('No encontre
|
|
132
|
+
throw new Error('No encontre un espacio seguro valido para este proyecto PHP. Corre dex -i primero para recrearlo.');
|
|
116
133
|
}
|
|
117
134
|
|
|
118
135
|
const nextPath = vendorBinDir + ':' + (process.env.PATH || '');
|
|
119
136
|
|
|
120
137
|
console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
|
|
121
138
|
console.log('Proyecto : ' + projectRoot);
|
|
139
|
+
if (currentDir !== projectRoot) {
|
|
140
|
+
console.log('Desde : ' + currentDir);
|
|
141
|
+
}
|
|
122
142
|
console.log('Seguro : ' + safeProjectDir);
|
|
123
143
|
console.log('Vendor : ' + vendorDir);
|
|
124
144
|
console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
|
|
@@ -137,7 +157,154 @@ export async function runSafeShellCommand() {
|
|
|
137
157
|
DEX_SAFE_PROJECT: projectRoot,
|
|
138
158
|
DEX_SAFE_PROJECT_KEY: projectKey,
|
|
139
159
|
DEX_SAFE_WORKSPACE: safeProjectDir,
|
|
160
|
+
DEX_SAFE_LABEL: 'PHP SAFE',
|
|
161
|
+
DEX_SAFE_RUNTIME: 'php',
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (projectContext.type === 'ruby') {
|
|
167
|
+
const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
|
|
168
|
+
const gemfilePath = path.join(safeProjectDir, 'Gemfile');
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
await fs.access(gemfilePath);
|
|
172
|
+
await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
|
|
173
|
+
} catch {
|
|
174
|
+
throw new Error('No encontre un espacio seguro valido para este proyecto Ruby. Corre dex -i primero para recrearlo.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
|
|
178
|
+
console.log('Proyecto : ' + projectRoot);
|
|
179
|
+
if (currentDir !== projectRoot) {
|
|
180
|
+
console.log('Desde : ' + currentDir);
|
|
181
|
+
}
|
|
182
|
+
console.log('Seguro : ' + safeProjectDir);
|
|
183
|
+
console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
|
|
184
|
+
console.log('Nota : ' + reason);
|
|
185
|
+
console.log('Usa ruby, bundle o bundle exec desde aqui.');
|
|
186
|
+
console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
|
|
187
|
+
console.log('Escribe exit para volver a tu shell normal.');
|
|
188
|
+
console.log('');
|
|
189
|
+
|
|
190
|
+
await openSafeShell(interactiveShell, safeProjectDir, {
|
|
191
|
+
DEX_CONTEXT: 'safe-shell',
|
|
192
|
+
DEX_SAFE_PROJECT: projectRoot,
|
|
193
|
+
DEX_SAFE_PROJECT_KEY: projectKey,
|
|
194
|
+
DEX_SAFE_WORKSPACE: safeProjectDir,
|
|
195
|
+
DEX_SAFE_LABEL: 'RUBY SAFE',
|
|
196
|
+
DEX_SAFE_RUNTIME: 'ruby',
|
|
197
|
+
});
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (projectContext.type === 'go') {
|
|
202
|
+
const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
|
|
203
|
+
const goModPath = path.join(safeProjectDir, 'go.mod');
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
await fs.access(goModPath);
|
|
207
|
+
await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
|
|
208
|
+
} catch {
|
|
209
|
+
throw new Error('No encontre un espacio seguro valido para este proyecto Go. Corre dex -i primero para recrearlo.');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
|
|
213
|
+
console.log('Proyecto : ' + projectRoot);
|
|
214
|
+
if (currentDir !== projectRoot) {
|
|
215
|
+
console.log('Desde : ' + currentDir);
|
|
216
|
+
}
|
|
217
|
+
console.log('Seguro : ' + safeProjectDir);
|
|
218
|
+
console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
|
|
219
|
+
console.log('Nota : ' + reason);
|
|
220
|
+
console.log('Usa go run, go test o go build desde aqui.');
|
|
221
|
+
console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
|
|
222
|
+
console.log('Escribe exit para volver a tu shell normal.');
|
|
223
|
+
console.log('');
|
|
224
|
+
|
|
225
|
+
await openSafeShell(interactiveShell, safeProjectDir, {
|
|
226
|
+
DEX_CONTEXT: 'safe-shell',
|
|
227
|
+
DEX_SAFE_PROJECT: projectRoot,
|
|
228
|
+
DEX_SAFE_PROJECT_KEY: projectKey,
|
|
229
|
+
DEX_SAFE_WORKSPACE: safeProjectDir,
|
|
230
|
+
DEX_SAFE_LABEL: 'GO SAFE',
|
|
231
|
+
DEX_SAFE_RUNTIME: 'go',
|
|
140
232
|
});
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (projectContext.type === 'rust') {
|
|
237
|
+
const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
|
|
238
|
+
const cargoTomlPath = path.join(safeProjectDir, 'Cargo.toml');
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
await fs.access(cargoTomlPath);
|
|
242
|
+
await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
|
|
243
|
+
} catch {
|
|
244
|
+
throw new Error('No encontre un espacio seguro valido para este proyecto Rust. Corre dex -i primero para recrearlo.');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
|
|
248
|
+
console.log('Proyecto : ' + projectRoot);
|
|
249
|
+
if (currentDir !== projectRoot) {
|
|
250
|
+
console.log('Desde : ' + currentDir);
|
|
251
|
+
}
|
|
252
|
+
console.log('Seguro : ' + safeProjectDir);
|
|
253
|
+
console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
|
|
254
|
+
console.log('Nota : ' + reason);
|
|
255
|
+
console.log('Usa cargo build, cargo test o cargo run desde aqui.');
|
|
256
|
+
console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
|
|
257
|
+
console.log('Escribe exit para volver a tu shell normal.');
|
|
258
|
+
console.log('');
|
|
259
|
+
|
|
260
|
+
await openSafeShell(interactiveShell, safeProjectDir, {
|
|
261
|
+
DEX_CONTEXT: 'safe-shell',
|
|
262
|
+
DEX_SAFE_PROJECT: projectRoot,
|
|
263
|
+
DEX_SAFE_PROJECT_KEY: projectKey,
|
|
264
|
+
DEX_SAFE_WORKSPACE: safeProjectDir,
|
|
265
|
+
DEX_SAFE_LABEL: 'RUST SAFE',
|
|
266
|
+
DEX_SAFE_RUNTIME: 'rust',
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (projectContext.type === 'java') {
|
|
272
|
+
const safeProjectDir = getSafeProjectWorkspacePath(projectRoot, projectContext.type);
|
|
273
|
+
const pomPath = path.join(safeProjectDir, 'pom.xml');
|
|
274
|
+
const gradlePath = path.join(safeProjectDir, 'build.gradle');
|
|
275
|
+
const gradleKtsPath = path.join(safeProjectDir, 'build.gradle.kts');
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
await ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectContext.type);
|
|
279
|
+
if (!(await hasJavaBuildFiles(pomPath, gradlePath, gradleKtsPath))) {
|
|
280
|
+
throw new Error('missing-java-build-files');
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
throw new Error('No encontre un espacio seguro valido para este proyecto Java. Corre dex -i primero para recrearlo.');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log('Modo Dex : shell segura para ' + path.basename(projectRoot));
|
|
287
|
+
console.log('Proyecto : ' + projectRoot);
|
|
288
|
+
if (currentDir !== projectRoot) {
|
|
289
|
+
console.log('Desde : ' + currentDir);
|
|
290
|
+
}
|
|
291
|
+
console.log('Seguro : ' + safeProjectDir);
|
|
292
|
+
console.log('Shell : ' + interactiveShell.shellName + ' interactiva');
|
|
293
|
+
console.log('Nota : ' + reason);
|
|
294
|
+
console.log('Usa mvn, ./mvnw, gradle o ./gradlew desde aqui segun el proyecto.');
|
|
295
|
+
console.log('Esta shell abre la copia segura del proyecto dentro de HOME.');
|
|
296
|
+
console.log('Escribe exit para volver a tu shell normal.');
|
|
297
|
+
console.log('');
|
|
298
|
+
|
|
299
|
+
await openSafeShell(interactiveShell, safeProjectDir, {
|
|
300
|
+
DEX_CONTEXT: 'safe-shell',
|
|
301
|
+
DEX_SAFE_PROJECT: projectRoot,
|
|
302
|
+
DEX_SAFE_PROJECT_KEY: projectKey,
|
|
303
|
+
DEX_SAFE_WORKSPACE: safeProjectDir,
|
|
304
|
+
DEX_SAFE_LABEL: 'JAVA SAFE',
|
|
305
|
+
DEX_SAFE_RUNTIME: 'java',
|
|
306
|
+
});
|
|
307
|
+
return;
|
|
141
308
|
}
|
|
142
309
|
}
|
|
143
310
|
|
|
@@ -167,12 +334,46 @@ function getComposerHome() {
|
|
|
167
334
|
return path.join(home, '.dex', 'composer');
|
|
168
335
|
}
|
|
169
336
|
|
|
170
|
-
function
|
|
171
|
-
return
|
|
337
|
+
async function hasJavaBuildFiles(pomPath, gradlePath, gradleKtsPath) {
|
|
338
|
+
return Boolean(await pathExists(pomPath) || await pathExists(gradlePath) || await pathExists(gradleKtsPath));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function ensureSafeWorkspaceMatchesProject(safeProjectDir, projectRoot, projectType) {
|
|
342
|
+
const metadataPath = path.join(safeProjectDir, '.dex-workspace.json');
|
|
343
|
+
let metadataRaw = '';
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
metadataRaw = await fs.readFile(metadataPath, 'utf8');
|
|
347
|
+
} catch {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const metadata = JSON.parse(metadataRaw);
|
|
353
|
+
if (metadata.projectRoot && metadata.projectRoot !== projectRoot) {
|
|
354
|
+
throw new Error('workspace-root-mismatch');
|
|
355
|
+
}
|
|
356
|
+
if (metadata.projectType && metadata.projectType !== projectType) {
|
|
357
|
+
throw new Error('workspace-type-mismatch');
|
|
358
|
+
}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (error && (error.message === 'workspace-root-mismatch' || error.message === 'workspace-type-mismatch')) {
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function pathExists(targetPath) {
|
|
367
|
+
try {
|
|
368
|
+
await fs.access(targetPath);
|
|
369
|
+
return true;
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
172
373
|
}
|
|
173
374
|
|
|
174
375
|
function supportsSafeMode(projectType) {
|
|
175
|
-
return projectType === 'python' || projectType === 'node' || projectType === 'php';
|
|
376
|
+
return projectType === 'python' || projectType === 'node' || projectType === 'php' || projectType === 'ruby' || projectType === 'go' || projectType === 'rust' || projectType === 'java';
|
|
176
377
|
}
|
|
177
378
|
|
|
178
379
|
function openSafeShell(interactiveShell, cwd, envPatch) {
|
package/src/commands/search.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import { loadUserConfig } from '../core/config.js';
|
|
2
3
|
import { chooseSearchScope } from '../ui/prompt.js';
|
|
3
4
|
import { formatSearchResults, printSection } from '../ui/output.js';
|
|
4
5
|
import { getScopeOptions, resolveScopeRoot } from '../core/scopes.js';
|
|
5
6
|
import { searchEntries } from '../utils/fs-search.js';
|
|
7
|
+
import { resolvePlatformMode } from '../utils/platform.js';
|
|
6
8
|
|
|
7
9
|
export async function runSearchCommand({ pattern, scope }) {
|
|
8
10
|
const cleanPattern = (pattern || '').trim();
|
|
@@ -11,7 +13,9 @@ export async function runSearchCommand({ pattern, scope }) {
|
|
|
11
13
|
throw new Error('Debes indicar un patron. Ejemplo: dex -b "archivo"');
|
|
12
14
|
}
|
|
13
15
|
|
|
14
|
-
const
|
|
16
|
+
const config = await loadUserConfig();
|
|
17
|
+
const platformMode = resolvePlatformMode(config);
|
|
18
|
+
const scopeOptions = getScopeOptions(platformMode);
|
|
15
19
|
const selectedScope = scope || await chooseSearchScope(scopeOptions);
|
|
16
20
|
const root = resolveScopeRoot(selectedScope, scopeOptions);
|
|
17
21
|
|
package/src/commands/tree.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { loadUserConfig } from '../core/config.js';
|
|
3
4
|
import { chooseSearchScope } from '../ui/prompt.js';
|
|
4
5
|
import { formatTreeReport, printSection } from '../ui/output.js';
|
|
5
6
|
import { getScopeOptions, resolveScopeRoot } from '../core/scopes.js';
|
|
6
7
|
import { buildTreeReport } from '../utils/fs-tree.js';
|
|
8
|
+
import { resolvePlatformMode } from '../utils/platform.js';
|
|
7
9
|
|
|
8
10
|
export async function runTreeCommand({ target, scope, depth }) {
|
|
9
|
-
const
|
|
11
|
+
const config = await loadUserConfig();
|
|
12
|
+
const platformMode = resolvePlatformMode(config);
|
|
13
|
+
const scopeOptions = getScopeOptions(platformMode);
|
|
10
14
|
const selectedScope = scope || await chooseSearchScope(scopeOptions);
|
|
11
15
|
const root = resolveScopeRoot(selectedScope, scopeOptions);
|
|
12
16
|
|
package/src/commands/version.js
CHANGED
|
@@ -69,14 +69,14 @@ async function readLatestPublishedVersion() {
|
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
function compareVersions(left, right) {
|
|
73
|
-
const
|
|
74
|
-
const
|
|
75
|
-
const maxLength = Math.max(
|
|
72
|
+
export function compareVersions(left, right) {
|
|
73
|
+
const leftVersion = parseSemver(left);
|
|
74
|
+
const rightVersion = parseSemver(right);
|
|
75
|
+
const maxLength = Math.max(leftVersion.core.length, rightVersion.core.length);
|
|
76
76
|
|
|
77
77
|
for (let index = 0; index < maxLength; index += 1) {
|
|
78
|
-
const leftValue =
|
|
79
|
-
const rightValue =
|
|
78
|
+
const leftValue = leftVersion.core[index] || 0;
|
|
79
|
+
const rightValue = rightVersion.core[index] || 0;
|
|
80
80
|
|
|
81
81
|
if (leftValue > rightValue) {
|
|
82
82
|
return 1;
|
|
@@ -87,12 +87,85 @@ function compareVersions(left, right) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
return comparePrerelease(leftVersion.prerelease, rightVersion.prerelease);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseSemver(version) {
|
|
94
|
+
const normalized = String(version).trim();
|
|
95
|
+
const [mainPart = '', prereleasePart = ''] = normalized.split('-', 2);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
core: mainPart
|
|
99
|
+
.split('.')
|
|
100
|
+
.map((part) => Number.parseInt(part, 10))
|
|
101
|
+
.filter((part) => Number.isInteger(part)),
|
|
102
|
+
prerelease: prereleasePart
|
|
103
|
+
? prereleasePart
|
|
104
|
+
.split('.')
|
|
105
|
+
.filter(Boolean)
|
|
106
|
+
.map((part) => (/^\d+$/.test(part) ? Number.parseInt(part, 10) : part.toLowerCase()))
|
|
107
|
+
: [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function comparePrerelease(left, right) {
|
|
112
|
+
const leftHasPrerelease = left.length > 0;
|
|
113
|
+
const rightHasPrerelease = right.length > 0;
|
|
114
|
+
|
|
115
|
+
if (!leftHasPrerelease && !rightHasPrerelease) {
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!leftHasPrerelease) {
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!rightHasPrerelease) {
|
|
124
|
+
return -1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const maxLength = Math.max(left.length, right.length);
|
|
128
|
+
|
|
129
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
130
|
+
const leftPart = left[index];
|
|
131
|
+
const rightPart = right[index];
|
|
132
|
+
|
|
133
|
+
if (leftPart === undefined) {
|
|
134
|
+
return -1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (rightPart === undefined) {
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const comparison = comparePrereleasePart(leftPart, rightPart);
|
|
142
|
+
if (comparison !== 0) {
|
|
143
|
+
return comparison;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
90
147
|
return 0;
|
|
91
148
|
}
|
|
92
149
|
|
|
93
|
-
function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
150
|
+
function comparePrereleasePart(left, right) {
|
|
151
|
+
const leftIsNumber = typeof left === 'number';
|
|
152
|
+
const rightIsNumber = typeof right === 'number';
|
|
153
|
+
|
|
154
|
+
if (leftIsNumber && rightIsNumber) {
|
|
155
|
+
return left === right ? 0 : left > right ? 1 : -1;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (leftIsNumber) {
|
|
159
|
+
return -1;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (rightIsNumber) {
|
|
163
|
+
return 1;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (left === right) {
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return left > right ? 1 : -1;
|
|
98
171
|
}
|
package/src/core/args.js
CHANGED
|
@@ -32,12 +32,22 @@ export function parseArgs(argv) {
|
|
|
32
32
|
continue;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
if (token === '--prompt-project-root') {
|
|
36
|
+
parsed.command = 'prompt-project-root';
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (token === '--prompt-project-path') {
|
|
41
|
+
parsed.command = 'prompt-project-path';
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
if (token === '-m' || token === '--menu' || token === 'menu') {
|
|
36
46
|
parsed.command = 'menu';
|
|
37
47
|
continue;
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
if (token === '-b' || token === '--buscar') {
|
|
50
|
+
if (token === '-b' || token === '--buscar' || token === '--search' || token === 'search') {
|
|
41
51
|
parsed.command = 'search';
|
|
42
52
|
const next = argv[index + 1];
|
|
43
53
|
if (next && !next.startsWith('-')) {
|
|
@@ -47,7 +57,7 @@ export function parseArgs(argv) {
|
|
|
47
57
|
continue;
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
if (token === '-e' || token === '--explicar' || token === 'explicar') {
|
|
60
|
+
if (token === '-e' || token === '--explicar' || token === '--explain' || token === 'explicar' || token === 'explain') {
|
|
51
61
|
parsed.command = 'explain';
|
|
52
62
|
const next = argv[index + 1];
|
|
53
63
|
if (next && !next.startsWith('-')) {
|
|
@@ -77,7 +87,7 @@ export function parseArgs(argv) {
|
|
|
77
87
|
continue;
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
if (token === '-r' || token === '--seguro' || token === '--safe-shell' || token === 'seguro') {
|
|
90
|
+
if (token === '-r' || token === '--seguro' || token === '--safe-shell' || token === 'seguro' || token === 'safe-shell') {
|
|
81
91
|
parsed.command = 'safe-shell';
|
|
82
92
|
continue;
|
|
83
93
|
}
|
package/src/core/config.js
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { getHomeDirectory, normalizePlatformMode } from '../utils/platform.js';
|
|
3
4
|
|
|
4
5
|
const DEFAULT_CONFIG = {
|
|
5
6
|
features: {
|
|
6
|
-
androidShortcut:
|
|
7
|
-
projectBadge:
|
|
7
|
+
androidShortcut: true,
|
|
8
|
+
projectBadge: true,
|
|
8
9
|
smartProjectInstall: false,
|
|
9
10
|
},
|
|
10
11
|
ui: {
|
|
11
12
|
promptContextPosition: 'right',
|
|
12
13
|
},
|
|
14
|
+
runtime: {
|
|
15
|
+
platformMode: 'auto',
|
|
16
|
+
},
|
|
13
17
|
projects: {},
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
export function getUserConfigPath() {
|
|
17
|
-
|
|
18
|
-
return path.join(home, '.config', 'dex', 'config.json');
|
|
21
|
+
return path.join(getHomeDirectory(), '.config', 'dex', 'config.json');
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export async function loadUserConfig() {
|
|
@@ -71,6 +74,34 @@ export async function setPromptContextPosition(position) {
|
|
|
71
74
|
return saveUserConfig(nextConfig);
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
export async function setPlatformMode(platformMode) {
|
|
78
|
+
const config = await loadUserConfig();
|
|
79
|
+
const normalized = normalizePlatformMode(platformMode);
|
|
80
|
+
const nextFeatures = {
|
|
81
|
+
...config.features,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (normalized === 'linux') {
|
|
85
|
+
nextFeatures.androidShortcut = true;
|
|
86
|
+
nextFeatures.projectBadge = true;
|
|
87
|
+
nextFeatures.smartProjectInstall = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (normalized === 'termux') {
|
|
91
|
+
nextFeatures.androidShortcut = true;
|
|
92
|
+
nextFeatures.projectBadge = true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return saveUserConfig({
|
|
96
|
+
...config,
|
|
97
|
+
features: nextFeatures,
|
|
98
|
+
runtime: {
|
|
99
|
+
...config.runtime,
|
|
100
|
+
platformMode: normalized,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
74
105
|
export async function saveProjectState(projectKey, projectState) {
|
|
75
106
|
const config = await loadUserConfig();
|
|
76
107
|
const nextConfig = {
|
|
@@ -124,6 +155,11 @@ function mergeConfig(config) {
|
|
|
124
155
|
...(config.ui || {}),
|
|
125
156
|
promptContextPosition: normalizePromptContextPosition(config.ui?.promptContextPosition),
|
|
126
157
|
},
|
|
158
|
+
runtime: {
|
|
159
|
+
...DEFAULT_CONFIG.runtime,
|
|
160
|
+
...(config.runtime || {}),
|
|
161
|
+
platformMode: normalizePlatformMode(config.runtime?.platformMode),
|
|
162
|
+
},
|
|
127
163
|
projects: {
|
|
128
164
|
...DEFAULT_CONFIG.projects,
|
|
129
165
|
...(config.projects || {}),
|
|
@@ -139,6 +175,9 @@ function cloneDefaultConfig() {
|
|
|
139
175
|
ui: {
|
|
140
176
|
...DEFAULT_CONFIG.ui,
|
|
141
177
|
},
|
|
178
|
+
runtime: {
|
|
179
|
+
...DEFAULT_CONFIG.runtime,
|
|
180
|
+
},
|
|
142
181
|
projects: {
|
|
143
182
|
...DEFAULT_CONFIG.projects,
|
|
144
183
|
},
|
package/src/core/scopes.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
canUseAndroidFeatures,
|
|
4
|
+
getAndroidStorageRoot,
|
|
5
|
+
getHomeDirectory,
|
|
6
|
+
getHomeScopeDescription,
|
|
7
|
+
getHomeScopeLabel,
|
|
8
|
+
} from '../utils/platform.js';
|
|
2
9
|
|
|
3
|
-
export function getScopeOptions() {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
return [
|
|
10
|
+
export function getScopeOptions(platformMode) {
|
|
11
|
+
const options = [
|
|
7
12
|
{
|
|
8
13
|
key: 'actual',
|
|
9
14
|
label: 'Carpeta actual',
|
|
@@ -12,20 +17,25 @@ export function getScopeOptions() {
|
|
|
12
17
|
},
|
|
13
18
|
{
|
|
14
19
|
key: 'home',
|
|
15
|
-
label:
|
|
16
|
-
root:
|
|
17
|
-
description:
|
|
20
|
+
label: getHomeScopeLabel(platformMode),
|
|
21
|
+
root: getHomeDirectory(),
|
|
22
|
+
description: getHomeScopeDescription(platformMode),
|
|
18
23
|
},
|
|
19
|
-
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
if (canUseAndroidFeatures(platformMode)) {
|
|
27
|
+
options.push({
|
|
20
28
|
key: 'android',
|
|
21
29
|
label: 'Almacenamiento Android',
|
|
22
|
-
root:
|
|
30
|
+
root: getAndroidStorageRoot(platformMode),
|
|
23
31
|
description: 'Busca dentro del almacenamiento compartido accesible.',
|
|
24
|
-
}
|
|
25
|
-
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return options;
|
|
26
36
|
}
|
|
27
37
|
|
|
28
|
-
export function resolveScopeRoot(scopeKey, options =
|
|
38
|
+
export function resolveScopeRoot(scopeKey, options = []) {
|
|
29
39
|
const found = options.find((option) => option.key === scopeKey);
|
|
30
40
|
return found ? path.resolve(found.root) : '';
|
|
31
41
|
}
|
package/src/ui/output.js
CHANGED
|
@@ -12,7 +12,8 @@ const ANSI = {
|
|
|
12
12
|
export function printHelp() {
|
|
13
13
|
printBanner();
|
|
14
14
|
console.log('');
|
|
15
|
-
console.log(' Explorar, entender y preparar proyectos desde Termux
|
|
15
|
+
console.log(' Explorar, entender y preparar proyectos desde Termux o Linux.');
|
|
16
|
+
console.log(' Android storage aparece cuando el modo de plataforma lo permite.');
|
|
16
17
|
console.log('');
|
|
17
18
|
|
|
18
19
|
printBlock('Comandos principales', [
|
|
@@ -22,22 +23,24 @@ export function printHelp() {
|
|
|
22
23
|
'dex -b "archivo" buscar archivos o carpetas',
|
|
23
24
|
'dex -e ls explicar un comando',
|
|
24
25
|
'dex -t src arbol guiado de una ruta',
|
|
25
|
-
'dex -i instalar dependencias del proyecto',
|
|
26
|
-
'dex -r abrir modo seguro del proyecto',
|
|
27
|
-
'dex -a acceso rapido a Android',
|
|
26
|
+
'dex -i instalar dependencias del proyecto detectado',
|
|
27
|
+
'dex -r abrir modo seguro del proyecto (Py/Node/PHP/Ruby/Go/Rust/Java)',
|
|
28
|
+
'dex -a acceso rapido a Android o Linux',
|
|
28
29
|
]);
|
|
29
30
|
|
|
30
31
|
printBlock('Uso rapido', [
|
|
31
32
|
'dex -b "*.js" --scope actual',
|
|
32
|
-
'dex -t . --depth 3',
|
|
33
|
+
'dex -t . --depth 3 --scope actual',
|
|
33
34
|
'dex -c',
|
|
34
35
|
'dex --prompt-context',
|
|
36
|
+
'dex --prompt-project-root',
|
|
37
|
+
'dex --prompt-project-path',
|
|
35
38
|
]);
|
|
36
39
|
|
|
37
40
|
printBlock('Scopes', [
|
|
38
41
|
'actual carpeta donde estas parado',
|
|
39
|
-
'home tu HOME
|
|
40
|
-
'android almacenamiento accesible',
|
|
42
|
+
'home tu HOME del entorno actual',
|
|
43
|
+
'android almacenamiento accesible en modo Termux',
|
|
41
44
|
]);
|
|
42
45
|
|
|
43
46
|
printBlock('Atajos', [
|