relayax-cli 0.2.22 → 0.2.23
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/login.js +5 -5
- package/dist/commands/publish.js +52 -0
- package/dist/commands/spaces.d.ts +11 -0
- package/dist/commands/spaces.js +77 -0
- package/dist/index.js +2 -0
- package/dist/lib/command-adapter.js +12 -2
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -59,6 +59,10 @@ const SUCCESS_HTML = `<!DOCTYPE html>
|
|
|
59
59
|
</body></html>`;
|
|
60
60
|
function waitForToken(port) {
|
|
61
61
|
return new Promise((resolve, reject) => {
|
|
62
|
+
const timeout = setTimeout(() => {
|
|
63
|
+
server.close();
|
|
64
|
+
reject(new Error('로그인 시간이 초과되었습니다 (5분)'));
|
|
65
|
+
}, 5 * 60 * 1000);
|
|
62
66
|
const server = http_1.default.createServer(async (req, res) => {
|
|
63
67
|
const url = new URL(req.url ?? '/', `http://localhost:${port}`);
|
|
64
68
|
if (url.pathname === '/callback') {
|
|
@@ -77,6 +81,7 @@ function waitForToken(port) {
|
|
|
77
81
|
const expires_at = expires_at_raw ? Number(expires_at_raw) : undefined;
|
|
78
82
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
79
83
|
res.end(SUCCESS_HTML);
|
|
84
|
+
clearTimeout(timeout);
|
|
80
85
|
server.close();
|
|
81
86
|
if (token) {
|
|
82
87
|
resolve({ token, refresh_token, expires_at });
|
|
@@ -91,11 +96,6 @@ function waitForToken(port) {
|
|
|
91
96
|
}
|
|
92
97
|
});
|
|
93
98
|
server.listen(port, '127.0.0.1');
|
|
94
|
-
// Timeout after 5 minutes
|
|
95
|
-
setTimeout(() => {
|
|
96
|
-
server.close();
|
|
97
|
-
reject(new Error('로그인 시간이 초과되었습니다 (5분)'));
|
|
98
|
-
}, 5 * 60 * 1000);
|
|
99
99
|
});
|
|
100
100
|
}
|
|
101
101
|
function findAvailablePort() {
|
package/dist/commands/publish.js
CHANGED
|
@@ -481,6 +481,58 @@ function registerPublish(program) {
|
|
|
481
481
|
}));
|
|
482
482
|
process.exit(1);
|
|
483
483
|
}
|
|
484
|
+
// Visibility validation: must be explicitly set
|
|
485
|
+
if (!config.visibility) {
|
|
486
|
+
if (isTTY) {
|
|
487
|
+
const { select: promptSelect } = await import('@inquirer/prompts');
|
|
488
|
+
console.error('\n\x1b[33m⚠ relay.yaml에 visibility가 설정되지 않았습니다.\x1b[0m');
|
|
489
|
+
// Show user's spaces to help decide
|
|
490
|
+
try {
|
|
491
|
+
const { fetchMySpaces } = await import('./spaces.js');
|
|
492
|
+
const spaceToken = opts.token ?? process.env.RELAY_TOKEN ?? await (0, config_js_1.getValidToken)();
|
|
493
|
+
if (spaceToken) {
|
|
494
|
+
const spaces = await fetchMySpaces(spaceToken);
|
|
495
|
+
const nonPersonal = spaces.filter((s) => !s.is_personal);
|
|
496
|
+
if (nonPersonal.length > 0) {
|
|
497
|
+
console.error(`\n 내 Space:`);
|
|
498
|
+
for (const s of nonPersonal) {
|
|
499
|
+
console.error(` \x1b[36m${s.slug}\x1b[0m — ${s.name}`);
|
|
500
|
+
}
|
|
501
|
+
console.error(`\n 비공개로 설정하면 위 Space 멤버만 접근할 수 있습니다.\n`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
// Space 조회 실패는 무시 — visibility 선택은 계속 진행
|
|
507
|
+
}
|
|
508
|
+
config.visibility = await promptSelect({
|
|
509
|
+
message: '공개 범위를 선택하세요:',
|
|
510
|
+
choices: [
|
|
511
|
+
{ name: '공개 — 마켓플레이스에 누구나 검색·설치', value: 'public' },
|
|
512
|
+
{ name: '비공개 — Space 멤버만 접근', value: 'private' },
|
|
513
|
+
],
|
|
514
|
+
});
|
|
515
|
+
// Save back to relay.yaml
|
|
516
|
+
const yamlData = js_yaml_1.default.load(yamlContent);
|
|
517
|
+
yamlData.visibility = config.visibility;
|
|
518
|
+
fs_1.default.writeFileSync(relayYamlPath, js_yaml_1.default.dump(yamlData, { lineWidth: 120 }), 'utf-8');
|
|
519
|
+
console.error(` → relay.yaml에 visibility: ${config.visibility} 저장됨\n`);
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
console.error(JSON.stringify({
|
|
523
|
+
error: 'MISSING_VISIBILITY',
|
|
524
|
+
message: 'relay.yaml에 visibility (public 또는 private)를 설정해주세요.',
|
|
525
|
+
}));
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Confirm visibility before publish
|
|
530
|
+
if (isTTY) {
|
|
531
|
+
const visLabel = config.visibility === 'public'
|
|
532
|
+
? '\x1b[32m공개\x1b[0m (마켓플레이스에 누구나 검색·설치)'
|
|
533
|
+
: '\x1b[33m비공개\x1b[0m (Space 멤버만 접근)';
|
|
534
|
+
console.error(`공개 범위: ${visLabel}`);
|
|
535
|
+
}
|
|
484
536
|
// Profile hint
|
|
485
537
|
if (isTTY) {
|
|
486
538
|
console.error('💡 프로필에 연락처를 설정하면 설치 시 명함이 전달됩니다: www.relayax.com/dashboard/profile');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export interface SpaceInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
slug: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string | null;
|
|
7
|
+
is_personal: boolean;
|
|
8
|
+
role: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function fetchMySpaces(token: string): Promise<SpaceInfo[]>;
|
|
11
|
+
export declare function registerSpaces(program: Command): void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchMySpaces = fetchMySpaces;
|
|
4
|
+
exports.registerSpaces = registerSpaces;
|
|
5
|
+
const config_js_1 = require("../lib/config.js");
|
|
6
|
+
async function fetchMySpaces(token) {
|
|
7
|
+
const res = await fetch(`${config_js_1.API_URL}/api/spaces`, {
|
|
8
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
9
|
+
signal: AbortSignal.timeout(8000),
|
|
10
|
+
});
|
|
11
|
+
if (!res.ok) {
|
|
12
|
+
throw new Error(`Space 목록 조회 실패 (${res.status})`);
|
|
13
|
+
}
|
|
14
|
+
return (await res.json());
|
|
15
|
+
}
|
|
16
|
+
function registerSpaces(program) {
|
|
17
|
+
program
|
|
18
|
+
.command('spaces')
|
|
19
|
+
.description('내 Space 목록을 확인합니다')
|
|
20
|
+
.action(async () => {
|
|
21
|
+
const json = program.opts().json ?? false;
|
|
22
|
+
const token = await (0, config_js_1.getValidToken)();
|
|
23
|
+
if (!token) {
|
|
24
|
+
if (json) {
|
|
25
|
+
console.error(JSON.stringify({ error: 'LOGIN_REQUIRED', message: '로그인이 필요합니다. relay login을 먼저 실행하세요.' }));
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
console.error('\x1b[31m오류: 로그인이 필요합니다.\x1b[0m');
|
|
29
|
+
console.error(' relay login을 먼저 실행하세요.');
|
|
30
|
+
}
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const spaces = await fetchMySpaces(token);
|
|
35
|
+
if (json) {
|
|
36
|
+
console.log(JSON.stringify({ spaces }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const personal = spaces.find((s) => s.is_personal);
|
|
40
|
+
const others = spaces.filter((s) => !s.is_personal);
|
|
41
|
+
if (personal) {
|
|
42
|
+
console.log(`\n\x1b[90m개인 스페이스:\x1b[0m`);
|
|
43
|
+
console.log(` \x1b[36m${personal.slug}\x1b[0m \x1b[1m${personal.name}\x1b[0m`);
|
|
44
|
+
}
|
|
45
|
+
if (others.length > 0) {
|
|
46
|
+
console.log(`\n\x1b[1m내 Space\x1b[0m (${others.length}개):\n`);
|
|
47
|
+
for (const s of others) {
|
|
48
|
+
const role = s.role === 'owner' ? '\x1b[33m소유자\x1b[0m'
|
|
49
|
+
: s.role === 'admin' ? '\x1b[36m관리자\x1b[0m'
|
|
50
|
+
: '\x1b[90m멤버\x1b[0m';
|
|
51
|
+
const desc = s.description
|
|
52
|
+
? ` \x1b[90m${s.description.length > 40 ? s.description.slice(0, 40) + '...' : s.description}\x1b[0m`
|
|
53
|
+
: '';
|
|
54
|
+
console.log(` \x1b[36m${s.slug}\x1b[0m \x1b[1m${s.name}\x1b[0m ${role}${desc}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!personal && others.length === 0) {
|
|
58
|
+
console.log('\nSpace가 없습니다.');
|
|
59
|
+
console.log('\x1b[33m💡 Space를 만들려면: www.relayax.com/spaces/new\x1b[0m');
|
|
60
|
+
}
|
|
61
|
+
if (others.length > 0) {
|
|
62
|
+
console.log(`\n\x1b[33m💡 Space 팀 목록: relay list --space <slug>\x1b[0m`);
|
|
63
|
+
console.log(`\x1b[33m💡 비공개 배포: relay.yaml에 visibility: private 설정 후 relay publish\x1b[0m`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
if (json) {
|
|
69
|
+
console.error(JSON.stringify({ error: 'FETCH_FAILED', message }));
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.error(`\x1b[31m오류: ${message}\x1b[0m`);
|
|
73
|
+
}
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ const check_update_js_1 = require("./commands/check-update.js");
|
|
|
17
17
|
const follow_js_1 = require("./commands/follow.js");
|
|
18
18
|
const changelog_js_1 = require("./commands/changelog.js");
|
|
19
19
|
const join_js_1 = require("./commands/join.js");
|
|
20
|
+
const spaces_js_1 = require("./commands/spaces.js");
|
|
20
21
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
21
22
|
const pkg = require('../package.json');
|
|
22
23
|
const program = new commander_1.Command();
|
|
@@ -40,4 +41,5 @@ program
|
|
|
40
41
|
(0, follow_js_1.registerFollow)(program);
|
|
41
42
|
(0, changelog_js_1.registerChangelog)(program);
|
|
42
43
|
(0, join_js_1.registerJoin)(program);
|
|
44
|
+
(0, spaces_js_1.registerSpaces)(program);
|
|
43
45
|
program.parse();
|
|
@@ -391,7 +391,17 @@ requires:
|
|
|
391
391
|
- tags: 팀 특성에 맞는 태그를 추천합니다.
|
|
392
392
|
- 사용자에게 확인: "이대로 배포할까요?"
|
|
393
393
|
|
|
394
|
-
### 7.
|
|
394
|
+
### 7. 공개 범위 확인 (필수)
|
|
395
|
+
- .relay/relay.yaml에 \`visibility\`가 반드시 설정되어 있어야 합니다.
|
|
396
|
+
- 설정되어 있지 않으면 빌더에게 반드시 물어봅니다:
|
|
397
|
+
- "공개 (마켓플레이스에 누구나 검색·설치 가능)" vs "비공개 (Space 멤버만 접근)"
|
|
398
|
+
- 선택 결과를 relay.yaml에 저장합니다.
|
|
399
|
+
- 이미 설정되어 있으면 현재 값을 확인합니다:
|
|
400
|
+
- 공개인 경우: "⚠ 이 팀은 **공개**로 설정되어 있어 마켓플레이스에 노출됩니다. 맞나요?"
|
|
401
|
+
- 비공개인 경우: "이 팀은 **비공개**로 설정되어 Space 멤버만 접근 가능합니다."
|
|
402
|
+
- 빌더가 변경을 원하면 relay.yaml을 업데이트합니다.
|
|
403
|
+
|
|
404
|
+
### 8. .relay/relay.yaml 업데이트
|
|
395
405
|
- 메타데이터, requires, 포트폴리오 슬롯을 .relay/relay.yaml에 반영합니다.
|
|
396
406
|
|
|
397
407
|
\`\`\`yaml
|
|
@@ -406,7 +416,7 @@ portfolio:
|
|
|
406
416
|
title: "카드뉴스 예시"
|
|
407
417
|
\`\`\`
|
|
408
418
|
|
|
409
|
-
###
|
|
419
|
+
### 9. 배포
|
|
410
420
|
- \`relay publish\` 명령어를 실행합니다.
|
|
411
421
|
- 배포 결과와 마켓플레이스 URL을 보여줍니다.
|
|
412
422
|
${BUSINESS_CARD_FORMAT}
|