kedovi 0.77.0 → 0.77.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 +14 -0
- package/bin/kedovi.js +39 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
`kedovi upgrade` 가 올린 버전 사이의 항목을 표로 보여 준다. 배포할 때 이번 버전 항목을 맨 위에 더한다 (`## 버전` 아래 `| 구분 | 내용 |` 표, 한 줄씩 짧게).
|
|
4
4
|
|
|
5
|
+
## 0.77.2
|
|
6
|
+
|
|
7
|
+
| 구분 | 내용 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| 수정 | `kedovi team whoami --refresh` 가 계정 입력 창을 띄우다 `TypeError (reading 'log')` 로 죽던 것 |
|
|
10
|
+
| 수정 | 화면 없는 실행(에이전트)에서 저장소 계정을 못 읽으면 이유(원격 없음 · gh · glab 로그인 · ssh 키 · git credential)를 한 줄 알림 |
|
|
11
|
+
|
|
12
|
+
## 0.77.1
|
|
13
|
+
|
|
14
|
+
| 구분 | 내용 |
|
|
15
|
+
|---|---|
|
|
16
|
+
| 수정 | 임시 폴더(시스템 임시 폴더 · `/private/tmp` · `/var/folders` · `~/.claude/jobs`)의 워크스페이스를 이 PC 목록에 넣지 않음. 이미 있으면 `kedovi list` · `upgrade --all` 때 정리 |
|
|
17
|
+
| 수정 | 공개판 CI 의 GitHub action 을 Node 24 판으로 (Node 20 지원 종료 경고), setup-node 자동 캐시 끔 |
|
|
18
|
+
|
|
5
19
|
## 0.77.0
|
|
6
20
|
|
|
7
21
|
| 구분 | 내용 |
|
package/bin/kedovi.js
CHANGED
|
@@ -79,8 +79,10 @@ const { spawnSync, execFileSync } = require('child_process');
|
|
|
79
79
|
const kit = require('./kit.js');
|
|
80
80
|
const gateway = require('./gateway.js');
|
|
81
81
|
|
|
82
|
-
let
|
|
83
|
-
const clack = () => (
|
|
82
|
+
let clackMod;
|
|
83
|
+
const clack = () => (clackMod = clackMod || require('@clack/prompts'));
|
|
84
|
+
// p.log 등을 어디서 먼저 쓰든 화면 라이브러리를 불러온다 (늦게 불러오기 때문에 생기던 "undefined (reading 'log')" 방지)
|
|
85
|
+
const p = new Proxy({}, { get: (_, k) => clack()[k] });
|
|
84
86
|
|
|
85
87
|
const BACK = Symbol('kedovi:back');
|
|
86
88
|
|
|
@@ -396,8 +398,21 @@ function writeRegistry(list) {
|
|
|
396
398
|
function workspaceName(root) {
|
|
397
399
|
try { return JSON.parse(read(path.join(root, 'workspace.json'))).project.name || path.basename(root); } catch { return path.basename(root); }
|
|
398
400
|
}
|
|
399
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* 임시 폴더의 워크스페이스인가 — 시험 · 에이전트 작업이 만든 것. 이 PC 목록(kedovi list · upgrade --all)에 올리지 않는다.
|
|
403
|
+
* 시스템 임시 폴더(os.tmpdir · /tmp · /private/tmp · /var/folders · Windows TEMP)와 ~/.claude/jobs
|
|
404
|
+
*/
|
|
405
|
+
function isTempWorkspace(dir) {
|
|
406
|
+
const os = require('os');
|
|
407
|
+
const real = (x) => { try { return fs.realpathSync(x); } catch { return path.resolve(x); } };
|
|
408
|
+
const p = real(dir);
|
|
409
|
+
const roots = [os.tmpdir(), '/tmp', '/private/tmp', '/var/folders', '/private/var/folders', process.env.TEMP, process.env.TMP, path.join(os.homedir(), '.claude', 'jobs')]
|
|
410
|
+
.filter(Boolean).map(real);
|
|
411
|
+
return roots.some((r) => p === r || p.startsWith(r + path.sep));
|
|
412
|
+
}
|
|
413
|
+
/** 목록에 올린다 (이미 있으면 이름만 갱신) — 받은 경로를 그대로 돌려준다. 임시 폴더는 올리지 않는다 */
|
|
400
414
|
function remember(root) {
|
|
415
|
+
if (isTempWorkspace(root) && !process.env.KEDOVI_HOME) { writeShim(root); return root; }
|
|
401
416
|
const all = readRegistry();
|
|
402
417
|
const name = workspaceName(root);
|
|
403
418
|
const old = all.find((w) => w.path === root);
|
|
@@ -425,10 +440,10 @@ function writeShim(root) {
|
|
|
425
440
|
}
|
|
426
441
|
} catch { /* 편의 기능 — 못 만들어도 명령은 계속 */ }
|
|
427
442
|
}
|
|
428
|
-
/** 아직 워크스페이스로 남아 있는 것만 — 지웠거나 옮긴 폴더는 목록에서 뺀다 */
|
|
443
|
+
/** 아직 워크스페이스로 남아 있는 것만 — 지웠거나 옮긴 폴더 · 임시 폴더는 목록에서 뺀다 */
|
|
429
444
|
function liveRegistry() {
|
|
430
445
|
const all = readRegistry();
|
|
431
|
-
const live = all.filter((w) => fs.existsSync(path.join(w.path, 'workspace.json')));
|
|
446
|
+
const live = all.filter((w) => fs.existsSync(path.join(w.path, 'workspace.json')) && (process.env.KEDOVI_HOME || !isTempWorkspace(w.path)));
|
|
432
447
|
if (live.length !== all.length) writeRegistry(live);
|
|
433
448
|
return live;
|
|
434
449
|
}
|
|
@@ -1607,32 +1622,39 @@ function requireAdmin(root, what, { soft = false } = {}) {
|
|
|
1607
1622
|
|
|
1608
1623
|
// 이 PC 사용자 확인 — 워크스페이스 repo 원격의 저장소 계정 username. ssh 면 ssh -T 인사말, https 면 gh · glab · git credential 의 사용자 이름
|
|
1609
1624
|
const NOT_USER = new Set(['oauth2', 'x-access-token', 'gitlab-ci-token', 'personalaccesstoken', 'token', 'git']);
|
|
1610
|
-
|
|
1625
|
+
/** why: 못 찾았을 때 이유를 쌓는다 (화면 없는 실행에서 한 줄로 알린다) */
|
|
1626
|
+
function detectAccount(root, why = []) {
|
|
1611
1627
|
const url = (spawnSync('git', ['-C', root, 'remote', 'get-url', 'origin'], { encoding: 'utf8' }).stdout || '').trim();
|
|
1612
|
-
if (!url) return null;
|
|
1628
|
+
if (!url) { why.push('워크스페이스 repo 에 origin 원격이 없음'); return null; }
|
|
1613
1629
|
const ssh = url.match(/^ssh:\/\/([\w.-]+)@([^:/]+)(?::(\d+))?\//) || url.match(/^([\w.-]+)@([^:/]+):/);
|
|
1614
1630
|
// 로그인 창 · 브라우저가 뜨지 않게 (Git Credential Manager · askpass)
|
|
1615
1631
|
const env = { ...process.env, GIT_TERMINAL_PROMPT: '0', GCM_INTERACTIVE: 'never' };
|
|
1616
1632
|
delete env.GIT_ASKPASS; delete env.SSH_ASKPASS;
|
|
1617
|
-
const
|
|
1633
|
+
const missing = new Set();
|
|
1634
|
+
const run = (cmd, args, input) => { try { const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: 8000, input, env }); if (r.error && r.error.code === 'ENOENT') missing.add(cmd); return `${r.stdout || ''}\n${r.stderr || ''}`; } catch { return ''; } };
|
|
1618
1635
|
if (ssh && !/^https?:/.test(url)) {
|
|
1619
1636
|
const [, user, host, port] = ssh;
|
|
1620
1637
|
const out = run('ssh', ['-T', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', '-o', 'StrictHostKeyChecking=accept-new', ...(port ? ['-p', port] : []), `${user}@${host}`]);
|
|
1621
1638
|
const m = out.match(/Hi ([\w.-]+)!/) || out.match(/Welcome to GitLab, @([\w.-]+)!/) || out.match(/logged in as ([\w.-]+)/i);
|
|
1622
|
-
|
|
1639
|
+
if (m) return { username: m[1], host, source: 'ssh' };
|
|
1640
|
+
why.push(`ssh -T ${user}@${host}${port ? ` -p ${port}` : ''} 이 계정 이름을 돌려주지 않음 (키 등록 · 접속 확인)`);
|
|
1641
|
+
return null;
|
|
1623
1642
|
}
|
|
1624
1643
|
let host = '';
|
|
1625
|
-
try { host = new URL(url).hostname; } catch { return null; }
|
|
1644
|
+
try { host = new URL(url).hostname; } catch { why.push(`원격 주소를 읽지 못함 (${url})`); return null; }
|
|
1626
1645
|
if (/github/.test(host)) {
|
|
1627
1646
|
const u = run('gh', ['api', 'user', '--hostname', host, '--jq', '.login']).trim().split('\n')[0];
|
|
1628
1647
|
if (/^[\w.-]+$/.test(u)) return { username: u, host, source: 'gh' };
|
|
1648
|
+
why.push(missing.has('gh') ? 'gh 없음' : `gh 로그인 안 됨 (gh auth login --hostname ${host})`);
|
|
1629
1649
|
} else {
|
|
1630
1650
|
const j = run('glab', ['api', 'user', '--hostname', host]);
|
|
1631
1651
|
const m = j.match(/"username"\s*:\s*"([\w.-]+)"/);
|
|
1632
1652
|
if (m) return { username: m[1], host, source: 'glab' };
|
|
1653
|
+
why.push(missing.has('glab') ? 'glab 없음' : `glab 로그인 안 됨 (glab auth login --hostname ${host})`);
|
|
1633
1654
|
}
|
|
1634
1655
|
const cred = run('git', ['-C', root, 'credential', 'fill'], `protocol=https\nhost=${host}\n\n`).match(/^username=(.+)$/m);
|
|
1635
1656
|
if (cred && !NOT_USER.has(cred[1].trim().toLowerCase())) return { username: cred[1].trim(), host, source: 'git credential' };
|
|
1657
|
+
why.push(cred ? `git credential 의 사용자가 토큰 이름(${cred[1].trim()})` : 'git credential 에 저장된 사용자 없음');
|
|
1636
1658
|
return null;
|
|
1637
1659
|
}
|
|
1638
1660
|
|
|
@@ -1643,10 +1665,13 @@ async function ensureAccount(root, { refresh = false, ask = process.stdin.isTTY
|
|
|
1643
1665
|
if (cur && cur.username && !refresh) return cur;
|
|
1644
1666
|
// 못 찾은 기록이 있으면 7일 동안 다시 묻지 않는다 (start 마다 느려지지 않게. 다시: kedovi team whoami --refresh)
|
|
1645
1667
|
if (cur && cur.source === 'none' && !refresh && (Date.now() - Date.parse(cur.checked || 0)) < 7 * 864e5) return null;
|
|
1646
|
-
const
|
|
1668
|
+
const why = [];
|
|
1669
|
+
const found = detectAccount(root, why);
|
|
1647
1670
|
let me = found ? { ...found, checked: today } : null;
|
|
1671
|
+
// 화면 없는 실행(에이전트 등)은 물을 수 없으니 이유를 한 줄 알린다
|
|
1672
|
+
if (!me && !ask) clack().log.warn(`저장소 계정을 읽지 못했습니다: ${why.join(' · ') || '이유 모름'}. 터미널에서 kedovi team whoami --refresh 를 직접 실행하면 입력해 적을 수 있습니다`);
|
|
1648
1673
|
if (!me && ask) {
|
|
1649
|
-
|
|
1674
|
+
clack().log.info('워크스페이스 원격에서 저장소 계정을 읽지 못했습니다. 팀장 · 담당 repo 판정에 쓰니 직접 적어 주세요 (나중에 다시 확인: kedovi team whoami --refresh)');
|
|
1650
1675
|
const v = await clack().text({ message: '저장소 계정 username', placeholder: '예: hong01' });
|
|
1651
1676
|
if (!clack().isCancel(v) && String(v || '').trim()) me = { username: String(v).trim(), host: '', source: 'input', checked: today };
|
|
1652
1677
|
}
|
|
@@ -1669,7 +1694,7 @@ function adoptAccount(root, username) {
|
|
|
1669
1694
|
const all = [...(t.leads || []), ...(t.admins || []), ...(t.members || [])];
|
|
1670
1695
|
if (all.some((x) => sameUser(x, username)) || !all.some((x) => sameUser(x, gitName))) return;
|
|
1671
1696
|
const lead = [...(t.leads || []), ...(t.admins || [])].some((x) => sameUser(x, gitName));
|
|
1672
|
-
|
|
1697
|
+
clack().log.info(lead
|
|
1673
1698
|
? `팀 설정의 팀장 ${gitName} 이(가) git user.name 으로 적혀 있습니다. 저장소 계정으로 바꾸세요: kedovi team lead add ${username} --nick <이름> 뒤 kedovi team lead remove ${gitName}`
|
|
1674
1699
|
: `팀 설정에 ${gitName} 이(가) git user.name 으로 적혀 있습니다. 팀장에게 저장소 계정 ${username} 으로 바꿔 달라고 요청하세요 (kedovi team add ${username} --nick <이름> --repos …)`);
|
|
1675
1700
|
}
|
|
@@ -1756,7 +1781,7 @@ async function cmdWhoami(root, args) {
|
|
|
1756
1781
|
console.log(`이 PC 사용자: ${w.nickname ? `${w.nickname} · ` : ''}${w.username || '(모름)'} (확인: ${w.source})`);
|
|
1757
1782
|
console.log(`역할: ${w.lead ? '팀장 (모든 repo · 설정 수정)' : w.person ? `팀원 · 담당 ${(w.repos || []).join(', ') || '없음'}` : '명단에 없음'}`);
|
|
1758
1783
|
if (blocked.length) console.log(`고칠 수 없는 repo: ${blocked.join(', ')} (담당자 · 팀장에게 요청)`);
|
|
1759
|
-
if (w.source === 'git user.name') console.log(' 저장소 계정을 아직 확인하지 못했습니다: kedovi team whoami --refresh');
|
|
1784
|
+
if (w.source === 'git user.name' && !args.includes('--refresh')) console.log(' 저장소 계정을 아직 확인하지 못했습니다: kedovi team whoami --refresh');
|
|
1760
1785
|
}
|
|
1761
1786
|
|
|
1762
1787
|
/** 팀장 · 팀원 · 담당 repo · 부를 이름 (workspace.json team.leads · members) */
|