relayax-cli 0.2.41 → 0.3.41
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/diff.d.ts +2 -0
- package/dist/commands/diff.js +72 -0
- package/dist/commands/install.js +73 -205
- package/dist/commands/join.d.ts +3 -2
- package/dist/commands/join.js +18 -69
- package/dist/commands/list.js +18 -21
- package/dist/commands/orgs.d.ts +10 -0
- package/dist/commands/orgs.js +128 -0
- package/dist/commands/publish.js +51 -57
- package/dist/commands/search.js +1 -1
- package/dist/commands/versions.d.ts +2 -0
- package/dist/commands/versions.js +44 -0
- package/dist/index.js +6 -2
- package/dist/lib/api.d.ts +7 -6
- package/dist/lib/api.js +14 -29
- package/dist/lib/command-adapter.js +15 -15
- package/dist/lib/config.d.ts +9 -4
- package/dist/lib/config.js +105 -23
- package/dist/lib/preamble.js +3 -4
- package/dist/lib/slug.d.ts +0 -1
- package/dist/lib/slug.js +3 -7
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerDiff = registerDiff;
|
|
4
|
+
const api_js_1 = require("../lib/api.js");
|
|
5
|
+
const slug_js_1 = require("../lib/slug.js");
|
|
6
|
+
const storage_js_1 = require("../lib/storage.js");
|
|
7
|
+
function registerDiff(program) {
|
|
8
|
+
program
|
|
9
|
+
.command('diff <slug> <v1> <v2>')
|
|
10
|
+
.description('두 버전의 패키지를 비교합니다')
|
|
11
|
+
.action(async (slugInput, v1, v2) => {
|
|
12
|
+
const json = program.opts().json ?? false;
|
|
13
|
+
try {
|
|
14
|
+
const resolved = await (0, slug_js_1.resolveSlug)(slugInput);
|
|
15
|
+
const versions = await (0, api_js_1.fetchTeamVersions)(resolved.full);
|
|
16
|
+
const ver1 = versions.find((v) => v.version === v1);
|
|
17
|
+
const ver2 = versions.find((v) => v.version === v2);
|
|
18
|
+
if (!ver1 || !ver2) {
|
|
19
|
+
throw new Error(`버전을 찾을 수 없습니다. 사용 가능: ${versions.map((v) => v.version).join(', ')}`);
|
|
20
|
+
}
|
|
21
|
+
if (!json) {
|
|
22
|
+
console.log(`\n\x1b[1m${resolved.full}\x1b[0m v${v1} ↔ v${v2} 비교 중...\n`);
|
|
23
|
+
}
|
|
24
|
+
// Download both versions to temp dirs
|
|
25
|
+
const tempDir1 = (0, storage_js_1.makeTempDir)();
|
|
26
|
+
const tempDir2 = (0, storage_js_1.makeTempDir)();
|
|
27
|
+
try {
|
|
28
|
+
// Get download URLs for both versions via registry API
|
|
29
|
+
// For now, we use the current version's package_url as fallback
|
|
30
|
+
// The registry API returns the latest version; for specific versions,
|
|
31
|
+
// we'd need a version-specific endpoint
|
|
32
|
+
const info = await (0, api_js_1.fetchTeamInfo)(resolved.full);
|
|
33
|
+
if (!info.package_url) {
|
|
34
|
+
throw new Error('패키지 URL을 가져올 수 없습니다');
|
|
35
|
+
}
|
|
36
|
+
// Since version-specific download isn't available yet, show versions info
|
|
37
|
+
if (json) {
|
|
38
|
+
console.log(JSON.stringify({
|
|
39
|
+
slug: resolved.full,
|
|
40
|
+
v1: { version: v1, created_at: ver1.created_at, changelog: ver1.changelog },
|
|
41
|
+
v2: { version: v2, created_at: ver2.created_at, changelog: ver2.changelog },
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.log(` v${v1} (${new Date(ver1.created_at).toLocaleDateString('ko-KR')})`);
|
|
46
|
+
if (ver1.changelog)
|
|
47
|
+
console.log(` ${ver1.changelog}`);
|
|
48
|
+
console.log();
|
|
49
|
+
console.log(` v${v2} (${new Date(ver2.created_at).toLocaleDateString('ko-KR')})`);
|
|
50
|
+
if (ver2.changelog)
|
|
51
|
+
console.log(` ${ver2.changelog}`);
|
|
52
|
+
console.log();
|
|
53
|
+
console.log(`\x1b[33m 버전별 패키지 다운로드 비교는 추후 지원 예정입니다.\x1b[0m`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
(0, storage_js_1.removeTempDir)(tempDir1);
|
|
58
|
+
(0, storage_js_1.removeTempDir)(tempDir2);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
63
|
+
if (json) {
|
|
64
|
+
console.error(JSON.stringify({ error: 'DIFF_FAILED', message }));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.error(`\x1b[31m오류: ${message}\x1b[0m`);
|
|
68
|
+
}
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
package/dist/commands/install.js
CHANGED
|
@@ -12,31 +12,13 @@ const config_js_1 = require("../lib/config.js");
|
|
|
12
12
|
const slug_js_1 = require("../lib/slug.js");
|
|
13
13
|
const contact_format_js_1 = require("../lib/contact-format.js");
|
|
14
14
|
const preamble_js_1 = require("../lib/preamble.js");
|
|
15
|
-
const join_js_1 = require("./join.js");
|
|
16
15
|
const init_js_1 = require("./init.js");
|
|
17
|
-
/**
|
|
18
|
-
* slugInput이 "@spaces/{spaceSlug}/{teamSlug}" 또는 "@{spaceSlug}/{teamSlug}" 형식이면 파싱해 반환.
|
|
19
|
-
* 아니면 null.
|
|
20
|
-
*/
|
|
21
|
-
function parseSpaceTarget(slugInput) {
|
|
22
|
-
// @spaces/{spaceSlug}/{teamSlug} 형식 (기존)
|
|
23
|
-
const m1 = slugInput.match(/^@spaces\/([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)$/);
|
|
24
|
-
if (m1) {
|
|
25
|
-
return { spaceSlug: m1[1], rawTeamSlug: m1[2], teamSlug: `@${m1[1]}/${m1[2]}` };
|
|
26
|
-
}
|
|
27
|
-
// @{spaceSlug}/{teamSlug} 형식 (신규)
|
|
28
|
-
const m2 = slugInput.match(/^@([a-z0-9][a-z0-9-]*)\/([a-z0-9][a-z0-9-]*)$/);
|
|
29
|
-
if (m2) {
|
|
30
|
-
return { spaceSlug: m2[1], rawTeamSlug: m2[2], teamSlug: `@${m2[1]}/${m2[2]}` };
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
16
|
function registerInstall(program) {
|
|
35
17
|
program
|
|
36
18
|
.command('install <slug>')
|
|
37
19
|
.description('에이전트 팀 패키지를 .relay/teams/에 다운로드합니다')
|
|
38
|
-
.option('--join-code <code>', '
|
|
39
|
-
.action(async (slugInput,
|
|
20
|
+
.option('--join-code <code>', '초대 코드 (Organization 팀 설치 시 자동 가입)')
|
|
21
|
+
.action(async (slugInput, _opts) => {
|
|
40
22
|
const json = program.opts().json ?? false;
|
|
41
23
|
const projectPath = process.cwd();
|
|
42
24
|
const tempDir = (0, storage_js_1.makeTempDir)();
|
|
@@ -48,201 +30,94 @@ function registerInstall(program) {
|
|
|
48
30
|
(0, init_js_1.installGlobalUserCommands)();
|
|
49
31
|
}
|
|
50
32
|
try {
|
|
51
|
-
//
|
|
52
|
-
const spaceTarget = parseSpaceTarget(slugInput);
|
|
53
|
-
// 0a. --join-code가 있으면 먼저 Space 가입 시도
|
|
54
|
-
if (opts.joinCode && spaceTarget) {
|
|
55
|
-
try {
|
|
56
|
-
const { spaceName } = await (0, join_js_1.joinSpace)(spaceTarget.spaceSlug, opts.joinCode);
|
|
57
|
-
if (!json) {
|
|
58
|
-
console.log(`\x1b[32m✅ ${spaceName} Space에 가입했습니다\x1b[0m`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
catch (joinErr) {
|
|
62
|
-
const joinMsg = joinErr instanceof Error ? joinErr.message : String(joinErr);
|
|
63
|
-
// 이미 멤버인 경우 설치 계속 진행
|
|
64
|
-
if (joinMsg !== 'ALREADY_MEMBER') {
|
|
65
|
-
if (!json) {
|
|
66
|
-
console.error(`\x1b[33m경고: Space 가입 실패 — ${joinMsg}\x1b[0m`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// 0b. Resolve scoped slug and fetch team metadata
|
|
33
|
+
// Resolve scoped slug and fetch team metadata
|
|
72
34
|
let team;
|
|
73
35
|
let slug;
|
|
74
36
|
let parsed;
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
37
|
+
// Extract version from @owner/team@version syntax
|
|
38
|
+
// Extract version from @owner/team@version syntax (e.g. acme/writer@1.2.0)
|
|
39
|
+
// Version-specific install is not yet supported by the registry API;
|
|
40
|
+
// the match is kept for future use when per-version package URLs are available.
|
|
41
|
+
const versionMatch = slugInput.match(/^(.+)@(\d+\.\d+\.\d+.*)$/);
|
|
42
|
+
const actualSlugInput = versionMatch ? versionMatch[1] : slugInput;
|
|
43
|
+
parsed = await (0, slug_js_1.resolveSlug)(actualSlugInput);
|
|
44
|
+
slug = parsed.full;
|
|
45
|
+
try {
|
|
46
|
+
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
47
|
+
}
|
|
48
|
+
catch (fetchErr) {
|
|
49
|
+
const fetchMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
50
|
+
if (fetchMsg.includes('403')) {
|
|
51
|
+
// Parse error body for membership_status, visibility, purchase_info
|
|
52
|
+
let membershipStatus;
|
|
53
|
+
let errorVisibility;
|
|
54
|
+
let purchaseInfo;
|
|
55
|
+
try {
|
|
56
|
+
const errBody = JSON.parse(fetchMsg.replace(/^.*?(\{)/, '{'));
|
|
57
|
+
membershipStatus = typeof errBody.membership_status === 'string' ? errBody.membership_status : undefined;
|
|
58
|
+
errorVisibility = typeof errBody.visibility === 'string' ? errBody.visibility : undefined;
|
|
59
|
+
if (errBody.purchase_info && typeof errBody.purchase_info === 'object') {
|
|
60
|
+
purchaseInfo = errBody.purchase_info;
|
|
61
|
+
}
|
|
90
62
|
}
|
|
91
|
-
|
|
63
|
+
catch { /* ignore parse errors */ }
|
|
64
|
+
// Gated team: show purchase info + relay access hint
|
|
65
|
+
if (errorVisibility === 'gated' || purchaseInfo) {
|
|
92
66
|
if (json) {
|
|
93
67
|
console.error(JSON.stringify({
|
|
94
|
-
error: '
|
|
95
|
-
message: '이 팀은
|
|
96
|
-
|
|
97
|
-
|
|
68
|
+
error: 'GATED_ACCESS_REQUIRED',
|
|
69
|
+
message: '이 팀은 접근 권한이 필요합니다.',
|
|
70
|
+
slug,
|
|
71
|
+
purchase_info: purchaseInfo ?? null,
|
|
72
|
+
fix: '접근 링크 코드가 있으면: relay access <slug> --code <코드>',
|
|
98
73
|
}));
|
|
99
74
|
}
|
|
100
75
|
else {
|
|
101
|
-
console.error('\x1b[31m이 팀은
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
throw fetchErr;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
if (!usedSpacePath) {
|
|
111
|
-
// Fallback: treat as normal registry install
|
|
112
|
-
parsed = await (0, slug_js_1.resolveSlug)(slugInput);
|
|
113
|
-
slug = parsed.full;
|
|
114
|
-
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
// slug from server is "@spaces/{spaceSlug}/{teamSlug}" — derive local path parts
|
|
118
|
-
// team is guaranteed assigned by installSpaceTeam above (usedSpacePath === true)
|
|
119
|
-
parsed = { owner: spaceTarget.spaceSlug, name: spaceTarget.rawTeamSlug, full: team.slug };
|
|
120
|
-
slug = team.slug;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
// Normal registry install
|
|
125
|
-
parsed = await (0, slug_js_1.resolveSlug)(slugInput);
|
|
126
|
-
slug = parsed.full;
|
|
127
|
-
try {
|
|
128
|
-
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
129
|
-
}
|
|
130
|
-
catch (fetchErr) {
|
|
131
|
-
const fetchMsg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
|
|
132
|
-
if (fetchMsg.includes('403')) {
|
|
133
|
-
// Parse error body for join_policy, membership_status, visibility, purchase_info
|
|
134
|
-
let joinPolicy;
|
|
135
|
-
let membershipStatus;
|
|
136
|
-
let errorVisibility;
|
|
137
|
-
let purchaseInfo;
|
|
138
|
-
try {
|
|
139
|
-
const errBody = JSON.parse(fetchMsg.replace(/^.*?(\{)/, '{'));
|
|
140
|
-
joinPolicy = typeof errBody.join_policy === 'string' ? errBody.join_policy : undefined;
|
|
141
|
-
membershipStatus = typeof errBody.membership_status === 'string' ? errBody.membership_status : undefined;
|
|
142
|
-
errorVisibility = typeof errBody.visibility === 'string' ? errBody.visibility : undefined;
|
|
143
|
-
if (errBody.purchase_info && typeof errBody.purchase_info === 'object') {
|
|
144
|
-
purchaseInfo = errBody.purchase_info;
|
|
76
|
+
console.error('\x1b[31m이 팀은 접근 권한이 필요합니다.\x1b[0m');
|
|
77
|
+
if (purchaseInfo?.message) {
|
|
78
|
+
console.error(`\n \x1b[36m${purchaseInfo.message}\x1b[0m`);
|
|
145
79
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Gated team: show purchase info + relay access hint
|
|
149
|
-
if (errorVisibility === 'gated' || purchaseInfo) {
|
|
150
|
-
if (json) {
|
|
151
|
-
console.error(JSON.stringify({
|
|
152
|
-
error: 'GATED_ACCESS_REQUIRED',
|
|
153
|
-
message: '이 팀은 접근 권한이 필요합니다.',
|
|
154
|
-
slug,
|
|
155
|
-
purchase_info: purchaseInfo ?? null,
|
|
156
|
-
fix: '접근 링크 코드가 있으면: relay access <slug> --code <코드>',
|
|
157
|
-
}));
|
|
80
|
+
if (purchaseInfo?.url) {
|
|
81
|
+
console.error(` \x1b[36m${purchaseInfo.url}\x1b[0m`);
|
|
158
82
|
}
|
|
159
|
-
|
|
160
|
-
console.error('\x1b[31m🔒 이 팀은 접근 권한이 필요합니다.\x1b[0m');
|
|
161
|
-
if (purchaseInfo?.message) {
|
|
162
|
-
console.error(`\n \x1b[36m${purchaseInfo.message}\x1b[0m`);
|
|
163
|
-
}
|
|
164
|
-
if (purchaseInfo?.url) {
|
|
165
|
-
console.error(` \x1b[36m${purchaseInfo.url}\x1b[0m`);
|
|
166
|
-
}
|
|
167
|
-
console.error(`\n\x1b[33m접근 링크 코드가 있으면: relay access ${slugInput} --code <코드>\x1b[0m`);
|
|
168
|
-
}
|
|
169
|
-
process.exit(1);
|
|
83
|
+
console.error(`\n\x1b[33m접근 링크 코드가 있으면: relay access ${slugInput} --code <코드>\x1b[0m`);
|
|
170
84
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
183
|
-
}
|
|
184
|
-
catch (joinErr) {
|
|
185
|
-
const joinMsg = joinErr instanceof Error ? joinErr.message : String(joinErr);
|
|
186
|
-
if (json) {
|
|
187
|
-
console.error(JSON.stringify({ error: 'JOIN_FAILED', message: joinMsg, slug, fix: 'Space slug와 초대 코드를 확인 후 재시도하세요.' }));
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
console.error(`\x1b[31mSpace 가입 실패: ${joinMsg}\x1b[0m`);
|
|
191
|
-
}
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
else if (joinPolicy === 'approval') {
|
|
196
|
-
const spaceSlug = parsed.owner;
|
|
197
|
-
if (json) {
|
|
198
|
-
console.error(JSON.stringify({
|
|
199
|
-
error: 'APPROVAL_REQUIRED',
|
|
200
|
-
message: `가입 신청이 필요합니다.`,
|
|
201
|
-
slug,
|
|
202
|
-
spaceSlug,
|
|
203
|
-
fix: `relay join @${spaceSlug} --code <초대코드>로 Space에 가입한 후 재시도하세요. 초대 코드는 Space 관리자에게 요청하세요.`,
|
|
204
|
-
}));
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
console.error(`\x1b[33m가입 신청이 필요합니다. \`relay join @${spaceSlug}\`로 가입하세요.\x1b[0m`);
|
|
208
|
-
}
|
|
209
|
-
process.exit(1);
|
|
210
|
-
}
|
|
211
|
-
else if (membershipStatus === 'member') {
|
|
212
|
-
// Member but no access to this specific team
|
|
213
|
-
if (json) {
|
|
214
|
-
console.error(JSON.stringify({
|
|
215
|
-
error: 'NO_ACCESS',
|
|
216
|
-
message: '이 팀에 대한 접근 권한이 없습니다.',
|
|
217
|
-
slug,
|
|
218
|
-
fix: '이 팀의 접근 링크 코드가 있으면 `relay access ' + slugInput + ' --code <코드>`로 접근 권한을 얻으세요. 없으면 팀 제작자에게 문의하세요.',
|
|
219
|
-
}));
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
console.error('\x1b[31m이 팀에 대한 접근 권한이 없습니다.\x1b[0m');
|
|
223
|
-
}
|
|
224
|
-
process.exit(1);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
if (membershipStatus === 'member') {
|
|
88
|
+
// Member but no access to this specific team
|
|
89
|
+
if (json) {
|
|
90
|
+
console.error(JSON.stringify({
|
|
91
|
+
error: 'NO_ACCESS',
|
|
92
|
+
message: '이 팀에 대한 접근 권한이 없습니다.',
|
|
93
|
+
slug,
|
|
94
|
+
fix: '이 팀의 접근 링크 코드가 있으면 `relay access ' + slugInput + ' --code <코드>`로 접근 권한을 얻으세요. 없으면 팀 제작자에게 문의하세요.',
|
|
95
|
+
}));
|
|
225
96
|
}
|
|
226
97
|
else {
|
|
227
|
-
|
|
228
|
-
console.error(JSON.stringify({
|
|
229
|
-
error: 'SPACE_ONLY',
|
|
230
|
-
message: '이 팀은 Space 멤버만 설치 가능합니다.',
|
|
231
|
-
slug,
|
|
232
|
-
fix: 'Space 관리자에게 초대 코드를 요청한 후 `relay join <space-slug> --code <코드>`로 가입하세요.',
|
|
233
|
-
}));
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
console.error('\x1b[31m이 팀은 Space 멤버만 설치 가능합니다.\x1b[0m');
|
|
237
|
-
console.error('\x1b[33mSpace 관리자에게 초대 코드를 요청하세요.\x1b[0m');
|
|
238
|
-
}
|
|
239
|
-
process.exit(1);
|
|
98
|
+
console.error('\x1b[31m이 팀에 대한 접근 권한이 없습니다.\x1b[0m');
|
|
240
99
|
}
|
|
100
|
+
process.exit(1);
|
|
241
101
|
}
|
|
242
102
|
else {
|
|
243
|
-
|
|
103
|
+
if (json) {
|
|
104
|
+
console.error(JSON.stringify({
|
|
105
|
+
error: 'ACCESS_REQUIRED',
|
|
106
|
+
message: '이 팀은 접근 권한이 필요합니다.',
|
|
107
|
+
slug,
|
|
108
|
+
fix: '초대 코드가 있으면 `relay join <org-slug> --code <코드>`로 가입하세요.',
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
console.error('\x1b[31m이 팀은 접근 권한이 필요합니다.\x1b[0m');
|
|
113
|
+
console.error('\x1b[33m초대 코드가 있으면 `relay join <org-slug> --code <코드>`로 가입하세요.\x1b[0m');
|
|
114
|
+
}
|
|
115
|
+
process.exit(1);
|
|
244
116
|
}
|
|
245
117
|
}
|
|
118
|
+
else {
|
|
119
|
+
throw fetchErr;
|
|
120
|
+
}
|
|
246
121
|
}
|
|
247
122
|
if (!team)
|
|
248
123
|
throw new Error('팀 정보를 가져오지 못했습니다.');
|
|
@@ -289,12 +164,7 @@ function registerInstall(program) {
|
|
|
289
164
|
if (!json) {
|
|
290
165
|
console.error('\x1b[33m⚙ 다운로드 URL 만료, 재시도 중...\x1b[0m');
|
|
291
166
|
}
|
|
292
|
-
|
|
293
|
-
team = await (0, api_js_1.installSpaceTeam)(spaceTarget.spaceSlug, spaceTarget.rawTeamSlug);
|
|
294
|
-
}
|
|
295
|
-
else {
|
|
296
|
-
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
297
|
-
}
|
|
167
|
+
team = await (0, api_js_1.fetchTeamInfo)(slug);
|
|
298
168
|
tarPath = await (0, storage_js_1.downloadPackage)(team.package_url, tempDir);
|
|
299
169
|
}
|
|
300
170
|
else {
|
|
@@ -325,14 +195,13 @@ function registerInstall(program) {
|
|
|
325
195
|
return count;
|
|
326
196
|
}
|
|
327
197
|
const fileCount = countFiles(teamDir);
|
|
328
|
-
// 6. Record in installed.json
|
|
198
|
+
// 6. Record in installed.json
|
|
329
199
|
const installed = (0, config_js_1.loadInstalled)();
|
|
330
200
|
installed[slug] = {
|
|
331
201
|
team_id: team.id,
|
|
332
202
|
version: team.version,
|
|
333
203
|
installed_at: new Date().toISOString(),
|
|
334
204
|
files: [teamDir],
|
|
335
|
-
...(spaceTarget ? { space_slug: spaceTarget.spaceSlug } : {}),
|
|
336
205
|
};
|
|
337
206
|
(0, config_js_1.saveInstalled)(installed);
|
|
338
207
|
// 7. Report install + usage ping (non-blocking, team_id 기반)
|
|
@@ -346,7 +215,6 @@ function registerInstall(program) {
|
|
|
346
215
|
commands: team.commands,
|
|
347
216
|
files: fileCount,
|
|
348
217
|
install_path: teamDir,
|
|
349
|
-
...(spaceTarget ? { space_slug: spaceTarget.spaceSlug } : {}),
|
|
350
218
|
author: team.author ? {
|
|
351
219
|
username: team.author.username,
|
|
352
220
|
display_name: team.author.display_name ?? null,
|
package/dist/commands/join.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
export declare function
|
|
3
|
-
|
|
2
|
+
export declare function joinOrg(orgSlug: string, code: string): Promise<{
|
|
3
|
+
type: string;
|
|
4
|
+
role?: string;
|
|
4
5
|
}>;
|
|
5
6
|
export declare function registerJoin(program: Command): void;
|
package/dist/commands/join.js
CHANGED
|
@@ -1,54 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.joinOrg = joinOrg;
|
|
4
4
|
exports.registerJoin = registerJoin;
|
|
5
5
|
const config_js_1 = require("../lib/config.js");
|
|
6
6
|
const init_js_1 = require("./init.js");
|
|
7
|
-
async function
|
|
8
|
-
const res = await fetch(`${config_js_1.API_URL}/api/spaces/${spaceSlug}/teams`, {
|
|
9
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
10
|
-
signal: AbortSignal.timeout(5000),
|
|
11
|
-
});
|
|
12
|
-
if (!res.ok)
|
|
13
|
-
throw new Error(`${res.status}`);
|
|
14
|
-
const data = (await res.json());
|
|
15
|
-
if (Array.isArray(data))
|
|
16
|
-
return data;
|
|
17
|
-
return data.teams ?? [];
|
|
18
|
-
}
|
|
19
|
-
async function joinSpace(spaceSlug, code) {
|
|
7
|
+
async function joinOrg(orgSlug, code) {
|
|
20
8
|
const token = await (0, config_js_1.getValidToken)();
|
|
21
9
|
if (!token) {
|
|
22
10
|
throw new Error('LOGIN_REQUIRED');
|
|
23
11
|
}
|
|
24
|
-
|
|
12
|
+
// Use the invite link via API
|
|
13
|
+
const res = await fetch(`${config_js_1.API_URL}/api/invite-links/${code}/use`, {
|
|
25
14
|
method: 'POST',
|
|
26
15
|
headers: {
|
|
27
16
|
'Content-Type': 'application/json',
|
|
28
17
|
Authorization: `Bearer ${token}`,
|
|
29
18
|
},
|
|
30
|
-
body: JSON.stringify({ code }),
|
|
31
19
|
});
|
|
32
|
-
const body = (await res.json().catch(() => ({})));
|
|
33
20
|
if (!res.ok) {
|
|
21
|
+
const body = await res.json().catch(() => ({}));
|
|
34
22
|
const errCode = body.error ?? String(res.status);
|
|
35
23
|
switch (errCode) {
|
|
36
|
-
case '
|
|
37
|
-
throw new Error('초대 코드가
|
|
38
|
-
case 'EXPIRED_CODE':
|
|
39
|
-
throw new Error('초대 코드가 만료되었습니다.');
|
|
40
|
-
case 'ALREADY_MEMBER':
|
|
41
|
-
throw new Error('ALREADY_MEMBER');
|
|
24
|
+
case 'INVALID_LINK':
|
|
25
|
+
throw new Error('초대 코드가 유효하지 않거나 만료되었습니다.');
|
|
42
26
|
default:
|
|
43
|
-
throw new Error(body.message ??
|
|
27
|
+
throw new Error(body.message ?? `가입 실패 (${res.status})`);
|
|
44
28
|
}
|
|
45
29
|
}
|
|
46
|
-
return
|
|
30
|
+
return res.json();
|
|
47
31
|
}
|
|
48
32
|
function registerJoin(program) {
|
|
49
33
|
program
|
|
50
34
|
.command('join <slug>')
|
|
51
|
-
.description('
|
|
35
|
+
.description('Organization에 초대 코드로 가입합니다')
|
|
52
36
|
.requiredOption('--code <code>', '초대 코드 (UUID)')
|
|
53
37
|
.action(async (slug, opts) => {
|
|
54
38
|
const json = program.opts().json ?? false;
|
|
@@ -62,57 +46,22 @@ function registerJoin(program) {
|
|
|
62
46
|
process.exit(1);
|
|
63
47
|
}
|
|
64
48
|
try {
|
|
65
|
-
const
|
|
49
|
+
const result = await joinOrg(slug, opts.code);
|
|
66
50
|
if (json) {
|
|
67
|
-
|
|
68
|
-
let teams = [];
|
|
69
|
-
try {
|
|
70
|
-
const token = await (0, config_js_1.getValidToken)();
|
|
71
|
-
if (token)
|
|
72
|
-
teams = await fetchSpaceTeams(slug, token);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
// ignore
|
|
76
|
-
}
|
|
77
|
-
console.log(JSON.stringify({ status: 'ok', space: slug, space_name: spaceName, teams: teams.map((t) => ({ slug: t.slug, name: t.name })) }));
|
|
51
|
+
console.log(JSON.stringify({ status: 'ok', ...result }));
|
|
78
52
|
}
|
|
79
53
|
else {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const token = await (0, config_js_1.getValidToken)();
|
|
84
|
-
if (token) {
|
|
85
|
-
const teams = await fetchSpaceTeams(slug, token);
|
|
86
|
-
if (teams.length === 0) {
|
|
87
|
-
console.log('\n아직 추가된 팀이 없습니다.');
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
console.log('\n\x1b[1m📦 사용 가능한 팀:\x1b[0m');
|
|
91
|
-
for (const t of teams) {
|
|
92
|
-
const desc = t.description ? ` \x1b[90m— ${t.description}\x1b[0m` : '';
|
|
93
|
-
console.log(` \x1b[36m•\x1b[0m \x1b[1m${t.slug}\x1b[0m${desc}`);
|
|
94
|
-
}
|
|
95
|
-
console.log(`\n\x1b[33m💡 전체 설치: relay install @spaces/${slug}/<팀슬러그>\x1b[0m`);
|
|
96
|
-
console.log(`\x1b[33m💡 가이드 URL 공유: https://relayax.com/api/spaces/${slug}/guide.md\x1b[0m`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
54
|
+
if (result.type === 'org') {
|
|
55
|
+
console.log(`\x1b[32m✅ @${slug} Organization에 가입했습니다 (역할: ${result.role ?? 'member'})\x1b[0m`);
|
|
56
|
+
console.log(`\n\x1b[33m 대시보드: www.relayax.com/orgs/${slug}\x1b[0m`);
|
|
99
57
|
}
|
|
100
|
-
|
|
101
|
-
|
|
58
|
+
else {
|
|
59
|
+
console.log(`\x1b[32m✅ 팀 접근 권한이 부여되었습니다\x1b[0m`);
|
|
102
60
|
}
|
|
103
61
|
}
|
|
104
62
|
}
|
|
105
63
|
catch (err) {
|
|
106
64
|
const message = err instanceof Error ? err.message : String(err);
|
|
107
|
-
if (message === 'ALREADY_MEMBER') {
|
|
108
|
-
if (json) {
|
|
109
|
-
console.log(JSON.stringify({ status: 'already_member', space: slug }));
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
console.log(`\x1b[33m이미 ${slug} Space의 멤버입니다.\x1b[0m`);
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
65
|
if (message === 'LOGIN_REQUIRED') {
|
|
117
66
|
if (json) {
|
|
118
67
|
console.error(JSON.stringify({
|
|
@@ -128,7 +77,7 @@ function registerJoin(program) {
|
|
|
128
77
|
process.exit(1);
|
|
129
78
|
}
|
|
130
79
|
if (json) {
|
|
131
|
-
console.error(JSON.stringify({ error: 'JOIN_FAILED', message, fix: '
|
|
80
|
+
console.error(JSON.stringify({ error: 'JOIN_FAILED', message, fix: 'slug와 초대 코드를 확인 후 재시도하세요.' }));
|
|
132
81
|
}
|
|
133
82
|
else {
|
|
134
83
|
console.error(`\x1b[31m오류: ${message}\x1b[0m`);
|