relayax-cli 0.1.995 → 0.1.996
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/dist/commands/install.js +2 -2
- package/dist/commands/login.js +18 -6
- package/dist/commands/publish.js +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/lib/api.js +1 -1
- package/dist/lib/config.d.ts +15 -0
- package/dist/lib/config.js +55 -3
- package/package.json +1 -1
package/dist/commands/install.js
CHANGED
|
@@ -28,7 +28,7 @@ function registerInstall(program) {
|
|
|
28
28
|
// 2. Visibility check
|
|
29
29
|
const visibility = team.visibility ?? 'public';
|
|
30
30
|
if (visibility === 'login-only' || visibility === 'invite-only') {
|
|
31
|
-
const token = (0, config_js_1.
|
|
31
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
32
32
|
if (!token) {
|
|
33
33
|
console.error(JSON.stringify({
|
|
34
34
|
error: 'LOGIN_REQUIRED',
|
|
@@ -123,7 +123,7 @@ function registerInstall(program) {
|
|
|
123
123
|
console.log(` \x1b[90m└${'─'.repeat(44)}┘\x1b[0m`);
|
|
124
124
|
}
|
|
125
125
|
// Follow prompt (only when logged in)
|
|
126
|
-
const token = (0, config_js_1.
|
|
126
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
127
127
|
if (authorUsername && token) {
|
|
128
128
|
try {
|
|
129
129
|
const { confirm } = await import('@inquirer/prompts');
|
package/dist/commands/login.js
CHANGED
|
@@ -43,6 +43,9 @@ function waitForToken(port) {
|
|
|
43
43
|
const url = new URL(req.url ?? '/', `http://localhost:${port}`);
|
|
44
44
|
if (url.pathname === '/callback') {
|
|
45
45
|
const token = url.searchParams.get('token');
|
|
46
|
+
const refresh_token = url.searchParams.get('refresh_token') ?? undefined;
|
|
47
|
+
const expires_at_raw = url.searchParams.get('expires_at');
|
|
48
|
+
const expires_at = expires_at_raw ? Number(expires_at_raw) : undefined;
|
|
46
49
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
47
50
|
res.end(`<!DOCTYPE html>
|
|
48
51
|
<html><head><title>RelayAX</title></head>
|
|
@@ -55,7 +58,7 @@ function waitForToken(port) {
|
|
|
55
58
|
</body></html>`);
|
|
56
59
|
server.close();
|
|
57
60
|
if (token) {
|
|
58
|
-
resolve(token);
|
|
61
|
+
resolve({ token, refresh_token, expires_at });
|
|
59
62
|
}
|
|
60
63
|
else {
|
|
61
64
|
reject(new Error('토큰이 전달되지 않았습니다'));
|
|
@@ -97,14 +100,19 @@ function registerLogin(program) {
|
|
|
97
100
|
.action(async (opts) => {
|
|
98
101
|
const json = program.opts().json ?? false;
|
|
99
102
|
(0, config_js_1.ensureGlobalRelayDir)();
|
|
100
|
-
let
|
|
101
|
-
|
|
103
|
+
let accessToken = opts.token;
|
|
104
|
+
let refreshToken;
|
|
105
|
+
let expiresAt;
|
|
106
|
+
if (!accessToken) {
|
|
102
107
|
try {
|
|
103
108
|
const port = await findAvailablePort();
|
|
104
109
|
const loginUrl = `${config_js_1.API_URL}/auth/cli-login?port=${port}`;
|
|
105
110
|
console.error('브라우저에서 로그인을 진행합니다...');
|
|
106
111
|
openBrowser(loginUrl);
|
|
107
|
-
|
|
112
|
+
const loginResult = await waitForToken(port);
|
|
113
|
+
accessToken = loginResult.token;
|
|
114
|
+
refreshToken = loginResult.refresh_token;
|
|
115
|
+
expiresAt = loginResult.expires_at;
|
|
108
116
|
}
|
|
109
117
|
catch (err) {
|
|
110
118
|
const msg = err instanceof Error ? err.message : '로그인 실패';
|
|
@@ -112,8 +120,12 @@ function registerLogin(program) {
|
|
|
112
120
|
process.exit(1);
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
|
-
const user = await verifyToken(
|
|
116
|
-
(0, config_js_1.
|
|
123
|
+
const user = await verifyToken(accessToken);
|
|
124
|
+
(0, config_js_1.saveTokenData)({
|
|
125
|
+
access_token: accessToken,
|
|
126
|
+
...(refreshToken ? { refresh_token: refreshToken } : {}),
|
|
127
|
+
...(expiresAt ? { expires_at: expiresAt } : {}),
|
|
128
|
+
});
|
|
117
129
|
const result = {
|
|
118
130
|
status: 'ok',
|
|
119
131
|
message: '로그인 성공',
|
package/dist/commands/publish.js
CHANGED
|
@@ -379,7 +379,7 @@ function registerPublish(program) {
|
|
|
379
379
|
process.exit(1);
|
|
380
380
|
}
|
|
381
381
|
// Get token (checked before tarball creation)
|
|
382
|
-
const token = opts.token ?? process.env.RELAY_TOKEN ?? (0, config_js_1.
|
|
382
|
+
const token = opts.token ?? process.env.RELAY_TOKEN ?? await (0, config_js_1.getValidToken)();
|
|
383
383
|
if (!token) {
|
|
384
384
|
console.error(JSON.stringify({
|
|
385
385
|
error: 'NO_TOKEN',
|
package/dist/commands/status.js
CHANGED
|
@@ -31,7 +31,7 @@ function registerStatus(program) {
|
|
|
31
31
|
const json = program.opts().json ?? false;
|
|
32
32
|
const projectPath = process.cwd();
|
|
33
33
|
// 1. 로그인 상태
|
|
34
|
-
const token = (0, config_js_1.
|
|
34
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
35
35
|
let username;
|
|
36
36
|
if (token) {
|
|
37
37
|
username = await resolveUsername(token);
|
package/dist/commands/update.js
CHANGED
|
@@ -51,7 +51,7 @@ function registerUpdate(program) {
|
|
|
51
51
|
// Visibility check
|
|
52
52
|
const visibility = team.visibility ?? 'public';
|
|
53
53
|
if (visibility === 'login-only') {
|
|
54
|
-
const token = (0, config_js_1.
|
|
54
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
55
55
|
if (!token) {
|
|
56
56
|
console.error('이 팀은 로그인이 필요합니다. `relay login`을 먼저 실행하세요.');
|
|
57
57
|
process.exit(1);
|
package/dist/lib/api.js
CHANGED
|
@@ -54,7 +54,7 @@ async function resolveSlugFromServer(name) {
|
|
|
54
54
|
return data.results;
|
|
55
55
|
}
|
|
56
56
|
async function followBuilder(username) {
|
|
57
|
-
const token = (0, config_js_1.
|
|
57
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
58
58
|
const headers = {
|
|
59
59
|
'Content-Type': 'application/json',
|
|
60
60
|
};
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -11,8 +11,23 @@ export declare function getInstallPath(override?: string): string;
|
|
|
11
11
|
export declare function ensureGlobalRelayDir(): void;
|
|
12
12
|
/** cwd/.relay/ — 프로젝트 로컬 (installed.json, teams/) */
|
|
13
13
|
export declare function ensureProjectRelayDir(): void;
|
|
14
|
+
export interface TokenData {
|
|
15
|
+
access_token: string;
|
|
16
|
+
refresh_token?: string;
|
|
17
|
+
expires_at?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function loadTokenData(): TokenData | undefined;
|
|
14
20
|
export declare function loadToken(): string | undefined;
|
|
21
|
+
export declare function saveTokenData(data: TokenData): void;
|
|
15
22
|
export declare function saveToken(token: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* 유효한 access_token을 반환한다.
|
|
25
|
+
* 1. 저장된 토큰이 없으면 undefined
|
|
26
|
+
* 2. expires_at이 아직 유효하면 access_token 반환
|
|
27
|
+
* 3. 만료되었으면 refresh_token으로 갱신 시도
|
|
28
|
+
* 4. 갱신 실패 시 undefined (재로그인 필요)
|
|
29
|
+
*/
|
|
30
|
+
export declare function getValidToken(): Promise<string | undefined>;
|
|
16
31
|
/** 프로젝트 로컬 installed.json 읽기 (unscoped 키 자동 마이그레이션) */
|
|
17
32
|
export declare function loadInstalled(): InstalledRegistry;
|
|
18
33
|
/**
|
package/dist/lib/config.js
CHANGED
|
@@ -7,8 +7,11 @@ exports.API_URL = void 0;
|
|
|
7
7
|
exports.getInstallPath = getInstallPath;
|
|
8
8
|
exports.ensureGlobalRelayDir = ensureGlobalRelayDir;
|
|
9
9
|
exports.ensureProjectRelayDir = ensureProjectRelayDir;
|
|
10
|
+
exports.loadTokenData = loadTokenData;
|
|
10
11
|
exports.loadToken = loadToken;
|
|
12
|
+
exports.saveTokenData = saveTokenData;
|
|
11
13
|
exports.saveToken = saveToken;
|
|
14
|
+
exports.getValidToken = getValidToken;
|
|
12
15
|
exports.loadInstalled = loadInstalled;
|
|
13
16
|
exports.migrateInstalled = migrateInstalled;
|
|
14
17
|
exports.saveInstalled = saveInstalled;
|
|
@@ -52,20 +55,69 @@ function ensureProjectRelayDir() {
|
|
|
52
55
|
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
|
-
function
|
|
58
|
+
function loadTokenData() {
|
|
56
59
|
const tokenFile = path_1.default.join(GLOBAL_RELAY_DIR, 'token');
|
|
57
60
|
if (!fs_1.default.existsSync(tokenFile))
|
|
58
61
|
return undefined;
|
|
59
62
|
try {
|
|
60
|
-
|
|
63
|
+
const raw = fs_1.default.readFileSync(tokenFile, 'utf-8').trim();
|
|
64
|
+
if (!raw)
|
|
65
|
+
return undefined;
|
|
66
|
+
// JSON 형식 (새 포맷)
|
|
67
|
+
if (raw.startsWith('{')) {
|
|
68
|
+
return JSON.parse(raw);
|
|
69
|
+
}
|
|
70
|
+
// plain text (기존 포맷) — 호환성 유지
|
|
71
|
+
return { access_token: raw };
|
|
61
72
|
}
|
|
62
73
|
catch {
|
|
63
74
|
return undefined;
|
|
64
75
|
}
|
|
65
76
|
}
|
|
77
|
+
function loadToken() {
|
|
78
|
+
return loadTokenData()?.access_token;
|
|
79
|
+
}
|
|
80
|
+
function saveTokenData(data) {
|
|
81
|
+
ensureGlobalRelayDir();
|
|
82
|
+
fs_1.default.writeFileSync(path_1.default.join(GLOBAL_RELAY_DIR, 'token'), JSON.stringify(data));
|
|
83
|
+
}
|
|
66
84
|
function saveToken(token) {
|
|
67
85
|
ensureGlobalRelayDir();
|
|
68
|
-
fs_1.default.writeFileSync(path_1.default.join(GLOBAL_RELAY_DIR, 'token'), token);
|
|
86
|
+
fs_1.default.writeFileSync(path_1.default.join(GLOBAL_RELAY_DIR, 'token'), JSON.stringify({ access_token: token }));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 유효한 access_token을 반환한다.
|
|
90
|
+
* 1. 저장된 토큰이 없으면 undefined
|
|
91
|
+
* 2. expires_at이 아직 유효하면 access_token 반환
|
|
92
|
+
* 3. 만료되었으면 refresh_token으로 갱신 시도
|
|
93
|
+
* 4. 갱신 실패 시 undefined (재로그인 필요)
|
|
94
|
+
*/
|
|
95
|
+
async function getValidToken() {
|
|
96
|
+
const data = loadTokenData();
|
|
97
|
+
if (!data)
|
|
98
|
+
return undefined;
|
|
99
|
+
// expires_at이 없거나 아직 유효하면 (30초 여유) 그대로 사용
|
|
100
|
+
if (!data.expires_at || data.expires_at > Date.now() / 1000 + 30) {
|
|
101
|
+
return data.access_token;
|
|
102
|
+
}
|
|
103
|
+
// 만료됨 — refresh 시도
|
|
104
|
+
if (!data.refresh_token)
|
|
105
|
+
return undefined;
|
|
106
|
+
try {
|
|
107
|
+
const res = await fetch(`${exports.API_URL}/api/auth/refresh`, {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({ refresh_token: data.refresh_token }),
|
|
111
|
+
});
|
|
112
|
+
if (!res.ok)
|
|
113
|
+
return undefined;
|
|
114
|
+
const refreshed = (await res.json());
|
|
115
|
+
saveTokenData(refreshed);
|
|
116
|
+
return refreshed.access_token;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
69
121
|
}
|
|
70
122
|
/** 프로젝트 로컬 installed.json 읽기 (unscoped 키 자동 마이그레이션) */
|
|
71
123
|
function loadInstalled() {
|