termux-dev 1.9.0 → 1.9.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 +1 -1
- package/assets/banner.svg +1 -1
- package/assets/preview.svg +1 -1
- package/dist/cli/finder.js +6 -2
- package/dist/cli/index.js +3 -16
- package/dist/cli/liveblock.js +9 -0
- package/dist/cli/update-screen.js +383 -0
- package/dist/cli/updater.js +119 -145
- package/dist/core/locales/de.js +33 -0
- package/dist/core/locales/en.js +33 -0
- package/dist/core/locales/es.js +33 -0
- package/dist/core/locales/fr.js +33 -0
- package/dist/core/locales/ja.js +33 -0
- package/dist/core/locales/pt.js +33 -0
- package/dist/core/locales/ru.js +33 -0
- package/dist/core/locales/uk.js +33 -0
- package/dist/core/locales/zh.js +33 -0
- package/dist/core/types.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -185,7 +185,7 @@ Type `/` in the prompt to open the autocomplete command palette:
|
|
|
185
185
|
| **`/resume`** | Browse and resume saved chat sessions |
|
|
186
186
|
| **`/session del`** | Selectively or bulk delete saved sessions |
|
|
187
187
|
| **`/settings`** | Configure theme, interface, YOLO auto-approve, project memory, update checks and the interface language |
|
|
188
|
-
| **`/update`** | Check
|
|
188
|
+
| **`/update`** | Check for a new version: a card with what is new, then **Update now**, **Later** or (at start-up) **Skip this version**. The install runs on a live screen step by step, checks that npm really has the version, and restarts devx. If it fails you get the reason and the command to run by hand |
|
|
189
189
|
| **`/model`** | Switch model for the active provider with live search |
|
|
190
190
|
| **`/effort [level]`** | Set the reasoning effort. Only the levels the current model supports are offered (see [Reasoning effort](#reasoning-effort)) |
|
|
191
191
|
| **`/provider`** | Switch provider (OpenRouter, Gemini, Groq, DeepSeek, Local) |
|
package/assets/banner.svg
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<rect width="1000" height="280" fill="url(#bg)"/>
|
|
17
17
|
</g>
|
|
18
18
|
<rect x="1.5" y="1.5" width="997" height="277" rx="18" fill="none" stroke="#23252c" stroke-width="1.5"/>
|
|
19
|
-
<text x="500.0" y="70" text-anchor="middle" font-family="'Inter',system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" letter-spacing="5" fill="#7d8896">THE TERMINAL-NATIVE AI CODING AGENT<tspan dx="16" fill="#2fe3ee" font-weight="700">v1.9.
|
|
19
|
+
<text x="500.0" y="70" text-anchor="middle" font-family="'Inter',system-ui,-apple-system,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" letter-spacing="5" fill="#7d8896">THE TERMINAL-NATIVE AI CODING AGENT<tspan dx="16" fill="#2fe3ee" font-weight="700">v1.9.2</tspan></text>
|
|
20
20
|
<g>
|
|
21
21
|
<text x="500.0" y="178" text-anchor="middle" font-family="'JetBrains Mono','Fira Code','SF Mono',ui-monospace,Menlo,Consolas,'DejaVu Sans Mono',monospace" font-size="112" font-weight="800" letter-spacing="14" fill="#2fe3ee">DEVX</text>
|
|
22
22
|
</g>
|
package/assets/preview.svg
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<rect x="595.2" y="574.0" width="243.2" height="19" fill="#000000"/>
|
|
33
33
|
<text x="44.8" y="62.0" class="t" fill="#b794f6">▌</text>
|
|
34
34
|
<text x="61.6" y="62.0" class="t b" fill="#f4f7fb">devx</text>
|
|
35
|
-
<text x="112.0" y="62.0" class="t" fill="#5f6b7a">v1.9.
|
|
35
|
+
<text x="112.0" y="62.0" class="t" fill="#5f6b7a">v1.9.2</text>
|
|
36
36
|
<text x="61.6" y="106.0" class="t" fill="#5f6b7a">model</text>
|
|
37
37
|
<text x="128.8" y="106.0" class="t" fill="#e5eaf1">deepseek-v4.1-flash · think high</text>
|
|
38
38
|
<text x="406.0" y="106.0" class="t" fill="#5f6b7a">/model</text>
|
package/dist/cli/finder.js
CHANGED
|
@@ -38,10 +38,14 @@ function hintLine(width, typing) {
|
|
|
38
38
|
return '';
|
|
39
39
|
}
|
|
40
40
|
/** The card around some content rows; `rows` are already padded to the inner width by the caller's `box`. */
|
|
41
|
-
|
|
41
|
+
/** The card every list and field is drawn in: a title and a subtitle in the border, a body, a line of hints under it. */
|
|
42
|
+
export function frame(title, subtitle, cols, body, footer) {
|
|
42
43
|
if (cols < 60) {
|
|
43
44
|
const width = Math.max(20, cols - 1);
|
|
44
|
-
|
|
45
|
+
// Never wider than the screen, however long the title is in the language of the interface.
|
|
46
|
+
const shownTitle = truncateWidth(title, Math.max(4, width - 1));
|
|
47
|
+
const subRoom = width - 1 - displayWidth(shownTitle) - 3;
|
|
48
|
+
const head = ' ' + palette.bold(palette.accent(shownTitle)) + (subtitle && subRoom >= 6 ? palette.faint(' · ' + truncateWidth(subtitle, subRoom)) : '');
|
|
45
49
|
const plain = (c, shaded = false) => (shaded ? palette.shade(padEndWidth(' ' + c, width)) : ' ' + c);
|
|
46
50
|
return [head, ...body(width - 1, plain), ' ' + footer];
|
|
47
51
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -34,7 +34,7 @@ import { MarkdownStreamer, renderMarkdown } from './markdown.js';
|
|
|
34
34
|
import { SmoothStreamer } from './smooth.js';
|
|
35
35
|
import { askPrompt } from './prompt.js';
|
|
36
36
|
import { resolveAtMentions } from './files.js';
|
|
37
|
-
import {
|
|
37
|
+
import { runManualUpdate, runStartupUpdateCheck } from './update-screen.js';
|
|
38
38
|
import { setActiveTheme, getCurrentTheme, listThemes, findTheme } from './theme.js';
|
|
39
39
|
import { runHeadlessMode } from './headless.js';
|
|
40
40
|
import { CustomCommandManager } from '../core/commands.js';
|
|
@@ -934,7 +934,7 @@ export async function main() {
|
|
|
934
934
|
history.addMessage({ role: 'system', content: sysPrompt });
|
|
935
935
|
const sessionManager = new SessionManager(config.model, planMode);
|
|
936
936
|
drawLogo(config.banner || 'full');
|
|
937
|
-
await runStartupUpdateCheck(config);
|
|
937
|
+
await runStartupUpdateCheck(config, { save: saveConfig });
|
|
938
938
|
await MCPManager.getInstance().init(undefined, config.projectMcpTrusted === true);
|
|
939
939
|
// Ask a custom endpoint what the current model can do (cached for a day; never blocks the prompt).
|
|
940
940
|
void refreshEndpoint(config).catch(() => null);
|
|
@@ -1275,20 +1275,7 @@ export async function main() {
|
|
|
1275
1275
|
continue;
|
|
1276
1276
|
}
|
|
1277
1277
|
if (cmd === '/update') {
|
|
1278
|
-
|
|
1279
|
-
s.start('Checking for updates from https://github.com/apvcode/Termux-Dev...');
|
|
1280
|
-
const res = await checkForUpdates(10000);
|
|
1281
|
-
s.stop();
|
|
1282
|
-
if (res.updateAvailable) {
|
|
1283
|
-
log.info(pc.bold(pc.yellow(`Update available: v${res.currentVersion} → v${res.latestVersion}`)));
|
|
1284
|
-
const doUpdate = await askConfirm({ title: 'update', question: t('confirm.update', { version: res.latestVersion }), initial: true });
|
|
1285
|
-
if (doUpdate) {
|
|
1286
|
-
await performSelfUpdate(res.latestVersion);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
else {
|
|
1290
|
-
log.success(pc.green(`devx is up to date! (v${res.currentVersion})`));
|
|
1291
|
-
}
|
|
1278
|
+
await runManualUpdate();
|
|
1292
1279
|
continue;
|
|
1293
1280
|
}
|
|
1294
1281
|
if (cmd === '/help') {
|
package/dist/cli/liveblock.js
CHANGED
|
@@ -61,6 +61,15 @@ export class LiveBlock {
|
|
|
61
61
|
this.out.write(`\r${rows > 1 ? `\x1b[${rows - 1}A` : ''}\x1b[J\x1b[?25h`);
|
|
62
62
|
this.widths = [];
|
|
63
63
|
}
|
|
64
|
+
/** Leaves what is drawn on the screen as it is: the cursor comes back under it and the next draw starts a new block. */
|
|
65
|
+
release() {
|
|
66
|
+
if (this.widths.length === 0) {
|
|
67
|
+
this.out.write('\x1b[?25h');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.out.write('\n\x1b[?25h');
|
|
71
|
+
this.widths = [];
|
|
72
|
+
}
|
|
64
73
|
/**
|
|
65
74
|
* Calls `redraw` when the window is resized, so the block is drawn again at the new width.
|
|
66
75
|
* Returns the function that stops watching (call it when the panel closes).
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { frame } from './finder.js';
|
|
2
|
+
import { LiveBlock } from './liveblock.js';
|
|
3
|
+
import { runPicker } from './picker.js';
|
|
4
|
+
import { palette, renderNotice } from './ui.js';
|
|
5
|
+
import { displayWidth, truncateWidth } from './width.js';
|
|
6
|
+
import { getLanguage, t } from '../core/i18n.js';
|
|
7
|
+
import { UpdateError, buildUpdateSteps, checkForUpdates, detectInstallMethod, getCurrentVersion, restartDevx, runUpdateSteps, } from './updater.js';
|
|
8
|
+
/**
|
|
9
|
+
* The update screens, in the style of the rest of devx:
|
|
10
|
+
*
|
|
11
|
+
* checking one line, the text with a bright band sweeping across it
|
|
12
|
+
* card what is new (from the notes of the release), how it will be installed
|
|
13
|
+
* choice Update now / Later / Skip this version
|
|
14
|
+
* progress the steps with a spinner and the time of each, and a bar that fills as they go
|
|
15
|
+
* result installed (the bar drains while devx restarts) or what failed and how to do it by hand
|
|
16
|
+
*
|
|
17
|
+
* Every screen is a pure function of its state and the animation tick, so it can be tested without
|
|
18
|
+
* a terminal; `presentUpdate` only adds the timer, the keyboard and the install.
|
|
19
|
+
*/
|
|
20
|
+
export const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
21
|
+
const TICK_MS = 80;
|
|
22
|
+
// ───────────────────────── animation pieces ─────────────────────────
|
|
23
|
+
/** The text with a bright band moving across it; `tick` is the frame of the animation. */
|
|
24
|
+
export function shimmer(text, tick) {
|
|
25
|
+
const chars = Array.from(text);
|
|
26
|
+
const position = ((tick * 1.2) % (chars.length + 10)) - 5;
|
|
27
|
+
return chars
|
|
28
|
+
.map((ch, i) => {
|
|
29
|
+
if (ch === ' ')
|
|
30
|
+
return ch;
|
|
31
|
+
const d = Math.abs(i - position);
|
|
32
|
+
if (d < 1.2)
|
|
33
|
+
return palette.bold(palette.text(ch));
|
|
34
|
+
if (d < 3.5)
|
|
35
|
+
return palette.accent(ch);
|
|
36
|
+
return palette.muted(ch);
|
|
37
|
+
})
|
|
38
|
+
.join('');
|
|
39
|
+
}
|
|
40
|
+
/** A bar of exactly `width` cells. While busy a bright spot runs along the filled part. */
|
|
41
|
+
export function renderBar(width, progress, tick, tone = 'busy') {
|
|
42
|
+
const w = Math.max(4, width);
|
|
43
|
+
const filled = Math.max(0, Math.min(w, Math.round(progress * w)));
|
|
44
|
+
const color = tone === 'done' ? palette.ok : tone === 'failed' ? palette.err : palette.accent;
|
|
45
|
+
const spot = tone === 'busy' && filled > 0 ? ((tick * 1.5) % (filled + 6)) - 3 : -99;
|
|
46
|
+
let out = '';
|
|
47
|
+
for (let i = 0; i < w; i++) {
|
|
48
|
+
if (i < filled)
|
|
49
|
+
out += Math.abs(i - spot) < 1.5 ? palette.bold(palette.text('━')) : color('━');
|
|
50
|
+
else if (i === filled && tone === 'busy')
|
|
51
|
+
out += color('╸');
|
|
52
|
+
else
|
|
53
|
+
out += palette.rule('─');
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/** How long each step weighs in the bar: the install is nearly all of the wait. */
|
|
58
|
+
const WEIGHT = { registry: 0.1, install: 0.75, verify: 0.15, pull: 0.15, build: 0.75, link: 0.1 };
|
|
59
|
+
/**
|
|
60
|
+
* How full the bar is. The real progress of an install is not known, so the step that is running
|
|
61
|
+
* fills asymptotically: it keeps moving, and it never reaches the end before the step is done.
|
|
62
|
+
*/
|
|
63
|
+
export function estimateProgress(ids, doneCount, runningMs, expectedMs = 15000) {
|
|
64
|
+
const total = ids.reduce((sum, id) => sum + WEIGHT[id], 0);
|
|
65
|
+
if (doneCount >= ids.length)
|
|
66
|
+
return 1;
|
|
67
|
+
const done = ids.slice(0, doneCount).reduce((sum, id) => sum + WEIGHT[id], 0);
|
|
68
|
+
const running = WEIGHT[ids[doneCount]] * (1 - Math.exp(-runningMs / expectedMs)) * 0.92;
|
|
69
|
+
return Math.min(0.99, (done + running) / total);
|
|
70
|
+
}
|
|
71
|
+
const seconds = (ms) => `${(ms / 1000).toFixed(1)}s`;
|
|
72
|
+
// ───────────────────────── what is new ─────────────────────────
|
|
73
|
+
const SECTION_NAMES = {
|
|
74
|
+
en: /english/i, ru: /русск/i, uk: /україн/i, de: /deutsch/i, es: /español/i, fr: /français/i, pt: /português/i, zh: /中文/, ja: /日本語/,
|
|
75
|
+
};
|
|
76
|
+
function cleanMarkdown(text) {
|
|
77
|
+
return text
|
|
78
|
+
.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
|
|
79
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
80
|
+
.replace(/[`*_]/g, '')
|
|
81
|
+
.replace(/\s+/g, ' ')
|
|
82
|
+
.trim();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The highlights of a release: the top-level bullets under its first `###` heading, in the language
|
|
86
|
+
* of the interface when the notes have a section for it (`## Русский`), else the first section.
|
|
87
|
+
*/
|
|
88
|
+
export function extractHighlights(body, language, max = 4) {
|
|
89
|
+
if (!body)
|
|
90
|
+
return [];
|
|
91
|
+
const lines = body.replace(/\r/g, '').split('\n');
|
|
92
|
+
const sections = [];
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const heading = /^##\s+(.+?)\s*$/.exec(line);
|
|
95
|
+
if (heading)
|
|
96
|
+
sections.push({ name: heading[1], lines: [] });
|
|
97
|
+
else
|
|
98
|
+
sections[sections.length - 1]?.lines.push(line);
|
|
99
|
+
}
|
|
100
|
+
const pool = sections.length > 0 ? sections : [{ name: '', lines }];
|
|
101
|
+
const own = SECTION_NAMES[language];
|
|
102
|
+
const chosen = (own && pool.find((s) => own.test(s.name))) || pool.find((s) => SECTION_NAMES.en.test(s.name)) || pool[0];
|
|
103
|
+
const out = [];
|
|
104
|
+
let seenHeading = false;
|
|
105
|
+
for (const line of chosen.lines) {
|
|
106
|
+
if (/^###\s/.test(line)) {
|
|
107
|
+
// The bullets of the first block only ("Highlights"): the next heading ends them.
|
|
108
|
+
if (seenHeading && out.length > 0)
|
|
109
|
+
break;
|
|
110
|
+
seenHeading = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const bullet = /^-\s+(.+)$/.exec(line);
|
|
114
|
+
if (bullet) {
|
|
115
|
+
const text = cleanMarkdown(bullet[1]);
|
|
116
|
+
if (text)
|
|
117
|
+
out.push(text);
|
|
118
|
+
if (out.length >= max)
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
/** `Your own providers. A name, an address…` -> the short head and the rest. */
|
|
125
|
+
function splitHighlight(text) {
|
|
126
|
+
const at = text.search(/[.:] /);
|
|
127
|
+
if (at > 0 && at <= 50)
|
|
128
|
+
return { head: text.slice(0, at + 1), rest: text.slice(at + 2) };
|
|
129
|
+
return { head: text, rest: '' };
|
|
130
|
+
}
|
|
131
|
+
const versions = (current, latest) => `v${current} → v${latest}`;
|
|
132
|
+
/** The line while the check runs. */
|
|
133
|
+
export function renderChecking(cols, tick, current) {
|
|
134
|
+
const limit = Math.max(10, cols - 1);
|
|
135
|
+
const spin = palette.accent(SPINNER_FRAMES[tick % SPINNER_FRAMES.length]);
|
|
136
|
+
// Two cells of indent, the spinner and a space come first; the text is cut to what is left.
|
|
137
|
+
const text = truncateWidth(t('update.checking'), Math.max(4, limit - 4));
|
|
138
|
+
const label = shimmer(text, tick);
|
|
139
|
+
const withTag = ` ${spin} ${label} ${palette.faint(`v${current}`)}`;
|
|
140
|
+
return displayWidth(withTag) <= limit ? withTag : ` ${spin} ${label}`;
|
|
141
|
+
}
|
|
142
|
+
/** The card that says a new version is out and what is in it. */
|
|
143
|
+
export function renderUpdateCard(info, cols) {
|
|
144
|
+
const how = info.method === 'npm' ? t('update.viaNpm') : t('update.viaGit');
|
|
145
|
+
return frame(t('update.title'), versions(info.current, info.latest), cols, (iw, box) => {
|
|
146
|
+
const lines = [];
|
|
147
|
+
lines.push(box(' ' + palette.bold(palette.accent(truncateWidth(t('update.available', { version: info.latest }), iw - 2)))));
|
|
148
|
+
const date = info.publishedAt ? info.publishedAt.slice(0, 10) : '';
|
|
149
|
+
const meta = [t('update.have', { version: info.current }), date ? t('update.released', { date }) : ''].filter(Boolean).join(' · ');
|
|
150
|
+
lines.push(box(' ' + palette.muted(truncateWidth(meta, iw - 2))));
|
|
151
|
+
if (info.highlights.length > 0) {
|
|
152
|
+
lines.push(box(''));
|
|
153
|
+
lines.push(box(' ' + palette.faint(truncateWidth(t('update.whatsNew').toUpperCase(), iw - 2))));
|
|
154
|
+
for (const text of info.highlights) {
|
|
155
|
+
const { head, rest } = splitHighlight(text);
|
|
156
|
+
const room = iw - 4;
|
|
157
|
+
const headText = truncateWidth(head, room);
|
|
158
|
+
const restRoom = room - displayWidth(headText) - 1;
|
|
159
|
+
const tailText = rest && restRoom >= 8 ? ' ' + palette.muted(truncateWidth(rest, restRoom)) : '';
|
|
160
|
+
lines.push(box(' ' + palette.accent('●') + ' ' + palette.bold(palette.text(headText)) + tailText));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
lines.push(box(''));
|
|
164
|
+
return lines;
|
|
165
|
+
}, palette.faint(truncateWidth(how, Math.max(10, cols - 3))));
|
|
166
|
+
}
|
|
167
|
+
/** The steps with a spinner on the one that runs, and the bar. */
|
|
168
|
+
export function renderProgressCard(state, cols, tick) {
|
|
169
|
+
const done = state.tone === 'done';
|
|
170
|
+
return frame(done ? t('update.titleDone') : t('update.titleBusy'), versions(state.current, state.latest), cols, (iw, box) => {
|
|
171
|
+
const lines = [];
|
|
172
|
+
for (const step of state.steps) {
|
|
173
|
+
const icon = step.status === 'ok' ? palette.ok('✓')
|
|
174
|
+
: step.status === 'run' ? palette.accent(SPINNER_FRAMES[tick % SPINNER_FRAMES.length])
|
|
175
|
+
: step.status === 'fail' ? palette.err('✗')
|
|
176
|
+
: palette.faint('○');
|
|
177
|
+
const time = step.ms !== undefined ? palette.faint(seconds(step.ms)) : '';
|
|
178
|
+
const room = Math.max(6, iw - 4 - displayWidth(time) - 1);
|
|
179
|
+
const text = truncateWidth(step.label, room);
|
|
180
|
+
const label = step.status === 'run' ? palette.bold(palette.text(text))
|
|
181
|
+
: step.status === 'wait' ? palette.faint(text)
|
|
182
|
+
: step.status === 'fail' ? palette.err(text)
|
|
183
|
+
: palette.muted(text);
|
|
184
|
+
const left = ' ' + icon + ' ' + label;
|
|
185
|
+
lines.push(box(left + ' '.repeat(Math.max(1, iw - displayWidth(left) - displayWidth(time) - 1)) + time));
|
|
186
|
+
}
|
|
187
|
+
lines.push(box(''));
|
|
188
|
+
lines.push(box(' ' + renderBar(iw - 2, state.progress, tick, state.tone)));
|
|
189
|
+
if (state.restarting)
|
|
190
|
+
lines.push(box(' ' + palette.muted(truncateWidth(t('update.restarting'), iw - 2))));
|
|
191
|
+
lines.push(box(''));
|
|
192
|
+
return lines;
|
|
193
|
+
}, palette.faint(truncateWidth(done ? t('update.installed', { version: state.latest }) : t('update.keepOpen'), Math.max(10, cols - 3))));
|
|
194
|
+
}
|
|
195
|
+
/** The command that does the update by hand. */
|
|
196
|
+
export const manualCommand = (method) => (method === 'npm' ? 'npm install -g termux-dev@latest' : 'git pull && npm install && npm run build');
|
|
197
|
+
/** What failed, and how to do it by hand. devx goes on with the version it has. */
|
|
198
|
+
export function renderFailureCard(info, cols) {
|
|
199
|
+
return frame(t('update.titleFailed'), versions(info.current, info.latest), cols, (iw, box) => {
|
|
200
|
+
const lines = [];
|
|
201
|
+
lines.push(box(' ' + palette.err('✗') + ' ' + palette.err(truncateWidth(t('update.failedStep', { step: info.step }), iw - 4))));
|
|
202
|
+
const detail = info.notPublished
|
|
203
|
+
? [t('update.notPublished', { version: info.latest })]
|
|
204
|
+
: info.message.split('\n').map((l) => l.trim()).filter(Boolean).slice(-4);
|
|
205
|
+
for (const line of detail)
|
|
206
|
+
lines.push(box(' ' + palette.muted(truncateWidth(line, iw - 4))));
|
|
207
|
+
lines.push(box(''));
|
|
208
|
+
lines.push(box(' ' + palette.text(truncateWidth(t('update.keepsRunning', { version: info.current }), iw - 2))));
|
|
209
|
+
lines.push(box(''));
|
|
210
|
+
lines.push(box(' ' + palette.faint(truncateWidth(t('update.byHand'), iw - 2))));
|
|
211
|
+
lines.push(box(' ' + palette.bold(palette.text(truncateWidth(manualCommand(info.method), iw - 4)))));
|
|
212
|
+
lines.push(box(''));
|
|
213
|
+
return lines;
|
|
214
|
+
}, '');
|
|
215
|
+
}
|
|
216
|
+
const columnsOf = (out) => out.columns || process.stdout.columns || 80;
|
|
217
|
+
const write = (out, lines) => out.write(lines.join('\n') + '\n');
|
|
218
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
219
|
+
/**
|
|
220
|
+
* Shows the card for a new version, asks what to do and, when told to, installs it on a live
|
|
221
|
+
* screen and restarts devx. `offerSkip` adds "Skip this version" (the start-up check, not /update);
|
|
222
|
+
* `save` is told which version was skipped.
|
|
223
|
+
*/
|
|
224
|
+
export async function presentUpdate(info, opts, deps = {}) {
|
|
225
|
+
const out = deps.output ?? process.stdout;
|
|
226
|
+
const now = deps.now ?? Date.now;
|
|
227
|
+
const install = deps.install ?? (await detectInstallMethod());
|
|
228
|
+
const cols = columnsOf(out);
|
|
229
|
+
write(out, [
|
|
230
|
+
'',
|
|
231
|
+
...renderUpdateCard({ current: info.currentVersion, latest: info.latestVersion, publishedAt: info.publishedAt, highlights: extractHighlights(info.releaseNotes, getLanguage()), method: install.method }, cols),
|
|
232
|
+
'',
|
|
233
|
+
]);
|
|
234
|
+
// Nobody to ask (a pipe): the card is all there is.
|
|
235
|
+
if (!out.isTTY)
|
|
236
|
+
return 'later';
|
|
237
|
+
const picker = deps.picker ?? runPicker;
|
|
238
|
+
const labels = [t('update.now'), t('update.later'), t('update.skip')];
|
|
239
|
+
const choice = await picker({
|
|
240
|
+
title: t('update.title'),
|
|
241
|
+
subtitle: versions(info.currentVersion, info.latestVersion),
|
|
242
|
+
items: [
|
|
243
|
+
{ value: 'now', label: t('update.now'), detail: t('update.nowDetail', { version: info.latestVersion }) },
|
|
244
|
+
{ value: 'later', label: t('update.later'), detail: t('update.laterDetail') },
|
|
245
|
+
...(opts.offerSkip ? [{ value: 'skip', label: t('update.skip'), detail: t('update.skipDetail', { version: info.latestVersion }) }] : []),
|
|
246
|
+
],
|
|
247
|
+
current: 'now',
|
|
248
|
+
labelWidth: Math.max(...labels.slice(0, opts.offerSkip ? 3 : 2).map((l) => displayWidth(l))) + 2,
|
|
249
|
+
});
|
|
250
|
+
if (choice === 'skip') {
|
|
251
|
+
await opts.save?.(info.latestVersion);
|
|
252
|
+
out.write(renderNotice('info', t('update.skipped', { version: info.latestVersion }), cols) + '\n\n');
|
|
253
|
+
return 'skipped';
|
|
254
|
+
}
|
|
255
|
+
if (choice !== 'now')
|
|
256
|
+
return 'later';
|
|
257
|
+
// ── the install, on a screen that is redrawn in place
|
|
258
|
+
const steps = buildUpdateSteps(install.method, install.root, info.latestVersion, deps.run);
|
|
259
|
+
const ids = steps.map((step) => step.id);
|
|
260
|
+
const state = {
|
|
261
|
+
current: info.currentVersion,
|
|
262
|
+
latest: info.latestVersion,
|
|
263
|
+
steps: ids.map((id, i) => ({ label: t(`update.step.${id}`, { version: info.latestVersion }), status: i === 0 ? 'run' : 'wait' })),
|
|
264
|
+
progress: 0,
|
|
265
|
+
tone: 'busy',
|
|
266
|
+
};
|
|
267
|
+
const block = new LiveBlock(out);
|
|
268
|
+
let tick = 0;
|
|
269
|
+
let runningSince = now();
|
|
270
|
+
let restartStart = 0;
|
|
271
|
+
const restartMs = deps.restartMs ?? 1500;
|
|
272
|
+
const draw = () => {
|
|
273
|
+
if (state.restarting)
|
|
274
|
+
state.progress = Math.max(0, 1 - (now() - restartStart) / restartMs);
|
|
275
|
+
else {
|
|
276
|
+
const doneCount = state.steps.filter((s) => s.status === 'ok').length;
|
|
277
|
+
state.progress = state.tone === 'failed' ? state.progress : estimateProgress(ids, doneCount, now() - runningSince);
|
|
278
|
+
}
|
|
279
|
+
block.draw(renderProgressCard(state, columnsOf(out), tick++));
|
|
280
|
+
};
|
|
281
|
+
draw();
|
|
282
|
+
const timer = setInterval(draw, deps.tickMs ?? TICK_MS);
|
|
283
|
+
timer.unref?.();
|
|
284
|
+
const stopWatching = block.watchResize(draw);
|
|
285
|
+
const outcome = await runUpdateSteps(steps, {
|
|
286
|
+
onStart: (i) => {
|
|
287
|
+
state.steps[i].status = 'run';
|
|
288
|
+
runningSince = now();
|
|
289
|
+
},
|
|
290
|
+
onDone: (i, ms) => {
|
|
291
|
+
state.steps[i].status = 'ok';
|
|
292
|
+
state.steps[i].ms = ms;
|
|
293
|
+
if (state.steps[i + 1])
|
|
294
|
+
state.steps[i + 1].status = 'run';
|
|
295
|
+
},
|
|
296
|
+
}, now);
|
|
297
|
+
if (!outcome.ok) {
|
|
298
|
+
clearInterval(timer);
|
|
299
|
+
stopWatching();
|
|
300
|
+
state.steps[outcome.index].status = 'fail';
|
|
301
|
+
state.tone = 'failed';
|
|
302
|
+
block.erase();
|
|
303
|
+
write(out, renderFailureCard({ current: info.currentVersion, latest: info.latestVersion, step: state.steps[outcome.index].label, message: outcome.error.message, notPublished: outcome.error.kind === 'not-published', method: install.method }, columnsOf(out)));
|
|
304
|
+
out.write('\x1b[?25h\n');
|
|
305
|
+
return 'failed';
|
|
306
|
+
}
|
|
307
|
+
// Installed: the bar turns green and drains while devx restarts.
|
|
308
|
+
state.tone = 'done';
|
|
309
|
+
state.restarting = true;
|
|
310
|
+
restartStart = now();
|
|
311
|
+
await sleep(restartMs);
|
|
312
|
+
clearInterval(timer);
|
|
313
|
+
stopWatching();
|
|
314
|
+
state.progress = 0;
|
|
315
|
+
block.draw(renderProgressCard(state, columnsOf(out), tick));
|
|
316
|
+
block.release();
|
|
317
|
+
(deps.restart ?? restartDevx)();
|
|
318
|
+
return 'updated';
|
|
319
|
+
}
|
|
320
|
+
// ───────────────────────── the check ─────────────────────────
|
|
321
|
+
/** Runs `work` while the "checking" line plays, and takes the line away when it is done. */
|
|
322
|
+
async function withCheckingLine(out, current, work, tickMs = TICK_MS) {
|
|
323
|
+
if (!out.isTTY)
|
|
324
|
+
return work();
|
|
325
|
+
const block = new LiveBlock(out);
|
|
326
|
+
let tick = 0;
|
|
327
|
+
const draw = () => block.draw([renderChecking(columnsOf(out), tick++, current)]);
|
|
328
|
+
draw();
|
|
329
|
+
const timer = setInterval(draw, tickMs);
|
|
330
|
+
timer.unref?.();
|
|
331
|
+
try {
|
|
332
|
+
return await work();
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
clearInterval(timer);
|
|
336
|
+
block.erase();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/** At start-up: look for a new version; say so only when there is one (and it was not skipped). */
|
|
340
|
+
export async function runStartupUpdateCheck(config, deps = {}) {
|
|
341
|
+
if (config.checkUpdates === false)
|
|
342
|
+
return;
|
|
343
|
+
const out = deps.output ?? process.stdout;
|
|
344
|
+
if (!out.isTTY)
|
|
345
|
+
return;
|
|
346
|
+
const current = await getCurrentVersion();
|
|
347
|
+
const result = await withCheckingLine(out, current, () => (deps.check ?? checkForUpdates)(6000));
|
|
348
|
+
if (result.updateAvailable) {
|
|
349
|
+
if (config.skippedUpdate === result.latestVersion)
|
|
350
|
+
return;
|
|
351
|
+
await presentUpdate(result, {
|
|
352
|
+
offerSkip: true,
|
|
353
|
+
save: async (version) => {
|
|
354
|
+
config.skippedUpdate = version;
|
|
355
|
+
await deps.save?.(config);
|
|
356
|
+
},
|
|
357
|
+
}, deps);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (result.error)
|
|
361
|
+
return; // offline at start-up is not worth a word
|
|
362
|
+
// Up to date: a short line that goes away by itself.
|
|
363
|
+
out.write(` ${palette.ok('✓')} ${palette.muted(t('update.upToDate', { version: result.currentVersion }))}\n`);
|
|
364
|
+
await sleep(deps.tickMs !== undefined ? 0 : 450);
|
|
365
|
+
out.write('\x1b[1A\x1b[2K');
|
|
366
|
+
}
|
|
367
|
+
/** `/update`: the same, but it always answers (also "up to date" and "could not check"). */
|
|
368
|
+
export async function runManualUpdate(deps = {}) {
|
|
369
|
+
const out = deps.output ?? process.stdout;
|
|
370
|
+
const current = await getCurrentVersion();
|
|
371
|
+
const result = await withCheckingLine(out, current, () => (deps.check ?? checkForUpdates)(10000));
|
|
372
|
+
const cols = columnsOf(out);
|
|
373
|
+
if (result.updateAvailable) {
|
|
374
|
+
await presentUpdate(result, { offerSkip: false }, deps);
|
|
375
|
+
}
|
|
376
|
+
else if (result.error) {
|
|
377
|
+
out.write(renderNotice('warn', t('update.offline', { reason: result.error }), cols) + '\n');
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
out.write(renderNotice('ok', t('update.upToDate', { version: result.currentVersion }), cols) + '\n');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
export { UpdateError };
|
package/dist/cli/updater.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
|
-
import pc from 'picocolors';
|
|
5
|
-
import { select } from '@inquirer/prompts';
|
|
6
|
-
import { palette } from './ui.js';
|
|
7
4
|
import { fileURLToPath } from 'url';
|
|
8
5
|
const GITHUB_REPO_URL = 'https://github.com/apvcode/Termux-Dev';
|
|
9
|
-
|
|
10
|
-
const
|
|
6
|
+
// Where to look for the newest version. Overridable (a mirror, a test server).
|
|
7
|
+
const REMOTE_PKG_URL = process.env.DEVX_UPDATE_PKG_URL || 'https://raw.githubusercontent.com/apvcode/Termux-Dev/main/package.json';
|
|
8
|
+
const REMOTE_RELEASE_URL = process.env.DEVX_UPDATE_RELEASE_URL || 'https://api.github.com/repos/apvcode/Termux-Dev/releases/latest';
|
|
11
9
|
// Compare semantic versions (e.g. 1.1.0 > 1.0.0)
|
|
12
10
|
export function isNewerVersion(remote, current) {
|
|
13
11
|
const clean = (v) => v.replace(/^v/, '').trim();
|
|
@@ -54,6 +52,7 @@ export async function checkForUpdates(timeoutMs = 10000) {
|
|
|
54
52
|
});
|
|
55
53
|
let latestVersion = '';
|
|
56
54
|
let releaseNotes = '';
|
|
55
|
+
let publishedAt = '';
|
|
57
56
|
if (res.ok) {
|
|
58
57
|
const pkg = await res.json();
|
|
59
58
|
latestVersion = (pkg.version || '').replace(/^v/, '').trim();
|
|
@@ -71,6 +70,7 @@ export async function checkForUpdates(timeoutMs = 10000) {
|
|
|
71
70
|
latestVersion = relTag;
|
|
72
71
|
}
|
|
73
72
|
releaseNotes = relData.body || '';
|
|
73
|
+
publishedAt = relData.published_at || '';
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
catch { }
|
|
@@ -79,7 +79,8 @@ export async function checkForUpdates(timeoutMs = 10000) {
|
|
|
79
79
|
updateAvailable: true,
|
|
80
80
|
currentVersion,
|
|
81
81
|
latestVersion,
|
|
82
|
-
releaseNotes
|
|
82
|
+
releaseNotes,
|
|
83
|
+
publishedAt: publishedAt || undefined
|
|
83
84
|
};
|
|
84
85
|
}
|
|
85
86
|
return {
|
|
@@ -98,163 +99,136 @@ export async function checkForUpdates(timeoutMs = 10000) {
|
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
+
/** How this devx was installed: from the npm package, or from a git clone (a developer's copy). */
|
|
103
|
+
export async function detectInstallMethod() {
|
|
104
|
+
const here = fileURLToPath(import.meta.url);
|
|
105
|
+
const root = path.resolve(path.dirname(here), '../../');
|
|
106
|
+
// DEVX_UPDATE_METHOD forces one of them (a copy in a git folder that is installed with npm, a test).
|
|
107
|
+
const forced = (process.env.DEVX_UPDATE_METHOD || '').toLowerCase();
|
|
108
|
+
if (forced === 'npm' || forced === 'git')
|
|
109
|
+
return { method: forced, root };
|
|
102
110
|
try {
|
|
103
|
-
await fs.stat(path.join(
|
|
104
|
-
return
|
|
111
|
+
await fs.stat(path.join(root, '.git'));
|
|
112
|
+
return { method: 'git', root };
|
|
105
113
|
}
|
|
106
114
|
catch {
|
|
107
|
-
return
|
|
115
|
+
return { method: 'npm', root };
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
process.stdout.write(` ${pc.cyan('›')} [1/1] ${pc.white(`Updating termux-dev via npm (v${latestVersion})...`)}\n`);
|
|
118
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
119
|
-
let fIdx = 0;
|
|
120
|
-
const interval = setInterval(() => {
|
|
121
|
-
process.stdout.write(`\r ${pc.cyan(frames[fIdx++ % frames.length])} ${pc.dim(`npm install -g termux-dev@latest`)} `);
|
|
122
|
-
}, 80);
|
|
123
|
-
try {
|
|
124
|
-
execSync('npm install -g termux-dev@latest --prefer-online', { stdio: 'pipe' });
|
|
125
|
-
clearInterval(interval);
|
|
126
|
-
process.stdout.write(`\r ${pc.green('✓')} ${pc.bold(pc.white(`Successfully updated termux-dev to v${latestVersion} via npm!`))}\n`);
|
|
127
|
-
}
|
|
128
|
-
catch (err) {
|
|
129
|
-
clearInterval(interval);
|
|
130
|
-
process.stdout.write(`\r ${pc.red('✖')} ${pc.red(`Update failed: ${err.message}`)}\n`);
|
|
131
|
-
console.log(pc.yellow(`\nTo update manually, run: ${pc.bold('npm install -g termux-dev@latest')}`));
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
118
|
+
/** What a step of the update says when it fails. `not-published`: the version is on GitHub but not in npm yet. */
|
|
119
|
+
export class UpdateError extends Error {
|
|
120
|
+
kind;
|
|
121
|
+
constructor(message, kind = 'failed') {
|
|
122
|
+
super(message);
|
|
123
|
+
this.kind = kind;
|
|
124
|
+
this.name = 'UpdateError';
|
|
134
125
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
126
|
+
}
|
|
127
|
+
const OUTPUT_KEEP = 4000;
|
|
128
|
+
export const runCommand = (command, args, options = {}) => new Promise((resolve) => {
|
|
129
|
+
let output = '';
|
|
130
|
+
const keep = (chunk) => {
|
|
131
|
+
output = (output + chunk.toString('utf8')).slice(-OUTPUT_KEEP);
|
|
132
|
+
};
|
|
133
|
+
// On Windows npm is a .cmd file, which only a shell can start.
|
|
134
|
+
const child = spawn(command, args, { cwd: options.cwd, shell: process.platform === 'win32', stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true });
|
|
135
|
+
child.stdout?.on('data', keep);
|
|
136
|
+
child.stderr?.on('data', keep);
|
|
137
|
+
child.on('error', (err) => resolve({ code: -1, output: output + String(err.message) }));
|
|
138
|
+
child.on('close', (code) => resolve({ code: code ?? -1, output }));
|
|
139
|
+
});
|
|
140
|
+
/** The last lines of a command's output, for the message of a failure. */
|
|
141
|
+
export function tail(output, lines = 4) {
|
|
142
|
+
return output
|
|
143
|
+
.split('\n')
|
|
144
|
+
.map((l) => l.trim())
|
|
145
|
+
.filter(Boolean)
|
|
146
|
+
.slice(-lines)
|
|
147
|
+
.join('\n');
|
|
148
|
+
}
|
|
149
|
+
function must(result, what) {
|
|
150
|
+
if (result.code !== 0)
|
|
151
|
+
throw new UpdateError(tail(result.output) || `${what} failed (exit code ${result.code})`);
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The steps of an update, in order. Nothing is run until `run()` of a step is called.
|
|
156
|
+
*
|
|
157
|
+
* npm: ask the registry whether the version is there (GitHub knows a release before npm has the
|
|
158
|
+
* package: installing `latest` then would "update" to the old version), install exactly that
|
|
159
|
+
* version, and read back which version is installed.
|
|
160
|
+
* git: pull, install and build, link the command.
|
|
161
|
+
*/
|
|
162
|
+
export function buildUpdateSteps(method, root, version, run = runCommand) {
|
|
163
|
+
if (method === 'npm') {
|
|
164
|
+
return [
|
|
138
165
|
{
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
166
|
+
id: 'registry',
|
|
167
|
+
run: async () => {
|
|
168
|
+
const res = await run('npm', ['view', `termux-dev@${version}`, 'version', '--prefer-online']);
|
|
169
|
+
if (res.code !== 0) {
|
|
170
|
+
// A version that is not in the registry is an E404, not an older answer.
|
|
171
|
+
if (/E404|No match found/i.test(res.output))
|
|
172
|
+
throw new UpdateError(`termux-dev@${version} is not in the npm registry yet`, 'not-published');
|
|
173
|
+
throw new UpdateError(tail(res.output) || `npm view failed (exit code ${res.code})`);
|
|
174
|
+
}
|
|
175
|
+
if (res.output.trim().split('\n').pop()?.trim() !== version) {
|
|
176
|
+
throw new UpdateError(`termux-dev@${version} is not in the npm registry yet`, 'not-published');
|
|
177
|
+
}
|
|
178
|
+
},
|
|
143
179
|
},
|
|
144
180
|
{
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
181
|
+
id: 'install',
|
|
182
|
+
run: async () => {
|
|
183
|
+
must(await run('npm', ['install', '-g', `termux-dev@${version}`, '--prefer-online']), 'npm install');
|
|
184
|
+
},
|
|
150
185
|
},
|
|
151
186
|
{
|
|
152
|
-
|
|
153
|
-
|
|
187
|
+
id: 'verify',
|
|
188
|
+
run: async () => {
|
|
189
|
+
const res = await run('npm', ['ls', '-g', 'termux-dev', '--depth=0', '--json']);
|
|
190
|
+
let installed = '';
|
|
154
191
|
try {
|
|
155
|
-
|
|
192
|
+
installed = JSON.parse(res.output.slice(res.output.indexOf('{')))?.dependencies?.['termux-dev']?.version ?? '';
|
|
156
193
|
}
|
|
157
194
|
catch { }
|
|
158
|
-
|
|
159
|
-
|
|
195
|
+
if (installed !== version)
|
|
196
|
+
throw new UpdateError(`npm reports ${installed || 'no version'} instead of ${version}`);
|
|
197
|
+
},
|
|
198
|
+
},
|
|
160
199
|
];
|
|
161
|
-
for (let i = 0; i < steps.length; i++) {
|
|
162
|
-
const step = steps[i];
|
|
163
|
-
process.stdout.write(` ${pc.cyan('›')} [${i + 1}/${steps.length}] ${pc.white(step.title)}\n`);
|
|
164
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
165
|
-
let fIdx = 0;
|
|
166
|
-
const interval = setInterval(() => {
|
|
167
|
-
process.stdout.write(`\r ${pc.cyan(frames[fIdx++ % frames.length])} ${pc.dim(step.title)} `);
|
|
168
|
-
}, 80);
|
|
169
|
-
try {
|
|
170
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
171
|
-
step.action();
|
|
172
|
-
clearInterval(interval);
|
|
173
|
-
process.stdout.write(`\r ${pc.green('✓')} ${pc.bold(pc.white(step.title))}\n`);
|
|
174
|
-
}
|
|
175
|
-
catch (err) {
|
|
176
|
-
clearInterval(interval);
|
|
177
|
-
process.stdout.write(`\r ${pc.red('✖')} ${pc.red(step.title)} - ${err.message}\n`);
|
|
178
|
-
console.log(pc.red(`\nUpdate failed during step ${i + 1}: ${err.message}`));
|
|
179
|
-
console.log(pc.yellow(`\nTo update manually, run: ${pc.bold('npm install -g termux-dev')}`));
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
console.log(pc.bold(pc.green(`\nSuccessfully updated to v${latestVersion}! Restarting devx...\n`)));
|
|
185
|
-
console.log(pc.bold(pc.cyan('──────────────────────────────────────────────────────\n')));
|
|
186
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
187
|
-
try {
|
|
188
|
-
const child = spawn(process.argv[0], process.argv.slice(1), {
|
|
189
|
-
stdio: 'inherit',
|
|
190
|
-
detached: true
|
|
191
|
-
});
|
|
192
|
-
child.unref();
|
|
193
|
-
process.exit(0);
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
process.exit(0);
|
|
197
200
|
}
|
|
198
|
-
return
|
|
201
|
+
return [
|
|
202
|
+
{ id: 'pull', run: async () => void must(await run('git', ['pull', '--ff-only', 'origin', 'main'], { cwd: root }), 'git pull') },
|
|
203
|
+
{
|
|
204
|
+
id: 'build',
|
|
205
|
+
run: async () => {
|
|
206
|
+
must(await run('npm', ['install'], { cwd: root }), 'npm install');
|
|
207
|
+
must(await run('npm', ['run', 'build'], { cwd: root }), 'npm run build');
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
// Linking is a convenience: a failure here does not undo the update.
|
|
211
|
+
{ id: 'link', run: async () => void (await run('npm', ['link'], { cwd: root })) },
|
|
212
|
+
];
|
|
199
213
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const currentVersion = await getCurrentVersion();
|
|
206
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
207
|
-
let fIdx = 0;
|
|
208
|
-
let isDone = false;
|
|
209
|
-
const spinner = setInterval(() => {
|
|
210
|
-
if (isDone)
|
|
211
|
-
return;
|
|
212
|
-
// The colours of the theme, like the rest of the interface.
|
|
213
|
-
const frame = palette.accent(frames[fIdx++ % frames.length]);
|
|
214
|
-
process.stdout.write(`\r ${frame} ${palette.muted('Checking for updates from')} ${palette.accent('apvcode/Termux-Dev')} ${palette.faint(`(v${currentVersion})...`)} `);
|
|
215
|
-
}, 75);
|
|
216
|
-
const result = await checkForUpdates(10000);
|
|
217
|
-
isDone = true;
|
|
218
|
-
clearInterval(spinner);
|
|
219
|
-
// Clear spinner line
|
|
220
|
-
process.stdout.write('\r\x1b[K');
|
|
221
|
-
if (result.updateAvailable) {
|
|
222
|
-
console.log();
|
|
223
|
-
console.log(palette.rule(' ┌───────────────────────────────────────────────────────────┐'));
|
|
224
|
-
console.log(palette.rule(' │') + palette.bold(palette.accent(` A new update is available: `)) + palette.muted(`v${result.currentVersion}`) + palette.bold(palette.ok(` → v${result.latestVersion}`)) + ' '.repeat(Math.max(1, 23 - result.currentVersion.length - result.latestVersion.length)) + palette.rule('│'));
|
|
225
|
-
console.log(palette.rule(' │') + palette.faint(` Repository: https://github.com/apvcode/Termux-Dev `) + palette.rule('│'));
|
|
226
|
-
console.log(palette.rule(' └───────────────────────────────────────────────────────────┘'));
|
|
227
|
-
console.log();
|
|
214
|
+
/** Runs the steps one after the other and stops at the first that fails. */
|
|
215
|
+
export async function runUpdateSteps(steps, events = {}, now = Date.now) {
|
|
216
|
+
for (let index = 0; index < steps.length; index++) {
|
|
217
|
+
events.onStart?.(index);
|
|
218
|
+
const started = now();
|
|
228
219
|
try {
|
|
229
|
-
|
|
230
|
-
message: palette.bold('Do you want to update devx now?'),
|
|
231
|
-
loop: false,
|
|
232
|
-
choices: [
|
|
233
|
-
{
|
|
234
|
-
name: `Update now (Pull v${result.latestVersion}, rebuild & restart)`,
|
|
235
|
-
value: 'update',
|
|
236
|
-
description: 'Download latest code, compile TypeScript, and restart devx'
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
name: 'Skip for now (Continue to chat)',
|
|
240
|
-
value: 'skip',
|
|
241
|
-
description: 'Keep current version and start session'
|
|
242
|
-
}
|
|
243
|
-
]
|
|
244
|
-
});
|
|
245
|
-
if (choice === 'update') {
|
|
246
|
-
await performSelfUpdate(result.latestVersion);
|
|
247
|
-
}
|
|
220
|
+
await steps[index].run();
|
|
248
221
|
}
|
|
249
|
-
catch {
|
|
250
|
-
|
|
222
|
+
catch (err) {
|
|
223
|
+
return { ok: false, index, error: err instanceof UpdateError ? err : new UpdateError(String(err?.message || err)) };
|
|
251
224
|
}
|
|
225
|
+
events.onDone?.(index, now() - started);
|
|
252
226
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
227
|
+
return { ok: true };
|
|
228
|
+
}
|
|
229
|
+
/** Starts devx again, with the same arguments, and ends this process when the new one ends. */
|
|
230
|
+
export function restartDevx() {
|
|
231
|
+
const child = spawn(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
|
|
232
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
233
|
+
child.on('error', () => process.exit(0));
|
|
260
234
|
}
|
package/dist/core/locales/de.js
CHANGED
|
@@ -225,6 +225,39 @@ export const de = {
|
|
|
225
225
|
'onboard.provider.note': 'Der Dienst, der dir die Modelle gibt. Nicht in der Liste? Füge deinen eigenen mit „Anbieter hinzufügen“ hinzu.',
|
|
226
226
|
'onboard.custom': 'Eigene Adresse',
|
|
227
227
|
'onboard.customDetail': 'Jede OpenAI-kompatible Adresse',
|
|
228
|
+
'update.title': 'Update',
|
|
229
|
+
'update.titleBusy': 'aktualisiere',
|
|
230
|
+
'update.titleDone': 'aktualisiert',
|
|
231
|
+
'update.titleFailed': 'Update fehlgeschlagen',
|
|
232
|
+
'update.checking': 'Suche nach Updates',
|
|
233
|
+
'update.upToDate': 'devx ist aktuell ({version})',
|
|
234
|
+
'update.offline': 'Update-Prüfung nicht möglich ({reason})',
|
|
235
|
+
'update.available': 'devx {version} ist da',
|
|
236
|
+
'update.have': 'Du hast {version}',
|
|
237
|
+
'update.released': 'veröffentlicht am {date}',
|
|
238
|
+
'update.whatsNew': 'Neu in dieser Version',
|
|
239
|
+
'update.viaNpm': 'Mit npm installiert: Das Update führt npm install -g termux-dev aus.',
|
|
240
|
+
'update.viaGit': 'Aus einem Git-Klon installiert: Das Update holt, baut und verlinkt ihn.',
|
|
241
|
+
'update.now': 'Jetzt aktualisieren',
|
|
242
|
+
'update.nowDetail': '{version} installieren und devx neu starten',
|
|
243
|
+
'update.later': 'Später',
|
|
244
|
+
'update.laterDetail': 'Diese Version vorerst behalten',
|
|
245
|
+
'update.skip': 'Diese Version überspringen',
|
|
246
|
+
'update.skipDetail': 'Zu {version} nicht mehr fragen',
|
|
247
|
+
'update.step.registry': 'Prüfe die Registry',
|
|
248
|
+
'update.step.install': 'Installiere devx {version}',
|
|
249
|
+
'update.step.verify': 'Prüfe die neue Version',
|
|
250
|
+
'update.step.pull': 'Hole von GitHub',
|
|
251
|
+
'update.step.build': 'Installiere und baue',
|
|
252
|
+
'update.step.link': 'Verlinke den Befehl',
|
|
253
|
+
'update.keepOpen': 'Lass devx geöffnet, das dauert einen Moment.',
|
|
254
|
+
'update.installed': 'devx {version} ist installiert',
|
|
255
|
+
'update.restarting': 'Starte devx neu…',
|
|
256
|
+
'update.failedStep': 'Gestoppt bei: {step}',
|
|
257
|
+
'update.keepsRunning': 'devx läuft weiter auf {version}.',
|
|
258
|
+
'update.byHand': 'So aktualisierst du von Hand:',
|
|
259
|
+
'update.notPublished': 'npm hat {version} noch nicht. Versuche es in ein paar Minuten erneut.',
|
|
260
|
+
'update.skipped': 'Verstanden: keine Fragen mehr zu {version}.',
|
|
228
261
|
'provider.add': 'Anbieter hinzufügen',
|
|
229
262
|
'provider.addDetail': 'Jede OpenAI- oder Anthropic-kompatible Adresse',
|
|
230
263
|
'provider.customTag': 'eigen',
|
package/dist/core/locales/en.js
CHANGED
|
@@ -236,6 +236,39 @@ export const en = {
|
|
|
236
236
|
'onboard.provider.note': 'The service that gives you the models. Not in the list? Add your own with “Add a provider”.',
|
|
237
237
|
'onboard.custom': 'Custom endpoint',
|
|
238
238
|
'onboard.customDetail': 'Any OpenAI-compatible address',
|
|
239
|
+
'update.title': 'update',
|
|
240
|
+
'update.titleBusy': 'updating',
|
|
241
|
+
'update.titleDone': 'updated',
|
|
242
|
+
'update.titleFailed': 'update failed',
|
|
243
|
+
'update.checking': 'Checking for updates',
|
|
244
|
+
'update.upToDate': 'devx is up to date ({version})',
|
|
245
|
+
'update.offline': 'Could not check for updates ({reason})',
|
|
246
|
+
'update.available': 'devx {version} is out',
|
|
247
|
+
'update.have': 'You have {version}',
|
|
248
|
+
'update.released': 'released {date}',
|
|
249
|
+
'update.whatsNew': 'What\'s new',
|
|
250
|
+
'update.viaNpm': 'Installed with npm: the update runs npm install -g termux-dev.',
|
|
251
|
+
'update.viaGit': 'Installed from a git clone: the update pulls, builds and links it.',
|
|
252
|
+
'update.now': 'Update now',
|
|
253
|
+
'update.nowDetail': 'Install {version} and restart devx',
|
|
254
|
+
'update.later': 'Later',
|
|
255
|
+
'update.laterDetail': 'Keep this version for now',
|
|
256
|
+
'update.skip': 'Skip this version',
|
|
257
|
+
'update.skipDetail': 'Do not ask again about {version}',
|
|
258
|
+
'update.step.registry': 'Checking the registry',
|
|
259
|
+
'update.step.install': 'Installing devx {version}',
|
|
260
|
+
'update.step.verify': 'Checking the new version',
|
|
261
|
+
'update.step.pull': 'Pulling from GitHub',
|
|
262
|
+
'update.step.build': 'Installing and building',
|
|
263
|
+
'update.step.link': 'Linking the command',
|
|
264
|
+
'update.keepOpen': 'Keep devx open, this takes a moment.',
|
|
265
|
+
'update.installed': 'devx {version} is installed',
|
|
266
|
+
'update.restarting': 'Restarting devx…',
|
|
267
|
+
'update.failedStep': 'Stopped at: {step}',
|
|
268
|
+
'update.keepsRunning': 'devx keeps running on {version}.',
|
|
269
|
+
'update.byHand': 'To update by hand:',
|
|
270
|
+
'update.notPublished': 'npm does not have {version} yet. Try again in a few minutes.',
|
|
271
|
+
'update.skipped': 'Understood: no more questions about {version}.',
|
|
239
272
|
'provider.add': 'Add a provider',
|
|
240
273
|
'provider.addDetail': 'Any OpenAI or Anthropic compatible address',
|
|
241
274
|
'provider.customTag': 'custom',
|
package/dist/core/locales/es.js
CHANGED
|
@@ -225,6 +225,39 @@ export const es = {
|
|
|
225
225
|
'onboard.provider.note': 'El servicio que te da los modelos. ¿No está en la lista? Añade el tuyo con «Añadir proveedor».',
|
|
226
226
|
'onboard.custom': 'Dirección propia',
|
|
227
227
|
'onboard.customDetail': 'Cualquier dirección compatible con OpenAI',
|
|
228
|
+
'update.title': 'actualización',
|
|
229
|
+
'update.titleBusy': 'actualizando',
|
|
230
|
+
'update.titleDone': 'actualizado',
|
|
231
|
+
'update.titleFailed': 'la actualización falló',
|
|
232
|
+
'update.checking': 'Buscando actualizaciones',
|
|
233
|
+
'update.upToDate': 'devx está al día ({version})',
|
|
234
|
+
'update.offline': 'No se pudo buscar actualizaciones ({reason})',
|
|
235
|
+
'update.available': 'Ya está devx {version}',
|
|
236
|
+
'update.have': 'Tienes {version}',
|
|
237
|
+
'update.released': 'publicada el {date}',
|
|
238
|
+
'update.whatsNew': 'Novedades',
|
|
239
|
+
'update.viaNpm': 'Instalado con npm: la actualización ejecuta npm install -g termux-dev.',
|
|
240
|
+
'update.viaGit': 'Instalado desde un clon de git: la actualización lo descarga, compila y enlaza.',
|
|
241
|
+
'update.now': 'Actualizar ahora',
|
|
242
|
+
'update.nowDetail': 'Instalar {version} y reiniciar devx',
|
|
243
|
+
'update.later': 'Más tarde',
|
|
244
|
+
'update.laterDetail': 'Mantener esta versión por ahora',
|
|
245
|
+
'update.skip': 'Omitir esta versión',
|
|
246
|
+
'update.skipDetail': 'No volver a preguntar por {version}',
|
|
247
|
+
'update.step.registry': 'Comprobando el registro',
|
|
248
|
+
'update.step.install': 'Instalando devx {version}',
|
|
249
|
+
'update.step.verify': 'Comprobando la nueva versión',
|
|
250
|
+
'update.step.pull': 'Descargando de GitHub',
|
|
251
|
+
'update.step.build': 'Instalando y compilando',
|
|
252
|
+
'update.step.link': 'Enlazando el comando',
|
|
253
|
+
'update.keepOpen': 'Mantén devx abierto, tarda un momento.',
|
|
254
|
+
'update.installed': 'devx {version} está instalado',
|
|
255
|
+
'update.restarting': 'Reiniciando devx…',
|
|
256
|
+
'update.failedStep': 'Se detuvo en: {step}',
|
|
257
|
+
'update.keepsRunning': 'devx sigue funcionando en {version}.',
|
|
258
|
+
'update.byHand': 'Para actualizar a mano:',
|
|
259
|
+
'update.notPublished': 'npm aún no tiene {version}. Inténtalo de nuevo en unos minutos.',
|
|
260
|
+
'update.skipped': 'Entendido: no se preguntará más por {version}.',
|
|
228
261
|
'provider.add': 'Añadir proveedor',
|
|
229
262
|
'provider.addDetail': 'Cualquier dirección compatible con OpenAI o Anthropic',
|
|
230
263
|
'provider.customTag': 'propio',
|
package/dist/core/locales/fr.js
CHANGED
|
@@ -225,6 +225,39 @@ export const fr = {
|
|
|
225
225
|
'onboard.provider.note': 'Le service qui vous donne les modèles. Pas dans la liste ? Ajoutez le vôtre avec « Ajouter un fournisseur ».',
|
|
226
226
|
'onboard.custom': 'Adresse personnalisée',
|
|
227
227
|
'onboard.customDetail': 'Toute adresse compatible OpenAI',
|
|
228
|
+
'update.title': 'mise à jour',
|
|
229
|
+
'update.titleBusy': 'mise à jour en cours',
|
|
230
|
+
'update.titleDone': 'mis à jour',
|
|
231
|
+
'update.titleFailed': 'échec de la mise à jour',
|
|
232
|
+
'update.checking': 'Recherche de mises à jour',
|
|
233
|
+
'update.upToDate': 'devx est à jour ({version})',
|
|
234
|
+
'update.offline': 'Impossible de vérifier les mises à jour ({reason})',
|
|
235
|
+
'update.available': 'devx {version} est sorti',
|
|
236
|
+
'update.have': 'Vous avez {version}',
|
|
237
|
+
'update.released': 'publiée le {date}',
|
|
238
|
+
'update.whatsNew': 'Nouveautés',
|
|
239
|
+
'update.viaNpm': 'Installé avec npm : la mise à jour lance npm install -g termux-dev.',
|
|
240
|
+
'update.viaGit': 'Installé depuis un clone git : la mise à jour le récupère, le compile et le lie.',
|
|
241
|
+
'update.now': 'Mettre à jour maintenant',
|
|
242
|
+
'update.nowDetail': 'Installer {version} et redémarrer devx',
|
|
243
|
+
'update.later': 'Plus tard',
|
|
244
|
+
'update.laterDetail': 'Garder cette version pour l’instant',
|
|
245
|
+
'update.skip': 'Ignorer cette version',
|
|
246
|
+
'update.skipDetail': 'Ne plus demander pour {version}',
|
|
247
|
+
'update.step.registry': 'Vérification du registre',
|
|
248
|
+
'update.step.install': 'Installation de devx {version}',
|
|
249
|
+
'update.step.verify': 'Vérification de la nouvelle version',
|
|
250
|
+
'update.step.pull': 'Récupération depuis GitHub',
|
|
251
|
+
'update.step.build': 'Installation et compilation',
|
|
252
|
+
'update.step.link': 'Liaison de la commande',
|
|
253
|
+
'update.keepOpen': 'Gardez devx ouvert, cela prend un instant.',
|
|
254
|
+
'update.installed': 'devx {version} est installé',
|
|
255
|
+
'update.restarting': 'Redémarrage de devx…',
|
|
256
|
+
'update.failedStep': 'Arrêté à l’étape : {step}',
|
|
257
|
+
'update.keepsRunning': 'devx continue de tourner en {version}.',
|
|
258
|
+
'update.byHand': 'Pour mettre à jour à la main :',
|
|
259
|
+
'update.notPublished': 'npm n’a pas encore {version}. Réessayez dans quelques minutes.',
|
|
260
|
+
'update.skipped': 'Compris : plus de questions sur {version}.',
|
|
228
261
|
'provider.add': 'Ajouter un fournisseur',
|
|
229
262
|
'provider.addDetail': 'Toute adresse compatible OpenAI ou Anthropic',
|
|
230
263
|
'provider.customTag': 'perso',
|
package/dist/core/locales/ja.js
CHANGED
|
@@ -225,6 +225,39 @@ export const ja = {
|
|
|
225
225
|
'onboard.provider.note': 'モデルを提供するサービスです。一覧にない場合は「プロバイダを追加」で自分のものを追加できます。',
|
|
226
226
|
'onboard.custom': '独自のアドレス',
|
|
227
227
|
'onboard.customDetail': 'OpenAI 互換の任意のアドレス',
|
|
228
|
+
'update.title': 'アップデート',
|
|
229
|
+
'update.titleBusy': '更新中',
|
|
230
|
+
'update.titleDone': '更新しました',
|
|
231
|
+
'update.titleFailed': '更新に失敗しました',
|
|
232
|
+
'update.checking': 'アップデートを確認しています',
|
|
233
|
+
'update.upToDate': 'devx は最新です({version})',
|
|
234
|
+
'update.offline': 'アップデートを確認できませんでした({reason})',
|
|
235
|
+
'update.available': 'devx {version} が公開されました',
|
|
236
|
+
'update.have': '現在のバージョンは {version} です',
|
|
237
|
+
'update.released': '{date} 公開',
|
|
238
|
+
'update.whatsNew': '新機能',
|
|
239
|
+
'update.viaNpm': 'npm でインストール済み:更新は npm install -g termux-dev を実行します。',
|
|
240
|
+
'update.viaGit': 'git クローンからインストール済み:更新は取得・ビルド・リンクを行います。',
|
|
241
|
+
'update.now': '今すぐ更新',
|
|
242
|
+
'update.nowDetail': '{version} をインストールして devx を再起動',
|
|
243
|
+
'update.later': 'あとで',
|
|
244
|
+
'update.laterDetail': '今回はこのバージョンのままにする',
|
|
245
|
+
'update.skip': 'このバージョンをスキップ',
|
|
246
|
+
'update.skipDetail': '{version} について今後は確認しない',
|
|
247
|
+
'update.step.registry': 'レジストリを確認中',
|
|
248
|
+
'update.step.install': 'devx {version} をインストール中',
|
|
249
|
+
'update.step.verify': '新しいバージョンを確認中',
|
|
250
|
+
'update.step.pull': 'GitHub から取得中',
|
|
251
|
+
'update.step.build': 'インストールとビルド中',
|
|
252
|
+
'update.step.link': 'コマンドをリンク中',
|
|
253
|
+
'update.keepOpen': 'devx を開いたままにしてください。少し時間がかかります。',
|
|
254
|
+
'update.installed': 'devx {version} をインストールしました',
|
|
255
|
+
'update.restarting': 'devx を再起動しています…',
|
|
256
|
+
'update.failedStep': '停止した手順:{step}',
|
|
257
|
+
'update.keepsRunning': 'devx は {version} のまま動作します。',
|
|
258
|
+
'update.byHand': '手動で更新するには:',
|
|
259
|
+
'update.notPublished': 'npm にはまだ {version} がありません。数分後にもう一度お試しください。',
|
|
260
|
+
'update.skipped': '了解しました:{version} についてはもう確認しません。',
|
|
228
261
|
'provider.add': 'プロバイダを追加',
|
|
229
262
|
'provider.addDetail': 'OpenAI または Anthropic 互換の任意のアドレス',
|
|
230
263
|
'provider.customTag': 'カスタム',
|
package/dist/core/locales/pt.js
CHANGED
|
@@ -225,6 +225,39 @@ export const pt = {
|
|
|
225
225
|
'onboard.provider.note': 'O serviço que fornece os modelos. Não está na lista? Adicione o seu em «Adicionar provedor».',
|
|
226
226
|
'onboard.custom': 'Endereço próprio',
|
|
227
227
|
'onboard.customDetail': 'Qualquer endereço compatível com OpenAI',
|
|
228
|
+
'update.title': 'atualização',
|
|
229
|
+
'update.titleBusy': 'atualizando',
|
|
230
|
+
'update.titleDone': 'atualizado',
|
|
231
|
+
'update.titleFailed': 'a atualização falhou',
|
|
232
|
+
'update.checking': 'Procurando atualizações',
|
|
233
|
+
'update.upToDate': 'o devx está atualizado ({version})',
|
|
234
|
+
'update.offline': 'Não foi possível procurar atualizações ({reason})',
|
|
235
|
+
'update.available': 'O devx {version} saiu',
|
|
236
|
+
'update.have': 'Você tem {version}',
|
|
237
|
+
'update.released': 'lançada em {date}',
|
|
238
|
+
'update.whatsNew': 'Novidades',
|
|
239
|
+
'update.viaNpm': 'Instalado com npm: a atualização executa npm install -g termux-dev.',
|
|
240
|
+
'update.viaGit': 'Instalado de um clone git: a atualização baixa, compila e vincula.',
|
|
241
|
+
'update.now': 'Atualizar agora',
|
|
242
|
+
'update.nowDetail': 'Instalar {version} e reiniciar o devx',
|
|
243
|
+
'update.later': 'Mais tarde',
|
|
244
|
+
'update.laterDetail': 'Manter esta versão por enquanto',
|
|
245
|
+
'update.skip': 'Ignorar esta versão',
|
|
246
|
+
'update.skipDetail': 'Não perguntar mais sobre {version}',
|
|
247
|
+
'update.step.registry': 'Verificando o registro',
|
|
248
|
+
'update.step.install': 'Instalando o devx {version}',
|
|
249
|
+
'update.step.verify': 'Verificando a nova versão',
|
|
250
|
+
'update.step.pull': 'Baixando do GitHub',
|
|
251
|
+
'update.step.build': 'Instalando e compilando',
|
|
252
|
+
'update.step.link': 'Vinculando o comando',
|
|
253
|
+
'update.keepOpen': 'Mantenha o devx aberto, leva um instante.',
|
|
254
|
+
'update.installed': 'o devx {version} está instalado',
|
|
255
|
+
'update.restarting': 'Reiniciando o devx…',
|
|
256
|
+
'update.failedStep': 'Parou em: {step}',
|
|
257
|
+
'update.keepsRunning': 'o devx continua na versão {version}.',
|
|
258
|
+
'update.byHand': 'Para atualizar manualmente:',
|
|
259
|
+
'update.notPublished': 'O npm ainda não tem {version}. Tente de novo em alguns minutos.',
|
|
260
|
+
'update.skipped': 'Entendido: sem mais perguntas sobre {version}.',
|
|
228
261
|
'provider.add': 'Adicionar provedor',
|
|
229
262
|
'provider.addDetail': 'Qualquer endereço compatível com OpenAI ou Anthropic',
|
|
230
263
|
'provider.customTag': 'próprio',
|
package/dist/core/locales/ru.js
CHANGED
|
@@ -225,6 +225,39 @@ export const ru = {
|
|
|
225
225
|
'onboard.provider.note': 'Сервис, который даёт модели. Нет в списке? Добавьте свой через «Добавить провайдера».',
|
|
226
226
|
'onboard.custom': 'Свой адрес',
|
|
227
227
|
'onboard.customDetail': 'Любой адрес, совместимый с OpenAI',
|
|
228
|
+
'update.title': 'обновление',
|
|
229
|
+
'update.titleBusy': 'обновляю',
|
|
230
|
+
'update.titleDone': 'обновлено',
|
|
231
|
+
'update.titleFailed': 'обновление не удалось',
|
|
232
|
+
'update.checking': 'Проверяю обновления',
|
|
233
|
+
'update.upToDate': 'devx последней версии ({version})',
|
|
234
|
+
'update.offline': 'Не удалось проверить обновления ({reason})',
|
|
235
|
+
'update.available': 'Вышел devx {version}',
|
|
236
|
+
'update.have': 'У вас {version}',
|
|
237
|
+
'update.released': 'выпущена {date}',
|
|
238
|
+
'update.whatsNew': 'Что нового',
|
|
239
|
+
'update.viaNpm': 'Установлен через npm: обновление выполнит npm install -g termux-dev.',
|
|
240
|
+
'update.viaGit': 'Установлен из git-клона: обновление подтянет, соберёт и подключит его.',
|
|
241
|
+
'update.now': 'Обновить сейчас',
|
|
242
|
+
'update.nowDetail': 'Установить {version} и перезапустить devx',
|
|
243
|
+
'update.later': 'Позже',
|
|
244
|
+
'update.laterDetail': 'Пока оставить эту версию',
|
|
245
|
+
'update.skip': 'Пропустить эту версию',
|
|
246
|
+
'update.skipDetail': 'Больше не спрашивать про {version}',
|
|
247
|
+
'update.step.registry': 'Проверяю реестр',
|
|
248
|
+
'update.step.install': 'Устанавливаю devx {version}',
|
|
249
|
+
'update.step.verify': 'Проверяю новую версию',
|
|
250
|
+
'update.step.pull': 'Забираю с GitHub',
|
|
251
|
+
'update.step.build': 'Устанавливаю и собираю',
|
|
252
|
+
'update.step.link': 'Подключаю команду',
|
|
253
|
+
'update.keepOpen': 'Не закрывайте devx, это займёт немного времени.',
|
|
254
|
+
'update.installed': 'devx {version} установлен',
|
|
255
|
+
'update.restarting': 'Перезапускаю devx…',
|
|
256
|
+
'update.failedStep': 'Остановилось на шаге: {step}',
|
|
257
|
+
'update.keepsRunning': 'devx продолжает работать на {version}.',
|
|
258
|
+
'update.byHand': 'Чтобы обновить вручную:',
|
|
259
|
+
'update.notPublished': 'В npm ещё нет {version}. Попробуйте через пару минут.',
|
|
260
|
+
'update.skipped': 'Понятно: про {version} больше не спрашиваю.',
|
|
228
261
|
'provider.add': 'Добавить провайдера',
|
|
229
262
|
'provider.addDetail': 'Любой адрес, совместимый с OpenAI или Anthropic',
|
|
230
263
|
'provider.customTag': 'свой',
|
package/dist/core/locales/uk.js
CHANGED
|
@@ -225,6 +225,39 @@ export const uk = {
|
|
|
225
225
|
'onboard.provider.note': 'Сервіс, що дає моделі. Немає в списку? Додайте свій через «Додати провайдера».',
|
|
226
226
|
'onboard.custom': 'Власна адреса',
|
|
227
227
|
'onboard.customDetail': 'Будь-яка адреса, сумісна з OpenAI',
|
|
228
|
+
'update.title': 'оновлення',
|
|
229
|
+
'update.titleBusy': 'оновлюю',
|
|
230
|
+
'update.titleDone': 'оновлено',
|
|
231
|
+
'update.titleFailed': 'оновлення не вдалося',
|
|
232
|
+
'update.checking': 'Перевіряю оновлення',
|
|
233
|
+
'update.upToDate': 'devx останньої версії ({version})',
|
|
234
|
+
'update.offline': 'Не вдалося перевірити оновлення ({reason})',
|
|
235
|
+
'update.available': 'Вийшов devx {version}',
|
|
236
|
+
'update.have': 'У вас {version}',
|
|
237
|
+
'update.released': 'випущено {date}',
|
|
238
|
+
'update.whatsNew': 'Що нового',
|
|
239
|
+
'update.viaNpm': 'Встановлено через npm: оновлення виконає npm install -g termux-dev.',
|
|
240
|
+
'update.viaGit': 'Встановлено з git-клону: оновлення підтягне, збере й підключить його.',
|
|
241
|
+
'update.now': 'Оновити зараз',
|
|
242
|
+
'update.nowDetail': 'Встановити {version} і перезапустити devx',
|
|
243
|
+
'update.later': 'Пізніше',
|
|
244
|
+
'update.laterDetail': 'Поки залишити цю версію',
|
|
245
|
+
'update.skip': 'Пропустити цю версію',
|
|
246
|
+
'update.skipDetail': 'Більше не питати про {version}',
|
|
247
|
+
'update.step.registry': 'Перевіряю реєстр',
|
|
248
|
+
'update.step.install': 'Встановлюю devx {version}',
|
|
249
|
+
'update.step.verify': 'Перевіряю нову версію',
|
|
250
|
+
'update.step.pull': 'Забираю з GitHub',
|
|
251
|
+
'update.step.build': 'Встановлюю і збираю',
|
|
252
|
+
'update.step.link': 'Підключаю команду',
|
|
253
|
+
'update.keepOpen': 'Не закривайте devx, це займе трохи часу.',
|
|
254
|
+
'update.installed': 'devx {version} встановлено',
|
|
255
|
+
'update.restarting': 'Перезапускаю devx…',
|
|
256
|
+
'update.failedStep': 'Зупинилося на кроці: {step}',
|
|
257
|
+
'update.keepsRunning': 'devx продовжує працювати на {version}.',
|
|
258
|
+
'update.byHand': 'Щоб оновити вручну:',
|
|
259
|
+
'update.notPublished': 'У npm ще немає {version}. Спробуйте за кілька хвилин.',
|
|
260
|
+
'update.skipped': 'Зрозуміло: про {version} більше не питаю.',
|
|
228
261
|
'provider.add': 'Додати провайдера',
|
|
229
262
|
'provider.addDetail': 'Будь-яка адреса, сумісна з OpenAI або Anthropic',
|
|
230
263
|
'provider.customTag': 'власний',
|
package/dist/core/locales/zh.js
CHANGED
|
@@ -225,6 +225,39 @@ export const zh = {
|
|
|
225
225
|
'onboard.provider.note': '为你提供模型的服务。不在列表里?用“添加提供商”添加你自己的。',
|
|
226
226
|
'onboard.custom': '自定义地址',
|
|
227
227
|
'onboard.customDetail': '任何兼容 OpenAI 的地址',
|
|
228
|
+
'update.title': '更新',
|
|
229
|
+
'update.titleBusy': '正在更新',
|
|
230
|
+
'update.titleDone': '已更新',
|
|
231
|
+
'update.titleFailed': '更新失败',
|
|
232
|
+
'update.checking': '正在检查更新',
|
|
233
|
+
'update.upToDate': 'devx 已是最新版本({version})',
|
|
234
|
+
'update.offline': '无法检查更新({reason})',
|
|
235
|
+
'update.available': 'devx {version} 已发布',
|
|
236
|
+
'update.have': '你当前是 {version}',
|
|
237
|
+
'update.released': '发布于 {date}',
|
|
238
|
+
'update.whatsNew': '新内容',
|
|
239
|
+
'update.viaNpm': '通过 npm 安装:更新会执行 npm install -g termux-dev。',
|
|
240
|
+
'update.viaGit': '从 git 克隆安装:更新会拉取、构建并链接。',
|
|
241
|
+
'update.now': '立即更新',
|
|
242
|
+
'update.nowDetail': '安装 {version} 并重启 devx',
|
|
243
|
+
'update.later': '稍后',
|
|
244
|
+
'update.laterDetail': '暂时保留此版本',
|
|
245
|
+
'update.skip': '跳过此版本',
|
|
246
|
+
'update.skipDetail': '不再询问 {version}',
|
|
247
|
+
'update.step.registry': '正在检查注册表',
|
|
248
|
+
'update.step.install': '正在安装 devx {version}',
|
|
249
|
+
'update.step.verify': '正在检查新版本',
|
|
250
|
+
'update.step.pull': '正在从 GitHub 拉取',
|
|
251
|
+
'update.step.build': '正在安装并构建',
|
|
252
|
+
'update.step.link': '正在链接命令',
|
|
253
|
+
'update.keepOpen': '请保持 devx 打开,这需要一点时间。',
|
|
254
|
+
'update.installed': 'devx {version} 已安装',
|
|
255
|
+
'update.restarting': '正在重启 devx…',
|
|
256
|
+
'update.failedStep': '停在:{step}',
|
|
257
|
+
'update.keepsRunning': 'devx 继续运行在 {version}。',
|
|
258
|
+
'update.byHand': '手动更新:',
|
|
259
|
+
'update.notPublished': 'npm 上还没有 {version},请几分钟后再试。',
|
|
260
|
+
'update.skipped': '明白:不再询问 {version}。',
|
|
228
261
|
'provider.add': '添加提供商',
|
|
229
262
|
'provider.addDetail': '任何兼容 OpenAI 或 Anthropic 的地址',
|
|
230
263
|
'provider.customTag': '自定义',
|
package/dist/core/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const DEVX_VERSION = '1.9.
|
|
1
|
+
export const DEVX_VERSION = '1.9.2';
|
package/package.json
CHANGED