tsoft-cli 3.14.1 → 3.14.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/CHANGELOG.md +5 -0
- package/locales/en.json +5 -1
- package/locales/tr.json +5 -1
- package/package.json +1 -1
- package/src/commands/login.js +23 -4
- package/src/server.js +24 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,11 @@ All notable changes to T-Soft CLI will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.14.2] - 2026-10-15
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `tsoft login` now says plainly when the port it needs for sign-in is taken, and no longer sends you to the browser first. The browser opened and waited for you to sign in before the CLI noticed it could not receive the answer, and what came back was `listen EADDRINUSE: address already in use :::8046` followed by a pointer to log files the CLI does not write — so you signed in for nothing and were left with a line that said nothing about what to do. The local connection is now opened before the browser, and when the port is in use the message names it, suggests stopping a `tsoft login` left running in another terminal, and gives the command to see which application holds the port (`lsof` on macOS and Linux, `netstat` on Windows). Other reasons the connection cannot be opened get their own message, the raw error is still available under `--verbose`, and the misleading log-file line is gone from every login error.
|
|
12
|
+
|
|
8
13
|
## [3.14.1] - 2026-09-22
|
|
9
14
|
|
|
10
15
|
### Fixed
|
package/locales/en.json
CHANGED
|
@@ -54,7 +54,11 @@
|
|
|
54
54
|
"login.tip": "💡 Tip: Use \"tsoft whoami\" to check your status.",
|
|
55
55
|
"login.json_error.message": "Login command requires interactive mode.",
|
|
56
56
|
"login.json_error.hint": "For CI/CD, authenticate with tsoft login first to store the token, then use it.",
|
|
57
|
-
"login.
|
|
57
|
+
"login.port_in_use.message": "Port {{port}}, which sign-in needs, is already in use by another application.",
|
|
58
|
+
"login.port_in_use.close_hint": "If \"tsoft login\" is still running in another terminal, stop it there with Ctrl+C and try again.",
|
|
59
|
+
"login.port_in_use.find_hint": "To see which application is using the port, run: {{command}}",
|
|
60
|
+
"login.server_start_failed.message": "Sign-in could not start: the local connection it needs could not be opened on this computer.",
|
|
61
|
+
"login.server_start_failed.hint": "For details: tsoft login --verbose",
|
|
58
62
|
"login.security_error": "State parameter mismatch! This may indicate a security issue.",
|
|
59
63
|
"logout.starting": "🚪 Logging out...",
|
|
60
64
|
"logout.not_logged_in": "▲ You are not logged in.",
|
package/locales/tr.json
CHANGED
|
@@ -54,7 +54,11 @@
|
|
|
54
54
|
"login.tip": "💡 İpucu: Durum kontrolü için \"tsoft whoami\" komutunu kullanın.",
|
|
55
55
|
"login.json_error.message": "Login komutu interaktif mod gerektiriyor.",
|
|
56
56
|
"login.json_error.hint": "CI/CD için önceden tsoft login ile kimlik doğrulayıp token'i sakladıktan sonra kullanın.",
|
|
57
|
-
"login.
|
|
57
|
+
"login.port_in_use.message": "Giriş için gereken {{port}} numaralı port şu anda başka bir uygulama tarafından kullanılıyor.",
|
|
58
|
+
"login.port_in_use.close_hint": "Başka bir terminalde açık kalmış bir \"tsoft login\" varsa o pencerede Ctrl+C ile durdurup tekrar deneyin.",
|
|
59
|
+
"login.port_in_use.find_hint": "Portu hangi uygulamanın kullandığını görmek için şunu çalıştırın: {{command}}",
|
|
60
|
+
"login.server_start_failed.message": "Giriş başlatılamadı: giriş için gereken yerel bağlantı bu bilgisayarda açılamadı.",
|
|
61
|
+
"login.server_start_failed.hint": "Detaylar için: tsoft login --verbose",
|
|
58
62
|
"login.security_error": "State parametresi eşleşmiyor! Güvenlik hatası olabilir.",
|
|
59
63
|
"logout.starting": "🚪 Çıkış yapılıyor...",
|
|
60
64
|
"logout.not_logged_in": "▲ Zaten giriş yapmamışsınız.",
|
package/package.json
CHANGED
package/src/commands/login.js
CHANGED
|
@@ -4,7 +4,7 @@ import {generateCodeVerifier, generateCodeChallenge} from '../pkce.js';
|
|
|
4
4
|
import {startCallbackServer} from '../server.js';
|
|
5
5
|
import {exchangeToken, saveTokens, setActiveStore} from '../storage.js';
|
|
6
6
|
import * as config from '../config.js';
|
|
7
|
-
import { isJsonMode, jsonOut } from '../output-mode.js';
|
|
7
|
+
import { isJsonMode, jsonOut, verboseLog } from '../output-mode.js';
|
|
8
8
|
import { t } from '../i18n.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -43,7 +43,8 @@ export async function loginCommand() {
|
|
|
43
43
|
authUrl.searchParams.set('prompt', 'select_org_store');
|
|
44
44
|
|
|
45
45
|
// 3. Callback server'ı başlat
|
|
46
|
-
const
|
|
46
|
+
const callbackServer = await startCallbackServer(config.CALLBACK_PORT)
|
|
47
|
+
.catch((error) => exitOnListenFailure(error, config.CALLBACK_PORT));
|
|
47
48
|
|
|
48
49
|
// 4. Browser'ı aç
|
|
49
50
|
console.log(chalk.cyan(t('login.browser_opening')));
|
|
@@ -56,7 +57,7 @@ export async function loginCommand() {
|
|
|
56
57
|
console.log(chalk.gray(' ' + t('login.waiting_detail') + '\n'));
|
|
57
58
|
|
|
58
59
|
// 5. Callback'i bekle
|
|
59
|
-
const {code, state: returnedState, store} = await
|
|
60
|
+
const {code, state: returnedState, store} = await callbackServer.result;
|
|
60
61
|
|
|
61
62
|
// 6. State doğrula (CSRF koruması)
|
|
62
63
|
if (state !== returnedState) {
|
|
@@ -89,7 +90,25 @@ export async function loginCommand() {
|
|
|
89
90
|
|
|
90
91
|
} catch (error) {
|
|
91
92
|
console.error(chalk.red('\n❌ Hata: ') + error.message);
|
|
92
|
-
console.error(chalk.gray('\n' + t('login.error_log')));
|
|
93
93
|
process.exit(1);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
|
|
97
|
+
function exitOnListenFailure(error, port) {
|
|
98
|
+
verboseLog(error.message);
|
|
99
|
+
|
|
100
|
+
if (error.code === 'EADDRINUSE') {
|
|
101
|
+
console.error(chalk.red('\n❌ ' + t('login.port_in_use.message', { port })));
|
|
102
|
+
console.error(chalk.gray(' ' + t('login.port_in_use.close_hint')));
|
|
103
|
+
console.error(chalk.gray(' ' + t('login.port_in_use.find_hint', { command: findPortOwnerCommand(port) })));
|
|
104
|
+
} else {
|
|
105
|
+
console.error(chalk.red('\n❌ ' + t('login.server_start_failed.message')));
|
|
106
|
+
console.error(chalk.gray(' ' + t('login.server_start_failed.hint')));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function findPortOwnerCommand(port) {
|
|
113
|
+
return process.platform === 'win32' ? `netstat -ano | findstr :${port}` : `lsof -i :${port}`;
|
|
114
|
+
}
|
package/src/server.js
CHANGED
|
@@ -7,7 +7,15 @@ import { URL } from 'url';
|
|
|
7
7
|
* @returns {Promise<{code: string, state: string, store: string}>}
|
|
8
8
|
*/
|
|
9
9
|
export function startCallbackServer(port) {
|
|
10
|
-
|
|
10
|
+
let resolve;
|
|
11
|
+
let reject;
|
|
12
|
+
const result = new Promise((resolveResult, rejectResult) => {
|
|
13
|
+
resolve = resolveResult;
|
|
14
|
+
reject = rejectResult;
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
return new Promise((resolveListening, rejectListening) => {
|
|
18
|
+
let listening = false;
|
|
11
19
|
const server = http.createServer((req, res) => {
|
|
12
20
|
const url = new URL(req.url, `http://localhost:${port}`);
|
|
13
21
|
|
|
@@ -45,15 +53,29 @@ export function startCallbackServer(port) {
|
|
|
45
53
|
});
|
|
46
54
|
|
|
47
55
|
server.on('error', (err) => {
|
|
48
|
-
|
|
56
|
+
if (listening) {
|
|
57
|
+
reject(err);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
server.close();
|
|
61
|
+
rejectListening(listenFailure(err, port));
|
|
49
62
|
});
|
|
50
63
|
|
|
51
64
|
server.listen(port, () => {
|
|
65
|
+
listening = true;
|
|
52
66
|
console.log(`✓ Callback sunucusu başlatıldı (http://localhost:${port}/callback)`);
|
|
67
|
+
resolveListening({ result });
|
|
53
68
|
});
|
|
54
69
|
});
|
|
55
70
|
}
|
|
56
71
|
|
|
72
|
+
function listenFailure(err, port) {
|
|
73
|
+
const failure = new Error(err.message, { cause: err });
|
|
74
|
+
failure.code = err.code;
|
|
75
|
+
failure.port = port;
|
|
76
|
+
return failure;
|
|
77
|
+
}
|
|
78
|
+
|
|
57
79
|
function successHtml() {
|
|
58
80
|
return `
|
|
59
81
|
<!DOCTYPE html>
|