axmap-cli 0.0.1
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/.claude/commands/ax-done.md +10 -0
- package/.claude/commands/ax-setup.md +31 -0
- package/.claude/commands/ax-start.md +15 -0
- package/.claude/commands/ax-tell.md +14 -0
- package/.claude/commands/ax-update.md +19 -0
- package/.claude/commands/ax.md +13 -0
- package/CLAUDE.md +309 -0
- package/LICENSE +20 -0
- package/README.md +207 -0
- package/app/README.md +366 -0
- package/app/eval/edges.mjs +242 -0
- package/app/lib/adjacent.mjs +125 -0
- package/app/lib/agentcli.mjs +153 -0
- package/app/lib/analyze.mjs +1159 -0
- package/app/lib/cochange.mjs +421 -0
- package/app/lib/datanodes.mjs +127 -0
- package/app/lib/entry.mjs +192 -0
- package/app/lib/featuregraph.mjs +389 -0
- package/app/lib/features.mjs +645 -0
- package/app/lib/fetchrepo-run.mjs +37 -0
- package/app/lib/fetchrepo.mjs +164 -0
- package/app/lib/flow.mjs +1089 -0
- package/app/lib/ladder.mjs +387 -0
- package/app/lib/langs.mjs +630 -0
- package/app/lib/live.mjs +346 -0
- package/app/lib/llm.mjs +594 -0
- package/app/lib/newfile.mjs +126 -0
- package/app/lib/prdiff.mjs +651 -0
- package/app/lib/reveal.mjs +316 -0
- package/app/lib/roots.mjs +186 -0
- package/app/lib/scope.mjs +342 -0
- package/app/lib/session.mjs +389 -0
- package/app/lib/slots.mjs +233 -0
- package/app/lib/ssot.mjs +277 -0
- package/app/lib/teamview.mjs +962 -0
- package/app/lib/terms.ko.mjs +169 -0
- package/app/server.mjs +1959 -0
- package/app/web/shell.css +538 -0
- package/app/web/shell.html +197 -0
- package/app/web/shell.js +638 -0
- package/app/web/stage.js +347 -0
- package/app/web/words.js +85 -0
- package/bin/axmap.mjs +1918 -0
- package/governance/GOVERNANCE.md +433 -0
- package/governance/gate.mjs +526 -0
- package/governance/vote.mjs +501 -0
- package/mcp/README.md +254 -0
- package/mcp/SETUP-FOR-AI.md +186 -0
- package/mcp/install.ps1 +341 -0
- package/mcp/install.sh +339 -0
- package/mcp/server.mjs +969 -0
- package/package.json +48 -0
- package/src/closure.mjs +343 -0
- package/src/governance.mjs +839 -0
- package/src/invariants.mjs +226 -0
- package/src/mrtarget.mjs +284 -0
- package/src/promote.mjs +177 -0
- package/src/protocol.mjs +423 -0
- package/src/repotarget.mjs +81 -0
- package/src/update.mjs +177 -0
- package/src/version.mjs +186 -0
- package/tools/bus.mjs +520 -0
- package/tools/cluster-experiment.mjs +256 -0
- package/tools/cluster-sweep.mjs +226 -0
- package/tools/make-icon.mjs +108 -0
- package/tools/mcp-register.mjs +269 -0
- package/tools/mr-target.mjs +49 -0
- package/tools/persona-bench.mjs +362 -0
- package/tools/pick-repo.mjs +229 -0
- package/tools/promote.mjs +550 -0
- package/tools/reveal-demo.mjs +158 -0
- package/tools/run-tests.mjs +42 -0
- package/tools/setup.mjs +490 -0
- package/tools/shortcut.mjs +121 -0
- package/tools/smoke.mjs +166 -0
- package/tools/topicgraph.py +154 -0
- package/tools/vendor.mjs +382 -0
- package/tools/version.mjs +115 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 이 PC 에 있는 AI CLI 를 찾아 axMap MCP 서버를 **각 CLI 의 설정에 등록**한다.
|
|
4
|
+
*
|
|
5
|
+
* node axmap/tools/mcp-register.mjs [--dry-run]
|
|
6
|
+
*
|
|
7
|
+
* ── 왜 이 파일이 따로 있나 ────────────────────────────────────────────────
|
|
8
|
+
*
|
|
9
|
+
* MCP 서버 자체(`axmap/mcp/server.mjs`)는 표준 규격(stdio + 줄바꿈 JSON-RPC 2.0)이라
|
|
10
|
+
* **MCP 를 지원하는 클라이언트면 무엇이든 붙는다.** 문제는 "자동으로 붙는 편의" 쪽이다.
|
|
11
|
+
* 저장소에 든 `.mcp.json` 을 스스로 발견해 주는 것은 Claude Code 뿐이고,
|
|
12
|
+
* 나머지 CLI 는 각자의 설정 파일에 손으로 적어야 한다. 그 손을 여기서 대신한다.
|
|
13
|
+
*
|
|
14
|
+
* setup.sh 와 setup.ps1 둘 다 이 파일을 부른다. 셸 두 벌에 같은 로직을 적으면
|
|
15
|
+
* 반드시 한쪽만 고쳐지고, 그 순간 두 OS 의 사용자가 서로 다른 설정을 갖게 된다.
|
|
16
|
+
*
|
|
17
|
+
* ── 🔴 이 파일이 절대 하지 않는 두 가지 ───────────────────────────────────
|
|
18
|
+
*
|
|
19
|
+
* 1. **`AXMAP_AGENT` 를 어떤 설정에도 적지 않는다.**
|
|
20
|
+
* 적는 순간 그 값이 clone 한 모두의 이름이 된다. 장부에는 한 명만 존재하게 되고,
|
|
21
|
+
* 겹침 판정은 자기 claim 을 겹침으로 보지 않으므로 팀 전원이 서로의 영역을
|
|
22
|
+
* 아무 경고 없이 덮어쓴다. 이 저장소가 뿌리뽑은 실패다(axmap/mcp/README.md).
|
|
23
|
+
* 이름은 서버가 `git config user.name` 으로 떨어뜨리게 둔다.
|
|
24
|
+
*
|
|
25
|
+
* 2. **남의 설정 파일을 덮어쓰지 않는다.**
|
|
26
|
+
* 이미 있으면 읽어서 `axmap` 항목만 더하고 나머지는 그대로 쓴다.
|
|
27
|
+
* 파싱이 안 되면 **건드리지 말고** 사람이 붙일 스니펫을 출력한다.
|
|
28
|
+
* 등록에 실패하는 것보다 남의 설정을 깨뜨리는 것이 훨씬 나쁘다 — 앞은 안 붙었다는
|
|
29
|
+
* 것이 바로 보이지만, 뒤는 다른 도구가 조용히 죽고 원인을 못 찾는다.
|
|
30
|
+
*
|
|
31
|
+
* ── 감지는 새로 짜지 않는다 ───────────────────────────────────────────────
|
|
32
|
+
*
|
|
33
|
+
* 어떤 CLI 가 이 PC 에 있는지는 `app/lib/agentcli.mjs` 가 이미 안다(`AGENTS`, `resolveBin`).
|
|
34
|
+
* 거기서 그대로 가져온다. 바이너리 이름이 두 군데에 적히면 한쪽만 낡는다.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { spawnSync } from 'node:child_process'
|
|
38
|
+
import fs from 'node:fs'
|
|
39
|
+
import os from 'node:os'
|
|
40
|
+
import path from 'node:path'
|
|
41
|
+
import { fileURLToPath } from 'node:url'
|
|
42
|
+
|
|
43
|
+
import { AGENTS, resolveBin } from '../app/lib/agentcli.mjs'
|
|
44
|
+
|
|
45
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url))
|
|
46
|
+
const AXMAP_DIR = path.resolve(HERE, '..') // .../axmap
|
|
47
|
+
const SERVER = path.join(AXMAP_DIR, 'mcp', 'server.mjs')
|
|
48
|
+
const DRY = process.argv.includes('--dry-run')
|
|
49
|
+
|
|
50
|
+
const ok = (what, detail) => console.log(` OK ${what.padEnd(10)} ${detail}`)
|
|
51
|
+
const hm = (what, detail) => console.log(` ~~ ${what.padEnd(10)} ${detail}`)
|
|
52
|
+
const no = (what, detail) => console.log(` !! ${what.padEnd(10)} ${detail}`)
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 모든 CLI 에 똑같이 들어가는 서버 정의.
|
|
56
|
+
*
|
|
57
|
+
* 🔴 `AXMAP_REPO` 를 **일부러 넣지 않는다.**
|
|
58
|
+
*
|
|
59
|
+
* 서버는 `AXMAP_REPO ?? process.cwd()` 로 대상 저장소를 정한다(mcp/server.mjs).
|
|
60
|
+
* 여기서 이 저장소의 절대 경로를 박아 두면 등록은 전역인데 대상은 한 곳으로 고정돼,
|
|
61
|
+
* 다른 저장소를 열었을 때 **엉뚱한 장부에 claim 이 쌓인다** — 아무 오류도 안 나면서.
|
|
62
|
+
* 비워 두면 CLI 가 띄운 작업 폴더가 그대로 대상이 된다.
|
|
63
|
+
*
|
|
64
|
+
* 반대로 `args` 는 반드시 절대 경로다. 이건 "실행할 프로그램" 이라 어느 폴더에서 켜든
|
|
65
|
+
* 같은 곳을 가리켜야 한다. 프로그램은 여기, 데이터는 거기(axmap/mcp/README.md).
|
|
66
|
+
*/
|
|
67
|
+
const entry = () => ({
|
|
68
|
+
command: 'node',
|
|
69
|
+
args: [SERVER],
|
|
70
|
+
env: { AXMAP_ACTOR: 'agent', AXMAP_TTL: '45m' },
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const same = (a, b) => JSON.stringify(a) === JSON.stringify(b)
|
|
74
|
+
|
|
75
|
+
// ── Claude Code ────────────────────────────────────────────────────────────
|
|
76
|
+
//
|
|
77
|
+
// 할 일이 없다. 저장소의 `.mcp.json` 을 스스로 발견하고 승인만 묻는다.
|
|
78
|
+
// 그래도 한 줄 찍는다 — "내 CLI 는 왜 아무 말이 없지" 를 없애기 위해서다.
|
|
79
|
+
function doClaude(repo) {
|
|
80
|
+
const f = path.join(repo, '.mcp.json')
|
|
81
|
+
if (fs.existsSync(f)) ok('claude', '.mcp.json — 이 폴더를 열면 스스로 발견합니다 (승인만 누르면 됩니다)')
|
|
82
|
+
else no('claude', `.mcp.json 이 없습니다: ${f}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── Antigravity CLI (agy) ──────────────────────────────────────────────────
|
|
86
|
+
//
|
|
87
|
+
// 설정: ~/.gemini/config/mcp_config.json (전역)
|
|
88
|
+
// 형식: .mcp.json 과 같은 { "mcpServers": { ... } }
|
|
89
|
+
//
|
|
90
|
+
// ✅ 이 PC 의 agy 1.1.10 으로 **실물 확인했다.**
|
|
91
|
+
// - 전역 파일에 적으면 도구가 뜬다 (헤드리스에서 "mcp 권한이 필요하다" 까지 도달)
|
|
92
|
+
// - 저장소의 `.mcp.json` 은 **읽지 않는다** (같은 조건에서 도구가 아예 안 뜬다)
|
|
93
|
+
// - 서버를 **작업 폴더를 cwd 로** 띄운다 (상대 경로 `./axmap/mcp/server.mjs` 가 풀렸다)
|
|
94
|
+
// - 일부러 없는 파일을 가리키면 도구가 사라진다 — 위 결과가 캐시가 아님을 확인한 대조군
|
|
95
|
+
//
|
|
96
|
+
// ⚠️ 공식 문서가 말하는 작업 폴더용 `.agents/mcp_config.json` 은 **CLI 에서는 안 먹었다.**
|
|
97
|
+
// IDE 쪽 경로로 보인다. 그래서 여기서는 전역 파일만 건드린다.
|
|
98
|
+
function doAgy() {
|
|
99
|
+
const f = path.join(os.homedir(), '.gemini', 'config', 'mcp_config.json')
|
|
100
|
+
mergeJson('agy', f)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* `{ mcpServers: {...} }` 꼴의 JSON 설정에 `axmap` 항목만 더한다.
|
|
105
|
+
*
|
|
106
|
+
* 파일이 없거나 **비어 있으면** 새로 쓴다. 빈 파일은 "아직 아무것도 안 적혔다" 이지
|
|
107
|
+
* "내가 못 읽는 내용이 들어 있다" 가 아니다 — 실제로 agy 는 설치할 때 0바이트 파일을
|
|
108
|
+
* 만들어 둔다. 이걸 파싱 실패로 취급하면 정상 설치가 전부 수동 안내로 떨어진다.
|
|
109
|
+
*/
|
|
110
|
+
function mergeJson(label, file) {
|
|
111
|
+
const want = entry()
|
|
112
|
+
let cfg = null
|
|
113
|
+
|
|
114
|
+
if (fs.existsSync(file)) {
|
|
115
|
+
const raw = fs.readFileSync(file, 'utf8')
|
|
116
|
+
if (raw.trim() === '') {
|
|
117
|
+
cfg = {}
|
|
118
|
+
} else {
|
|
119
|
+
try { cfg = JSON.parse(raw) } catch { cfg = null }
|
|
120
|
+
if (cfg === null || typeof cfg !== 'object' || Array.isArray(cfg)) {
|
|
121
|
+
// 🔴 여기서 덮어쓰지 않는다. 사람이 쓴 설정이 들어 있는데 우리가 못 읽는 것뿐일
|
|
122
|
+
// 수도 있다. 못 읽는 것을 지우는 것은 고치는 게 아니라 부수는 것이다.
|
|
123
|
+
no(label, `설정을 읽지 못했습니다 — 건드리지 않았습니다: ${file}`)
|
|
124
|
+
snippetJson()
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
cfg = {}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (cfg.mcpServers != null && (typeof cfg.mcpServers !== 'object' || Array.isArray(cfg.mcpServers))) {
|
|
133
|
+
no(label, `mcpServers 가 객체가 아닙니다 — 건드리지 않았습니다: ${file}`)
|
|
134
|
+
snippetJson()
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const servers = cfg.mcpServers ?? {}
|
|
139
|
+
if (same(servers.axmap, want)) { ok(label, `이미 등록돼 있습니다: ${file}`); return }
|
|
140
|
+
|
|
141
|
+
const had = servers.axmap !== undefined
|
|
142
|
+
servers.axmap = want
|
|
143
|
+
cfg.mcpServers = servers
|
|
144
|
+
|
|
145
|
+
if (DRY) { hm(label, `(dry-run) ${had ? '갱신' : '추가'}했을 파일: ${file}`); return }
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
149
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n')
|
|
150
|
+
ok(label, `${had ? '갱신' : '등록'}했습니다: ${file}`)
|
|
151
|
+
} catch (e) {
|
|
152
|
+
no(label, `쓰지 못했습니다 (${e.message}): ${file}`)
|
|
153
|
+
snippetJson()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function snippetJson() {
|
|
158
|
+
console.log('')
|
|
159
|
+
console.log(' 아래를 그 파일의 "mcpServers" 안에 직접 넣으세요:')
|
|
160
|
+
console.log('')
|
|
161
|
+
for (const l of JSON.stringify({ axmap: entry() }, null, 2).split('\n')) console.log(` ${l}`)
|
|
162
|
+
console.log('')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ── Codex CLI ──────────────────────────────────────────────────────────────
|
|
166
|
+
//
|
|
167
|
+
// 설정: ~/.codex/config.toml (전역) / 신뢰된 프로젝트의 .codex/config.toml
|
|
168
|
+
// 형식: TOML — [mcp_servers.<이름>] + [mcp_servers.<이름>.env]
|
|
169
|
+
//
|
|
170
|
+
// ⚠️ **이 PC 에 codex 가 없어 실물로 확인하지 못했다.** 아래는 공식 문서
|
|
171
|
+
// (learn.chatgpt.com/docs/extend/mcp?surface=cli) 에 적힌 것을 그대로 따른 것이다.
|
|
172
|
+
//
|
|
173
|
+
// 그래서 문서에 적힌 명령을 **있다고 가정하고 부르지 않는다.** 먼저 `codex mcp --help`
|
|
174
|
+
// 로 그 하위 명령이 실제로 있는지 물어보고, 있을 때만 `codex mcp add` 를 부른다.
|
|
175
|
+
// 없으면 TOML 스니펫을 찍고 끝낸다.
|
|
176
|
+
//
|
|
177
|
+
// 🔴 TOML 을 직접 파싱하지 않는다. 파서를 새로 짜는 것은 이 저장소의 "의존성 없음" 과
|
|
178
|
+
// "판정을 복잡하게 만들지 않는다" 에 둘 다 어긋나고, 어설픈 파서가 남의 config.toml 을
|
|
179
|
+
// 깨뜨리는 것이 이 작업에서 나올 수 있는 최악의 결과다. 쓰는 일은 codex 자신에게 맡긴다.
|
|
180
|
+
function doCodex(bin) {
|
|
181
|
+
const run = (args) => spawnSync(bin, args, { encoding: 'utf8', windowsHide: true })
|
|
182
|
+
|
|
183
|
+
const help = run(['mcp', '--help'])
|
|
184
|
+
if (help.status !== 0 || !/\badd\b/.test(`${help.stdout}${help.stderr}`)) {
|
|
185
|
+
hm('codex', "'codex mcp add' 를 확인하지 못했습니다 — 손으로 넣으세요")
|
|
186
|
+
snippetToml()
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 이미 들어 있으면 다시 넣지 않는다. setup 은 몇 번을 돌려도 안전해야 한다.
|
|
191
|
+
const list = run(['mcp', 'list'])
|
|
192
|
+
if (list.status === 0 && /\baxmap\b/.test(`${list.stdout}`)) {
|
|
193
|
+
ok('codex', `이미 등록돼 있습니다 (codex mcp list)`)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const add = ['mcp', 'add', 'axmap', '--env', 'AXMAP_ACTOR=agent', '--env', 'AXMAP_TTL=45m', '--', 'node', SERVER]
|
|
198
|
+
if (DRY) { hm('codex', `(dry-run) codex ${add.join(' ')}`); return }
|
|
199
|
+
|
|
200
|
+
const r = run(add)
|
|
201
|
+
if (r.status === 0) ok('codex', 'codex mcp add 로 등록했습니다')
|
|
202
|
+
else {
|
|
203
|
+
no('codex', `codex mcp add 가 실패했습니다 (${(r.stderr || r.stdout || '').trim().split('\n')[0] || `exit ${r.status}`})`)
|
|
204
|
+
snippetToml()
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* TOML 스니펫.
|
|
210
|
+
*
|
|
211
|
+
* 경로에 홑따옴표를 쓴다(TOML 의 literal string). 윈도우 경로의 역슬래시가
|
|
212
|
+
* 이스케이프로 먹히지 않게 하기 위해서다 — 겹따옴표로 적으면 `C:\Users` 의 `\U` 가
|
|
213
|
+
* 유니코드 이스케이프로 해석돼 파일이 아예 안 읽힌다.
|
|
214
|
+
*/
|
|
215
|
+
function snippetToml() {
|
|
216
|
+
console.log('')
|
|
217
|
+
console.log(' ~/.codex/config.toml 에 아래를 붙여 넣으세요:')
|
|
218
|
+
console.log('')
|
|
219
|
+
for (const l of [
|
|
220
|
+
'[mcp_servers.axmap]',
|
|
221
|
+
'command = "node"',
|
|
222
|
+
`args = ['${SERVER}']`,
|
|
223
|
+
'',
|
|
224
|
+
'[mcp_servers.axmap.env]',
|
|
225
|
+
'AXMAP_ACTOR = "agent"',
|
|
226
|
+
'AXMAP_TTL = "45m"',
|
|
227
|
+
]) console.log(` ${l}`)
|
|
228
|
+
console.log('')
|
|
229
|
+
console.log(' AXMAP_AGENT 는 적지 마세요 — 여러 사람이 같은 이름이 되면')
|
|
230
|
+
console.log(' 서로의 선점을 겹침으로 보지 못합니다.')
|
|
231
|
+
console.log('')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── 본체 ───────────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
function main() {
|
|
237
|
+
const repo = spawnSync('git', ['rev-parse', '--show-toplevel'], { cwd: AXMAP_DIR, encoding: 'utf8', windowsHide: true })
|
|
238
|
+
const root = repo.status === 0 ? path.resolve(repo.stdout.trim()) : path.resolve(AXMAP_DIR, '..')
|
|
239
|
+
|
|
240
|
+
console.log('')
|
|
241
|
+
console.log('AI CLI 에 axmap 을 등록합니다' + (DRY ? ' (dry-run — 아무것도 쓰지 않습니다)' : ''))
|
|
242
|
+
console.log(` 서버: ${SERVER}`)
|
|
243
|
+
console.log('')
|
|
244
|
+
|
|
245
|
+
if (!fs.existsSync(SERVER)) {
|
|
246
|
+
no('서버', `파일이 없습니다: ${SERVER}`)
|
|
247
|
+
return 0
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 감지는 agentcli.mjs 가 아는 바이너리 이름을 그대로 쓴다.
|
|
251
|
+
let any = false
|
|
252
|
+
for (const a of AGENTS) {
|
|
253
|
+
const bin = resolveBin(a.bin)
|
|
254
|
+
if (!bin) continue
|
|
255
|
+
any = true
|
|
256
|
+
if (a.id === 'claude') doClaude(root)
|
|
257
|
+
else if (a.id === 'agy') doAgy()
|
|
258
|
+
else if (a.id === 'codex') doCodex(bin)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!any) hm('CLI', '이 PC 에서 AI CLI 를 찾지 못했습니다 — 나중에 다시 실행하세요')
|
|
262
|
+
|
|
263
|
+
console.log('')
|
|
264
|
+
// 🔴 등록 실패로 setup 을 멈추지 않는다. 장부와 훅은 이미 심겨 있고, 그쪽이 본체다.
|
|
265
|
+
// MCP 는 편의일 뿐이라 못 붙어도 사람이 CLI 로 claim 할 수 있다.
|
|
266
|
+
return 0
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
process.exit(main())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MR 이 한 칸씩 올라가는지 검사하는 CLI — 환경변수와 출력만 담당하는 층.
|
|
4
|
+
*
|
|
5
|
+
* 판정은 전부 `src/mrtarget.mjs` 의 순수 함수에 있다.
|
|
6
|
+
*
|
|
7
|
+
* node axmap/tools/mr-target.mjs CI 에서 (환경변수로)
|
|
8
|
+
* node axmap/tools/mr-target.mjs --source <브랜치> --target <브랜치> 손으로
|
|
9
|
+
*
|
|
10
|
+
* --json 판정을 기계가 읽는 모양으로도 낸다
|
|
11
|
+
*
|
|
12
|
+
* 종료 코드: 0 통과 · 2 규칙 위반(대상을 바꾸면 풀린다) · 1 판정 불가(환경 문제)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { checkTarget, formatCheck, EXIT } from '../src/mrtarget.mjs'
|
|
16
|
+
|
|
17
|
+
const argv = process.argv.slice(2)
|
|
18
|
+
const flag = (n) => {
|
|
19
|
+
const i = argv.indexOf(n)
|
|
20
|
+
return i < 0 ? null : argv[i + 1] ?? null
|
|
21
|
+
}
|
|
22
|
+
const has = (n) => argv.includes(n)
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 🔴 플래그가 환경변수를 이긴다.
|
|
26
|
+
*
|
|
27
|
+
* CI 안에서 손으로 한 번 더 돌려볼 때 환경변수가 남아 있으면 **내가 준 값이
|
|
28
|
+
* 무시되고 엉뚱한 판정이 나온다.** 그러면 "손으로는 되는데 CI 에서만 막힌다"
|
|
29
|
+
* 같은 착각이 생기고, 그 착각은 규칙을 못 믿게 만든다.
|
|
30
|
+
*/
|
|
31
|
+
const source = flag('--source') ?? process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME ?? ''
|
|
32
|
+
const target = flag('--target') ?? process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME ?? ''
|
|
33
|
+
|
|
34
|
+
if (!source || !target) {
|
|
35
|
+
console.error('소스/대상 브랜치를 알 수 없습니다.')
|
|
36
|
+
console.error('')
|
|
37
|
+
console.error(' CI 에서라면 이 잡이 MR 파이프라인에서만 돌게 되어 있는지 보세요.')
|
|
38
|
+
console.error(' (`rules:` 에 `$CI_PIPELINE_SOURCE == "merge_request_event"` 가 있어야')
|
|
39
|
+
console.error(' `CI_MERGE_REQUEST_*` 변수가 내려옵니다. 브랜치 파이프라인에는 없습니다.)')
|
|
40
|
+
console.error('')
|
|
41
|
+
console.error(' 손으로 돌린다면:')
|
|
42
|
+
console.error(' node axmap/tools/mr-target.mjs --source feat/S15P21E201-144-login --target front/dev')
|
|
43
|
+
process.exit(EXIT.UNDECIDABLE)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = checkTarget(source, target)
|
|
47
|
+
console.log(formatCheck(result))
|
|
48
|
+
if (has('--json')) console.log(JSON.stringify(result, null, 2))
|
|
49
|
+
process.exit(result.exit)
|