@obov/mext 0.0.0-stage → 0.2.0
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/build-cli.d.ts +39 -0
- package/dist/build-cli.js +167 -0
- package/dist/build-cli.js.map +1 -0
- package/dist/build.d.ts +65 -0
- package/dist/build.js +252 -0
- package/dist/build.js.map +1 -0
- package/dist/cli/args.d.ts +107 -0
- package/dist/cli/args.js +621 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bin.d.ts +2 -0
- package/dist/cli/bin.js +13 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/build.d.ts +11 -0
- package/dist/cli/build.js +87 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/dev-lock.d.ts +23 -0
- package/dist/cli/dev-lock.js +68 -0
- package/dist/cli/dev-lock.js.map +1 -0
- package/dist/cli/dev-server.d.ts +20 -0
- package/dist/cli/dev-server.js +75 -0
- package/dist/cli/dev-server.js.map +1 -0
- package/dist/cli/dev.d.ts +21 -0
- package/dist/cli/dev.js +565 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/help.d.ts +31 -0
- package/dist/cli/help.js +238 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/io.d.ts +26 -0
- package/dist/cli/io.js +74 -0
- package/dist/cli/io.js.map +1 -0
- package/dist/cli/login.d.ts +32 -0
- package/dist/cli/login.js +284 -0
- package/dist/cli/login.js.map +1 -0
- package/dist/cli/logout.d.ts +9 -0
- package/dist/cli/logout.js +105 -0
- package/dist/cli/logout.js.map +1 -0
- package/dist/cli/main.d.ts +19 -0
- package/dist/cli/main.js +77 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/mcp.d.ts +16 -0
- package/dist/cli/mcp.js +92 -0
- package/dist/cli/mcp.js.map +1 -0
- package/dist/cli/output.d.ts +78 -0
- package/dist/cli/output.js +229 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/remote.d.ts +17 -0
- package/dist/cli/remote.js +35 -0
- package/dist/cli/remote.js.map +1 -0
- package/dist/cli/run.d.ts +14 -0
- package/dist/cli/run.js +218 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/start.d.ts +8 -0
- package/dist/cli/start.js +37 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/client/cache.d.ts +104 -0
- package/dist/client/cache.js +208 -0
- package/dist/client/cache.js.map +1 -0
- package/dist/client/main.d.ts +31 -0
- package/dist/client/main.js +101 -0
- package/dist/client/main.js.map +1 -0
- package/dist/client/manifest.d.ts +19 -0
- package/dist/client/manifest.js +68 -0
- package/dist/client/manifest.js.map +1 -0
- package/dist/client/runner.d.ts +52 -0
- package/dist/client/runner.js +563 -0
- package/dist/client/runner.js.map +1 -0
- package/dist/command.d.ts +75 -0
- package/dist/command.js +44 -0
- package/dist/command.js.map +1 -0
- package/dist/config.d.ts +111 -0
- package/dist/config.js +197 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +74 -0
- package/dist/errors.js +38 -0
- package/dist/errors.js.map +1 -0
- package/dist/ids.d.ts +6 -0
- package/dist/ids.js +12 -0
- package/dist/ids.js.map +1 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.d.ts +97 -0
- package/dist/instrumentation.js +165 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/invoke.d.ts +64 -0
- package/dist/invoke.js +152 -0
- package/dist/invoke.js.map +1 -0
- package/dist/lookup.d.ts +9 -0
- package/dist/lookup.js +27 -0
- package/dist/lookup.js.map +1 -0
- package/dist/manifest.d.ts +68 -0
- package/dist/manifest.js +77 -0
- package/dist/manifest.js.map +1 -0
- package/dist/mcp/constants.d.ts +9 -0
- package/dist/mcp/constants.js +10 -0
- package/dist/mcp/constants.js.map +1 -0
- package/dist/mcp/dispatch.d.ts +66 -0
- package/dist/mcp/dispatch.js +249 -0
- package/dist/mcp/dispatch.js.map +1 -0
- package/dist/mcp/help.d.ts +57 -0
- package/dist/mcp/help.js +136 -0
- package/dist/mcp/help.js.map +1 -0
- package/dist/mcp/http.d.ts +96 -0
- package/dist/mcp/http.js +368 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.d.ts +15 -0
- package/dist/mcp/index.js +9 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/install.d.ts +17 -0
- package/dist/mcp/install.js +36 -0
- package/dist/mcp/install.js.map +1 -0
- package/dist/mcp/mrtr.d.ts +82 -0
- package/dist/mcp/mrtr.js +130 -0
- package/dist/mcp/mrtr.js.map +1 -0
- package/dist/mcp/server.d.ts +63 -0
- package/dist/mcp/server.js +210 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/stdio.d.ts +75 -0
- package/dist/mcp/stdio.js +222 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/principal.d.ts +16 -0
- package/dist/principal.js +24 -0
- package/dist/principal.js.map +1 -0
- package/dist/registry.d.ts +90 -0
- package/dist/registry.js +169 -0
- package/dist/registry.js.map +1 -0
- package/dist/remote/browser.d.ts +15 -0
- package/dist/remote/browser.js +47 -0
- package/dist/remote/browser.js.map +1 -0
- package/dist/remote/client.d.ts +113 -0
- package/dist/remote/client.js +312 -0
- package/dist/remote/client.js.map +1 -0
- package/dist/remote/credentials.d.ts +48 -0
- package/dist/remote/credentials.js +196 -0
- package/dist/remote/credentials.js.map +1 -0
- package/dist/remote/index.d.ts +8 -0
- package/dist/remote/index.js +5 -0
- package/dist/remote/index.js.map +1 -0
- package/dist/remote/oauth.d.ts +186 -0
- package/dist/remote/oauth.js +507 -0
- package/dist/remote/oauth.js.map +1 -0
- package/dist/scan.d.ts +34 -0
- package/dist/scan.js +155 -0
- package/dist/scan.js.map +1 -0
- package/dist/start.d.ts +63 -0
- package/dist/start.js +269 -0
- package/dist/start.js.map +1 -0
- package/dist/suggest.d.ts +7 -0
- package/dist/suggest.js +45 -0
- package/dist/suggest.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +13 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -4
- package/README.md +0 -3
package/dist/cli/mcp.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { loadConfig } from "../config.js";
|
|
2
|
+
import { DEFAULT_HOST, DEFAULT_PORT, MCP_PATH } from "../mcp/constants.js";
|
|
3
|
+
import { mcpInstallText } from "../mcp/install.js";
|
|
4
|
+
import { table } from "./help.js";
|
|
5
|
+
import { EXIT, exitCodeOf, formatError } from "./output.js";
|
|
6
|
+
export const MCP_PROG = "mext mcp";
|
|
7
|
+
/**
|
|
8
|
+
* mext mcp install [--name] [--url] MCP 클라이언트 등록 안내 출력 (설정 파일은 쓰지 않음)
|
|
9
|
+
* 서버 형태 (mext mcp · mext mcp --stdio) 는 제거: mext dev · mext dev --stdio (운영 = mext start)
|
|
10
|
+
*/
|
|
11
|
+
export async function mcpCommand(argv, io) {
|
|
12
|
+
const parsed = parseMcpArgs(argv);
|
|
13
|
+
if (typeof parsed === "string") {
|
|
14
|
+
io.stderr.write(`error: ${parsed}\nRun "${MCP_PROG} --help" for usage.\n`);
|
|
15
|
+
return EXIT.usage;
|
|
16
|
+
}
|
|
17
|
+
if (parsed.help) {
|
|
18
|
+
io.stdout.write(mcpHelp());
|
|
19
|
+
return EXIT.ok;
|
|
20
|
+
}
|
|
21
|
+
return install(parsed, io);
|
|
22
|
+
}
|
|
23
|
+
/** 반환 문자열 = 사용법 오류 메시지 */
|
|
24
|
+
export function parseMcpArgs(argv) {
|
|
25
|
+
const [first, ...rest] = argv;
|
|
26
|
+
if (first === "--help" || first === "-h")
|
|
27
|
+
return { help: true };
|
|
28
|
+
if (first !== "install") {
|
|
29
|
+
if (argv.includes("--stdio"))
|
|
30
|
+
return `"${MCP_PROG} --stdio" was removed. Use "mext dev --stdio"`;
|
|
31
|
+
if (first === undefined || first.startsWith("--")) {
|
|
32
|
+
return `the "${MCP_PROG}" server was removed. Use "mext dev" (development) or "mext start" (production)`;
|
|
33
|
+
}
|
|
34
|
+
return `unknown subcommand "${first}"`;
|
|
35
|
+
}
|
|
36
|
+
const args = { help: false };
|
|
37
|
+
for (let i = 0; i < rest.length; i++) {
|
|
38
|
+
const tok = rest[i];
|
|
39
|
+
if (tok === "--help" || tok === "-h") {
|
|
40
|
+
args.help = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (!tok.startsWith("--"))
|
|
44
|
+
return `unexpected argument "${tok}"`;
|
|
45
|
+
const eq = tok.indexOf("=");
|
|
46
|
+
const name = eq < 0 ? tok.slice(2) : tok.slice(2, eq);
|
|
47
|
+
if (name !== "name" && name !== "url")
|
|
48
|
+
return `unknown flag --${name} for ${MCP_PROG} install`;
|
|
49
|
+
const value = eq < 0 ? rest[++i] : tok.slice(eq + 1);
|
|
50
|
+
if (value === undefined || value === "")
|
|
51
|
+
return `--${name} requires a value`;
|
|
52
|
+
if (name === "name")
|
|
53
|
+
args.name = value;
|
|
54
|
+
else
|
|
55
|
+
args.url = value;
|
|
56
|
+
}
|
|
57
|
+
return args;
|
|
58
|
+
}
|
|
59
|
+
async function install(args, io) {
|
|
60
|
+
let config;
|
|
61
|
+
try {
|
|
62
|
+
config = await loadConfig(io.cwd);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
io.stderr.write(formatError(error, MCP_PROG));
|
|
66
|
+
return exitCodeOf(error);
|
|
67
|
+
}
|
|
68
|
+
const host = config.server?.host ?? DEFAULT_HOST;
|
|
69
|
+
const port = config.server?.port ?? DEFAULT_PORT;
|
|
70
|
+
const url = args.url ?? `http://${host.includes(":") ? `[${host}]` : host}:${port}${MCP_PATH}`;
|
|
71
|
+
io.stdout.write(`${mcpInstallText({ name: args.name ?? "mext", url, auth: config.auth !== undefined })}\n`);
|
|
72
|
+
return EXIT.ok;
|
|
73
|
+
}
|
|
74
|
+
export function mcpHelp() {
|
|
75
|
+
const lines = [
|
|
76
|
+
"Print MCP client registration commands (does not modify client config files).",
|
|
77
|
+
"",
|
|
78
|
+
"Usage:",
|
|
79
|
+
` ${MCP_PROG} install [--name <name>] [--url <url>]`,
|
|
80
|
+
"",
|
|
81
|
+
"Flags:",
|
|
82
|
+
...table([
|
|
83
|
+
["--name <name>", 'Server name to register (default "mext")'],
|
|
84
|
+
["--url <url>", "Streamable HTTP endpoint (default from mext.config.ts server)"],
|
|
85
|
+
["-h, --help", "Show help"],
|
|
86
|
+
]).map((l) => ` ${l}`),
|
|
87
|
+
"",
|
|
88
|
+
'The MCP server itself runs with "mext dev" (development; --stdio for stdio) or "mext start" (production).',
|
|
89
|
+
];
|
|
90
|
+
return `${lines.join("\n")}\n`;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/cli/mcp.ts"],"names":[],"mappings":"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","sourcesContent":["import { loadConfig, type MextConfig } from \"../config.ts\"\nimport { DEFAULT_HOST, DEFAULT_PORT, MCP_PATH } from \"../mcp/constants.ts\"\nimport { mcpInstallText } from \"../mcp/install.ts\"\nimport { table } from \"./help.ts\"\nimport type { CliIo } from \"./io.ts\"\nimport { EXIT, exitCodeOf, formatError } from \"./output.ts\"\n\nexport const MCP_PROG = \"mext mcp\"\n\n/**\n * mext mcp install [--name] [--url] MCP 클라이언트 등록 안내 출력 (설정 파일은 쓰지 않음)\n * 서버 형태 (mext mcp · mext mcp --stdio) 는 제거: mext dev · mext dev --stdio (운영 = mext start)\n */\nexport async function mcpCommand(argv: readonly string[], io: CliIo): Promise<number> {\n const parsed = parseMcpArgs(argv)\n if (typeof parsed === \"string\") {\n io.stderr.write(`error: ${parsed}\\nRun \"${MCP_PROG} --help\" for usage.\\n`)\n return EXIT.usage\n }\n if (parsed.help) {\n io.stdout.write(mcpHelp())\n return EXIT.ok\n }\n return install(parsed, io)\n}\n\ninterface McpArgs {\n help: boolean\n name?: string\n url?: string\n}\n\n/** 반환 문자열 = 사용법 오류 메시지 */\nexport function parseMcpArgs(argv: readonly string[]): McpArgs | string {\n const [first, ...rest] = argv\n if (first === \"--help\" || first === \"-h\") return { help: true }\n if (first !== \"install\") {\n if (argv.includes(\"--stdio\")) return `\"${MCP_PROG} --stdio\" was removed. Use \"mext dev --stdio\"`\n if (first === undefined || first.startsWith(\"--\")) {\n return `the \"${MCP_PROG}\" server was removed. Use \"mext dev\" (development) or \"mext start\" (production)`\n }\n return `unknown subcommand \"${first}\"`\n }\n const args: McpArgs = { help: false }\n for (let i = 0; i < rest.length; i++) {\n const tok = rest[i]!\n if (tok === \"--help\" || tok === \"-h\") {\n args.help = true\n continue\n }\n if (!tok.startsWith(\"--\")) return `unexpected argument \"${tok}\"`\n const eq = tok.indexOf(\"=\")\n const name = eq < 0 ? tok.slice(2) : tok.slice(2, eq)\n if (name !== \"name\" && name !== \"url\") return `unknown flag --${name} for ${MCP_PROG} install`\n const value = eq < 0 ? rest[++i] : tok.slice(eq + 1)\n if (value === undefined || value === \"\") return `--${name} requires a value`\n if (name === \"name\") args.name = value\n else args.url = value\n }\n return args\n}\n\nasync function install(args: McpArgs, io: CliIo): Promise<number> {\n let config: MextConfig\n try {\n config = await loadConfig(io.cwd)\n } catch (error) {\n io.stderr.write(formatError(error, MCP_PROG))\n return exitCodeOf(error)\n }\n const host = config.server?.host ?? DEFAULT_HOST\n const port = config.server?.port ?? DEFAULT_PORT\n const url = args.url ?? `http://${host.includes(\":\") ? `[${host}]` : host}:${port}${MCP_PATH}`\n io.stdout.write(`${mcpInstallText({ name: args.name ?? \"mext\", url, auth: config.auth !== undefined })}\\n`)\n return EXIT.ok\n}\n\nexport function mcpHelp(): string {\n const lines = [\n \"Print MCP client registration commands (does not modify client config files).\",\n \"\",\n \"Usage:\",\n ` ${MCP_PROG} install [--name <name>] [--url <url>]`,\n \"\",\n \"Flags:\",\n ...table([\n [\"--name <name>\", 'Server name to register (default \"mext\")'],\n [\"--url <url>\", \"Streamable HTTP endpoint (default from mext.config.ts server)\"],\n [\"-h, --help\", \"Show help\"],\n ]).map((l) => ` ${l}`),\n \"\",\n 'The MCP server itself runs with \"mext dev\" (development; --stdio for stdio) or \"mext start\" (production).',\n ]\n return `${lines.join(\"\\n\")}\\n`\n}\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ProgressUpdate } from "../command.ts";
|
|
2
|
+
/** exit code (spec §5) */
|
|
3
|
+
export declare const EXIT: {
|
|
4
|
+
readonly ok: 0;
|
|
5
|
+
readonly error: 1;
|
|
6
|
+
readonly usage: 2;
|
|
7
|
+
readonly auth: 77;
|
|
8
|
+
readonly interrupted: 130;
|
|
9
|
+
};
|
|
10
|
+
/** 실행 중 SIGINT (ctx.signal abort) 를 나타내는 CLI 내부 오류 */
|
|
11
|
+
export declare class CliInterrupted extends Error {
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 오류 → exit code.
|
|
16
|
+
* INVALID_ARGUMENT · UNKNOWN_COMMAND · INPUT_UNAVAILABLE = 2 · UNAUTHENTICATED · PERMISSION_DENIED = 77 · SIGINT = 130 · 그 외 = 1
|
|
17
|
+
*/
|
|
18
|
+
export declare function exitCodeOf(error: unknown): number;
|
|
19
|
+
/**
|
|
20
|
+
* --json 오류 본문. MextError = toJSON(), 빌드 오류 = BUILD_ERROR + issues,
|
|
21
|
+
* SIGINT = INTERRUPTED, 핸들러 예외 = EXECUTION_ERROR
|
|
22
|
+
*/
|
|
23
|
+
export declare function errorJson(error: unknown): Record<string, unknown>;
|
|
24
|
+
/** 사람용 오류 텍스트 (끝 줄바꿈 포함) */
|
|
25
|
+
export declare function formatError(error: unknown, prog: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* 사람용 결과 텍스트 (끝 줄바꿈 포함, undefined = 빈 문자열).
|
|
28
|
+
* 문자열 = 그대로 · 원시 값 = String · 객체 배열 = 표 · 원시 배열 = 줄마다 · 객체 = 키 값 정렬. 중첩 값은 JSON 한 줄
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatResult(value: unknown): string;
|
|
31
|
+
/** --json 결과 */
|
|
32
|
+
export declare function formatJson(value: unknown): string;
|
|
33
|
+
/** ctx.progress → stderr 한 줄 */
|
|
34
|
+
export declare function formatProgress(update: ProgressUpdate): string;
|
|
35
|
+
/** cli.errors formatter 입력: 오류 하나 + mext 기본 출력 */
|
|
36
|
+
export interface CliErrorInfo {
|
|
37
|
+
/** MextErrorCode · EXECUTION_ERROR · INTERRUPTED · REMOTE_ERROR · 서버 핸들러 오류 code 등 (= body.code) */
|
|
38
|
+
code: string;
|
|
39
|
+
message: string;
|
|
40
|
+
/** 오류 상세 (MextError.details · 서버 오류 본문의 code · message 외 필드) */
|
|
41
|
+
details: Record<string, unknown>;
|
|
42
|
+
/** INVALID_ARGUMENT issues 의 "경로: 메시지" (경로 없으면 메시지). 그 외 [] */
|
|
43
|
+
reasons: string[];
|
|
44
|
+
/** mext 기본 --json 오류 본문 */
|
|
45
|
+
body: Record<string, unknown>;
|
|
46
|
+
/** mext 기본 사람용 텍스트 (끝 줄바꿈 포함) */
|
|
47
|
+
text: string;
|
|
48
|
+
/** mext 기본 exit code (0 · 1 · 2 · 77 · 130) */
|
|
49
|
+
exitCode: number;
|
|
50
|
+
/** --json 지정 여부 */
|
|
51
|
+
json: boolean;
|
|
52
|
+
}
|
|
53
|
+
/** formatter 출력. 지정한 필드만 기본값 대체 (stderr 기본 = json ? body JSON : text, stdout 기본 = 없음) */
|
|
54
|
+
export interface CliErrorOutput {
|
|
55
|
+
exitCode?: number;
|
|
56
|
+
stderr?: string;
|
|
57
|
+
stdout?: string;
|
|
58
|
+
}
|
|
59
|
+
/** cli.errors 모듈의 default export. undefined 반환 = mext 기본 */
|
|
60
|
+
export type CliErrorFormatter = (error: CliErrorInfo) => CliErrorOutput | undefined | void;
|
|
61
|
+
export interface CliErrorWriter {
|
|
62
|
+
stdout: {
|
|
63
|
+
write(chunk: string): unknown;
|
|
64
|
+
};
|
|
65
|
+
stderr: {
|
|
66
|
+
write(chunk: string): unknown;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 오류 출력 (기본 = json ? body : text) + formatter 적용 → exit code.
|
|
71
|
+
* formatter 예외 · 잘못된 exitCode = 기본값 + stderr 경고 1줄
|
|
72
|
+
*/
|
|
73
|
+
export declare function emitError(io: CliErrorWriter, error: {
|
|
74
|
+
body: Record<string, unknown>;
|
|
75
|
+
text: string;
|
|
76
|
+
exitCode: number;
|
|
77
|
+
json: boolean;
|
|
78
|
+
}, formatter: CliErrorFormatter | undefined): number;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { isMextError, MextBuildError, MextError } from "../errors.js";
|
|
2
|
+
import { toKebab } from "./args.js";
|
|
3
|
+
import { cliPath, table } from "./help.js";
|
|
4
|
+
/** exit code (spec §5) */
|
|
5
|
+
export const EXIT = {
|
|
6
|
+
ok: 0,
|
|
7
|
+
error: 1,
|
|
8
|
+
usage: 2,
|
|
9
|
+
auth: 77,
|
|
10
|
+
interrupted: 130,
|
|
11
|
+
};
|
|
12
|
+
/** 실행 중 SIGINT (ctx.signal abort) 를 나타내는 CLI 내부 오류 */
|
|
13
|
+
export class CliInterrupted extends Error {
|
|
14
|
+
constructor() {
|
|
15
|
+
super("Interrupted.");
|
|
16
|
+
this.name = "CliInterrupted";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 오류 → exit code.
|
|
21
|
+
* INVALID_ARGUMENT · UNKNOWN_COMMAND · INPUT_UNAVAILABLE = 2 · UNAUTHENTICATED · PERMISSION_DENIED = 77 · SIGINT = 130 · 그 외 = 1
|
|
22
|
+
*/
|
|
23
|
+
export function exitCodeOf(error) {
|
|
24
|
+
if (error instanceof CliInterrupted)
|
|
25
|
+
return EXIT.interrupted;
|
|
26
|
+
if (error instanceof MextError) {
|
|
27
|
+
switch (error.code) {
|
|
28
|
+
case "INVALID_ARGUMENT":
|
|
29
|
+
case "UNKNOWN_COMMAND":
|
|
30
|
+
case "INPUT_UNAVAILABLE":
|
|
31
|
+
return EXIT.usage;
|
|
32
|
+
case "UNAUTHENTICATED":
|
|
33
|
+
case "PERMISSION_DENIED":
|
|
34
|
+
return EXIT.auth;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return EXIT.error;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* --json 오류 본문. MextError = toJSON(), 빌드 오류 = BUILD_ERROR + issues,
|
|
41
|
+
* SIGINT = INTERRUPTED, 핸들러 예외 = EXECUTION_ERROR
|
|
42
|
+
*/
|
|
43
|
+
export function errorJson(error) {
|
|
44
|
+
if (error instanceof MextError)
|
|
45
|
+
return error.toJSON();
|
|
46
|
+
if (error instanceof MextBuildError)
|
|
47
|
+
return { code: "BUILD_ERROR", message: error.message, issues: error.issues };
|
|
48
|
+
if (error instanceof CliInterrupted)
|
|
49
|
+
return { code: "INTERRUPTED", message: error.message };
|
|
50
|
+
return { code: "EXECUTION_ERROR", message: error instanceof Error ? error.message : String(error) };
|
|
51
|
+
}
|
|
52
|
+
/** 사람용 오류 텍스트 (끝 줄바꿈 포함) */
|
|
53
|
+
export function formatError(error, prog) {
|
|
54
|
+
const lines = [];
|
|
55
|
+
if (isMextError(error, "INVALID_ARGUMENT")) {
|
|
56
|
+
const d = error.details;
|
|
57
|
+
lines.push(d.command === "" ? "error: invalid arguments" : `error: invalid arguments for ${cliPath(d.command)}`);
|
|
58
|
+
const rows = d.issues.map((i) => {
|
|
59
|
+
if (i.path === "")
|
|
60
|
+
return [i.message];
|
|
61
|
+
return [flagOf(i.path), d.missing_fields.includes(i.path) ? "required" : i.message];
|
|
62
|
+
});
|
|
63
|
+
lines.push(...table(rows.filter((r) => r.length === 2)).map((l) => ` ${l}`));
|
|
64
|
+
lines.push(...rows.filter((r) => r.length === 1).map((r) => ` ${r[0]}`));
|
|
65
|
+
if (d.command !== "")
|
|
66
|
+
lines.push(`Run "${prog} ${cliPath(d.command)} --help" for usage.`);
|
|
67
|
+
}
|
|
68
|
+
else if (isMextError(error, "UNKNOWN_COMMAND")) {
|
|
69
|
+
const d = error.details;
|
|
70
|
+
if (d.command === "")
|
|
71
|
+
lines.push("error: no command given");
|
|
72
|
+
else if (error.message.startsWith("Unknown command"))
|
|
73
|
+
lines.push(`error: unknown command "${cliPath(d.command)}"`);
|
|
74
|
+
else
|
|
75
|
+
lines.push(`error: ${error.message}`);
|
|
76
|
+
if (d.suggested.length > 0)
|
|
77
|
+
lines.push(`Did you mean: ${d.suggested.map((s) => `${prog} ${cliPath(s)}`).join(", ")}?`);
|
|
78
|
+
lines.push(`Run "${usageCommand(prog, d.usage)}" for available commands.`);
|
|
79
|
+
}
|
|
80
|
+
else if (isMextError(error, "PERMISSION_DENIED")) {
|
|
81
|
+
const d = error.details;
|
|
82
|
+
lines.push(`error: ${error.message}`);
|
|
83
|
+
lines.push(` required scopes: ${d.required_scopes.join(", ") || "(none)"}`);
|
|
84
|
+
lines.push(` held scopes: ${d.held_scopes.join(", ") || "(none)"}`);
|
|
85
|
+
}
|
|
86
|
+
else if (isMextError(error, "UNAUTHENTICATED")) {
|
|
87
|
+
lines.push(`error: ${error.message}`);
|
|
88
|
+
if (error.details.login !== undefined)
|
|
89
|
+
lines.push(`Run "${error.details.login}" to log in.`);
|
|
90
|
+
}
|
|
91
|
+
else if (isMextError(error, "INPUT_UNAVAILABLE")) {
|
|
92
|
+
lines.push(`error: ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
else if (error instanceof Error) {
|
|
95
|
+
lines.push(`error: ${error.message}`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
lines.push(`error: ${String(error)}`);
|
|
99
|
+
}
|
|
100
|
+
return `${lines.join("\n")}\n`;
|
|
101
|
+
}
|
|
102
|
+
/** "help" → "mext run help" · "help users" → "mext run users --help" */
|
|
103
|
+
function usageCommand(prog, usage) {
|
|
104
|
+
const target = usage.replace(/^help\s*/, "");
|
|
105
|
+
return target === "" ? `${prog} help` : `${prog} ${cliPath(target)} --help`;
|
|
106
|
+
}
|
|
107
|
+
/** 입력 경로 → 플래그 표기 ("filter.minAge" → "--filter.min-age") */
|
|
108
|
+
function flagOf(path) {
|
|
109
|
+
return `--${path
|
|
110
|
+
.split(".")
|
|
111
|
+
.map((s) => (/^\d+$/.test(s) ? s : toKebab(s)))
|
|
112
|
+
.join(".")}`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 사람용 결과 텍스트 (끝 줄바꿈 포함, undefined = 빈 문자열).
|
|
116
|
+
* 문자열 = 그대로 · 원시 값 = String · 객체 배열 = 표 · 원시 배열 = 줄마다 · 객체 = 키 값 정렬. 중첩 값은 JSON 한 줄
|
|
117
|
+
*/
|
|
118
|
+
export function formatResult(value) {
|
|
119
|
+
if (value === undefined)
|
|
120
|
+
return "";
|
|
121
|
+
if (value === null)
|
|
122
|
+
return "null\n";
|
|
123
|
+
if (typeof value === "string")
|
|
124
|
+
return value.endsWith("\n") ? value : `${value}\n`;
|
|
125
|
+
if (Array.isArray(value)) {
|
|
126
|
+
if (value.length === 0)
|
|
127
|
+
return "(empty)\n";
|
|
128
|
+
if (value.every(isRecord)) {
|
|
129
|
+
const columns = [];
|
|
130
|
+
for (const row of value)
|
|
131
|
+
for (const k of Object.keys(row))
|
|
132
|
+
if (!columns.includes(k))
|
|
133
|
+
columns.push(k);
|
|
134
|
+
return lines(table([columns, ...value.map((row) => columns.map((c) => cell(row[c])))]));
|
|
135
|
+
}
|
|
136
|
+
return lines(value.map(cell));
|
|
137
|
+
}
|
|
138
|
+
if (isRecord(value)) {
|
|
139
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined);
|
|
140
|
+
if (entries.length === 0)
|
|
141
|
+
return "(empty)\n";
|
|
142
|
+
return lines(table(entries.map(([k, v]) => [k, cell(v)])));
|
|
143
|
+
}
|
|
144
|
+
return `${cell(value)}\n`;
|
|
145
|
+
}
|
|
146
|
+
/** --json 결과 */
|
|
147
|
+
export function formatJson(value) {
|
|
148
|
+
return `${JSON.stringify(value === undefined ? null : value, null, 2)}\n`;
|
|
149
|
+
}
|
|
150
|
+
function isRecord(value) {
|
|
151
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
152
|
+
}
|
|
153
|
+
function cell(value) {
|
|
154
|
+
if (value === undefined || value === null)
|
|
155
|
+
return "";
|
|
156
|
+
if (typeof value === "string")
|
|
157
|
+
return value;
|
|
158
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint")
|
|
159
|
+
return String(value);
|
|
160
|
+
if (value instanceof Date)
|
|
161
|
+
return value.toISOString();
|
|
162
|
+
return JSON.stringify(value) ?? "";
|
|
163
|
+
}
|
|
164
|
+
function lines(list) {
|
|
165
|
+
return `${list.join("\n")}\n`;
|
|
166
|
+
}
|
|
167
|
+
/** ctx.progress → stderr 한 줄 */
|
|
168
|
+
export function formatProgress(update) {
|
|
169
|
+
const head = update.total !== undefined ? `[${update.progress}/${update.total}]` : `[${update.progress}]`;
|
|
170
|
+
return `${update.message ? `${head} ${update.message}` : head}\n`;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 오류 출력 (기본 = json ? body : text) + formatter 적용 → exit code.
|
|
174
|
+
* formatter 예외 · 잘못된 exitCode = 기본값 + stderr 경고 1줄
|
|
175
|
+
*/
|
|
176
|
+
export function emitError(io, error, formatter) {
|
|
177
|
+
const fallback = error.json ? formatJson(error.body) : error.text;
|
|
178
|
+
if (!formatter) {
|
|
179
|
+
io.stderr.write(fallback);
|
|
180
|
+
return error.exitCode;
|
|
181
|
+
}
|
|
182
|
+
const { code, message, ...rest } = error.body;
|
|
183
|
+
const details = isRecord(rest.details) ? rest.details : rest;
|
|
184
|
+
const info = {
|
|
185
|
+
code: typeof code === "string" ? code : "EXECUTION_ERROR",
|
|
186
|
+
message: typeof message === "string" ? message : String(message ?? ""),
|
|
187
|
+
details,
|
|
188
|
+
reasons: reasonsOf(details),
|
|
189
|
+
body: error.body,
|
|
190
|
+
text: error.text,
|
|
191
|
+
exitCode: error.exitCode,
|
|
192
|
+
json: error.json,
|
|
193
|
+
};
|
|
194
|
+
let out;
|
|
195
|
+
try {
|
|
196
|
+
out = formatter(info);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
io.stderr.write(fallback);
|
|
200
|
+
io.stderr.write(`warning: cli.errors formatter failed: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
201
|
+
return error.exitCode;
|
|
202
|
+
}
|
|
203
|
+
if (out === undefined || out === null) {
|
|
204
|
+
io.stderr.write(fallback);
|
|
205
|
+
return error.exitCode;
|
|
206
|
+
}
|
|
207
|
+
if (out.stdout !== undefined)
|
|
208
|
+
io.stdout.write(out.stdout);
|
|
209
|
+
io.stderr.write(out.stderr ?? fallback);
|
|
210
|
+
if (out.exitCode === undefined)
|
|
211
|
+
return error.exitCode;
|
|
212
|
+
if (!Number.isInteger(out.exitCode) || out.exitCode < 0 || out.exitCode > 255) {
|
|
213
|
+
io.stderr.write(`warning: cli.errors formatter returned an invalid exitCode (${String(out.exitCode)})\n`);
|
|
214
|
+
return error.exitCode;
|
|
215
|
+
}
|
|
216
|
+
return out.exitCode;
|
|
217
|
+
}
|
|
218
|
+
/** INVALID_ARGUMENT details.issues → "경로: 메시지" */
|
|
219
|
+
function reasonsOf(details) {
|
|
220
|
+
const issues = details.issues;
|
|
221
|
+
if (!Array.isArray(issues))
|
|
222
|
+
return [];
|
|
223
|
+
return issues.flatMap((i) => {
|
|
224
|
+
if (!isRecord(i) || typeof i.message !== "string")
|
|
225
|
+
return [];
|
|
226
|
+
return [typeof i.path === "string" && i.path !== "" ? `${i.path}: ${i.message}` : i.message];
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/cli/output.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProgressUpdate } from \"../command.ts\"\nimport { isMextError, MextBuildError, MextError } from \"../errors.ts\"\nimport { toKebab } from \"./args.ts\"\nimport { cliPath, table } from \"./help.ts\"\n\n/** exit code (spec §5) */\nexport const EXIT = {\n ok: 0,\n error: 1,\n usage: 2,\n auth: 77,\n interrupted: 130,\n} as const\n\n/** 실행 중 SIGINT (ctx.signal abort) 를 나타내는 CLI 내부 오류 */\nexport class CliInterrupted extends Error {\n constructor() {\n super(\"Interrupted.\")\n this.name = \"CliInterrupted\"\n }\n}\n\n/**\n * 오류 → exit code.\n * INVALID_ARGUMENT · UNKNOWN_COMMAND · INPUT_UNAVAILABLE = 2 · UNAUTHENTICATED · PERMISSION_DENIED = 77 · SIGINT = 130 · 그 외 = 1\n */\nexport function exitCodeOf(error: unknown): number {\n if (error instanceof CliInterrupted) return EXIT.interrupted\n if (error instanceof MextError) {\n switch (error.code) {\n case \"INVALID_ARGUMENT\":\n case \"UNKNOWN_COMMAND\":\n case \"INPUT_UNAVAILABLE\":\n return EXIT.usage\n case \"UNAUTHENTICATED\":\n case \"PERMISSION_DENIED\":\n return EXIT.auth\n }\n }\n return EXIT.error\n}\n\n/**\n * --json 오류 본문. MextError = toJSON(), 빌드 오류 = BUILD_ERROR + issues,\n * SIGINT = INTERRUPTED, 핸들러 예외 = EXECUTION_ERROR\n */\nexport function errorJson(error: unknown): Record<string, unknown> {\n if (error instanceof MextError) return error.toJSON()\n if (error instanceof MextBuildError) return { code: \"BUILD_ERROR\", message: error.message, issues: error.issues }\n if (error instanceof CliInterrupted) return { code: \"INTERRUPTED\", message: error.message }\n return { code: \"EXECUTION_ERROR\", message: error instanceof Error ? error.message : String(error) }\n}\n\n/** 사람용 오류 텍스트 (끝 줄바꿈 포함) */\nexport function formatError(error: unknown, prog: string): string {\n const lines: string[] = []\n if (isMextError(error, \"INVALID_ARGUMENT\")) {\n const d = error.details\n lines.push(d.command === \"\" ? \"error: invalid arguments\" : `error: invalid arguments for ${cliPath(d.command)}`)\n const rows = d.issues.map((i) => {\n if (i.path === \"\") return [i.message]\n return [flagOf(i.path), d.missing_fields.includes(i.path) ? \"required\" : i.message]\n })\n lines.push(...table(rows.filter((r) => r.length === 2)).map((l) => ` ${l}`))\n lines.push(...rows.filter((r) => r.length === 1).map((r) => ` ${r[0]}`))\n if (d.command !== \"\") lines.push(`Run \"${prog} ${cliPath(d.command)} --help\" for usage.`)\n } else if (isMextError(error, \"UNKNOWN_COMMAND\")) {\n const d = error.details\n if (d.command === \"\") lines.push(\"error: no command given\")\n else if (error.message.startsWith(\"Unknown command\")) lines.push(`error: unknown command \"${cliPath(d.command)}\"`)\n else lines.push(`error: ${error.message}`)\n if (d.suggested.length > 0) lines.push(`Did you mean: ${d.suggested.map((s) => `${prog} ${cliPath(s)}`).join(\", \")}?`)\n lines.push(`Run \"${usageCommand(prog, d.usage)}\" for available commands.`)\n } else if (isMextError(error, \"PERMISSION_DENIED\")) {\n const d = error.details\n lines.push(`error: ${error.message}`)\n lines.push(` required scopes: ${d.required_scopes.join(\", \") || \"(none)\"}`)\n lines.push(` held scopes: ${d.held_scopes.join(\", \") || \"(none)\"}`)\n } else if (isMextError(error, \"UNAUTHENTICATED\")) {\n lines.push(`error: ${error.message}`)\n if (error.details.login !== undefined) lines.push(`Run \"${error.details.login}\" to log in.`)\n } else if (isMextError(error, \"INPUT_UNAVAILABLE\")) {\n lines.push(`error: ${error.message}`)\n } else if (error instanceof Error) {\n lines.push(`error: ${error.message}`)\n } else {\n lines.push(`error: ${String(error)}`)\n }\n return `${lines.join(\"\\n\")}\\n`\n}\n\n/** \"help\" → \"mext run help\" · \"help users\" → \"mext run users --help\" */\nfunction usageCommand(prog: string, usage: string): string {\n const target = usage.replace(/^help\\s*/, \"\")\n return target === \"\" ? `${prog} help` : `${prog} ${cliPath(target)} --help`\n}\n\n/** 입력 경로 → 플래그 표기 (\"filter.minAge\" → \"--filter.min-age\") */\nfunction flagOf(path: string): string {\n return `--${path\n .split(\".\")\n .map((s) => (/^\\d+$/.test(s) ? s : toKebab(s)))\n .join(\".\")}`\n}\n\n/**\n * 사람용 결과 텍스트 (끝 줄바꿈 포함, undefined = 빈 문자열).\n * 문자열 = 그대로 · 원시 값 = String · 객체 배열 = 표 · 원시 배열 = 줄마다 · 객체 = 키 값 정렬. 중첩 값은 JSON 한 줄\n */\nexport function formatResult(value: unknown): string {\n if (value === undefined) return \"\"\n if (value === null) return \"null\\n\"\n if (typeof value === \"string\") return value.endsWith(\"\\n\") ? value : `${value}\\n`\n if (Array.isArray(value)) {\n if (value.length === 0) return \"(empty)\\n\"\n if (value.every(isRecord)) {\n const columns: string[] = []\n for (const row of value) for (const k of Object.keys(row)) if (!columns.includes(k)) columns.push(k)\n return lines(table([columns, ...value.map((row) => columns.map((c) => cell(row[c])))]))\n }\n return lines(value.map(cell))\n }\n if (isRecord(value)) {\n const entries = Object.entries(value).filter(([, v]) => v !== undefined)\n if (entries.length === 0) return \"(empty)\\n\"\n return lines(table(entries.map(([k, v]) => [k, cell(v)])))\n }\n return `${cell(value)}\\n`\n}\n\n/** --json 결과 */\nexport function formatJson(value: unknown): string {\n return `${JSON.stringify(value === undefined ? null : value, null, 2)}\\n`\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value) && !(value instanceof Date)\n}\n\nfunction cell(value: unknown): string {\n if (value === undefined || value === null) return \"\"\n if (typeof value === \"string\") return value\n if (typeof value === \"number\" || typeof value === \"boolean\" || typeof value === \"bigint\") return String(value)\n if (value instanceof Date) return value.toISOString()\n return JSON.stringify(value) ?? \"\"\n}\n\nfunction lines(list: string[]): string {\n return `${list.join(\"\\n\")}\\n`\n}\n\n/** ctx.progress → stderr 한 줄 */\nexport function formatProgress(update: ProgressUpdate): string {\n const head = update.total !== undefined ? `[${update.progress}/${update.total}]` : `[${update.progress}]`\n return `${update.message ? `${head} ${update.message}` : head}\\n`\n}\n\n/* ---------------------------------------------------------------- 오류 출력 커스텀 (cli.errors, spec §5) */\n\n/** cli.errors formatter 입력: 오류 하나 + mext 기본 출력 */\nexport interface CliErrorInfo {\n /** MextErrorCode · EXECUTION_ERROR · INTERRUPTED · REMOTE_ERROR · 서버 핸들러 오류 code 등 (= body.code) */\n code: string\n message: string\n /** 오류 상세 (MextError.details · 서버 오류 본문의 code · message 외 필드) */\n details: Record<string, unknown>\n /** INVALID_ARGUMENT issues 의 \"경로: 메시지\" (경로 없으면 메시지). 그 외 [] */\n reasons: string[]\n /** mext 기본 --json 오류 본문 */\n body: Record<string, unknown>\n /** mext 기본 사람용 텍스트 (끝 줄바꿈 포함) */\n text: string\n /** mext 기본 exit code (0 · 1 · 2 · 77 · 130) */\n exitCode: number\n /** --json 지정 여부 */\n json: boolean\n}\n\n/** formatter 출력. 지정한 필드만 기본값 대체 (stderr 기본 = json ? body JSON : text, stdout 기본 = 없음) */\nexport interface CliErrorOutput {\n exitCode?: number\n stderr?: string\n stdout?: string\n}\n\n/** cli.errors 모듈의 default export. undefined 반환 = mext 기본 */\nexport type CliErrorFormatter = (error: CliErrorInfo) => CliErrorOutput | undefined | void\n\nexport interface CliErrorWriter {\n stdout: { write(chunk: string): unknown }\n stderr: { write(chunk: string): unknown }\n}\n\n/**\n * 오류 출력 (기본 = json ? body : text) + formatter 적용 → exit code.\n * formatter 예외 · 잘못된 exitCode = 기본값 + stderr 경고 1줄\n */\nexport function emitError(\n io: CliErrorWriter,\n error: { body: Record<string, unknown>; text: string; exitCode: number; json: boolean },\n formatter: CliErrorFormatter | undefined,\n): number {\n const fallback = error.json ? formatJson(error.body) : error.text\n if (!formatter) {\n io.stderr.write(fallback)\n return error.exitCode\n }\n const { code, message, ...rest } = error.body\n const details = isRecord(rest.details) ? rest.details : rest\n const info: CliErrorInfo = {\n code: typeof code === \"string\" ? code : \"EXECUTION_ERROR\",\n message: typeof message === \"string\" ? message : String(message ?? \"\"),\n details,\n reasons: reasonsOf(details),\n body: error.body,\n text: error.text,\n exitCode: error.exitCode,\n json: error.json,\n }\n let out: CliErrorOutput | undefined | void\n try {\n out = formatter(info)\n } catch (e) {\n io.stderr.write(fallback)\n io.stderr.write(`warning: cli.errors formatter failed: ${e instanceof Error ? e.message : String(e)}\\n`)\n return error.exitCode\n }\n if (out === undefined || out === null) {\n io.stderr.write(fallback)\n return error.exitCode\n }\n if (out.stdout !== undefined) io.stdout.write(out.stdout)\n io.stderr.write(out.stderr ?? fallback)\n if (out.exitCode === undefined) return error.exitCode\n if (!Number.isInteger(out.exitCode) || out.exitCode < 0 || out.exitCode > 255) {\n io.stderr.write(`warning: cli.errors formatter returned an invalid exitCode (${String(out.exitCode)})\\n`)\n return error.exitCode\n }\n return out.exitCode\n}\n\n/** INVALID_ARGUMENT details.issues → \"경로: 메시지\" */\nfunction reasonsOf(details: Record<string, unknown>): string[] {\n const issues = details.issues\n if (!Array.isArray(issues)) return []\n return issues.flatMap((i) => {\n if (!isRecord(i) || typeof i.message !== \"string\") return []\n return [typeof i.path === \"string\" && i.path !== \"\" ? `${i.path}: ${i.message}` : i.message]\n })\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CliIo } from "./io.ts";
|
|
2
|
+
/**
|
|
3
|
+
* mext run --url <url> (또는 MEXT_URL) — 원격 실행. 고객 CLI 와 같은 runner (client/runner.ts, bin = mext, 서버 help).
|
|
4
|
+
* 토큰 = MEXT_TOKEN, 없으면 저장된 토큰 (mext login --url). 출력 · exit code 는 로컬 모드와 같다.
|
|
5
|
+
*/
|
|
6
|
+
export interface RemoteTarget {
|
|
7
|
+
url: string;
|
|
8
|
+
/** --url 을 뺀 mext run argv */
|
|
9
|
+
argv: string[];
|
|
10
|
+
/** MEXT_URL 에서 왔는지 (help 의 prog 표기) */
|
|
11
|
+
fromEnv: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** mext run argv 앞쪽 플래그의 --url <url> · --url=<url>, 없으면 MEXT_URL. 둘 다 없으면 undefined (로컬) */
|
|
14
|
+
export declare function remoteTarget(argv: readonly string[], env: Record<string, string | undefined>): RemoteTarget | undefined;
|
|
15
|
+
/** mext login 안내 문구 */
|
|
16
|
+
export declare function loginCommand(url: string): string;
|
|
17
|
+
export declare function runRemoteCommand(target: RemoteTarget, io: CliIo): Promise<number>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { runRemote } from "../client/runner.js";
|
|
2
|
+
import { VERSION } from "../version.js";
|
|
3
|
+
import { RUN_PROG } from "./run.js";
|
|
4
|
+
/** mext run argv 앞쪽 플래그의 --url <url> · --url=<url>, 없으면 MEXT_URL. 둘 다 없으면 undefined (로컬) */
|
|
5
|
+
export function remoteTarget(argv, env) {
|
|
6
|
+
for (let i = 0; i < argv.length; i++) {
|
|
7
|
+
const tok = argv[i];
|
|
8
|
+
if (!tok.startsWith("-") || tok === "-" || tok === "--")
|
|
9
|
+
break;
|
|
10
|
+
if (tok === "--url" || tok.startsWith("--url=")) {
|
|
11
|
+
const url = tok === "--url" ? argv[i + 1] : tok.slice("--url=".length);
|
|
12
|
+
const rest = [...argv.slice(0, i), ...argv.slice(i + (tok === "--url" ? 2 : 1))];
|
|
13
|
+
return { url: url ?? "", argv: rest, fromEnv: false };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const url = env.MEXT_URL;
|
|
17
|
+
if (url !== undefined && url !== "")
|
|
18
|
+
return { url, argv: [...argv], fromEnv: true };
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
/** mext login 안내 문구 */
|
|
22
|
+
export function loginCommand(url) {
|
|
23
|
+
return `mext login --url ${url}`;
|
|
24
|
+
}
|
|
25
|
+
export function runRemoteCommand(target, io) {
|
|
26
|
+
const { url } = target;
|
|
27
|
+
return runRemote({
|
|
28
|
+
bin: "mext",
|
|
29
|
+
url,
|
|
30
|
+
prog: target.fromEnv ? RUN_PROG : `${RUN_PROG} --url ${url}`,
|
|
31
|
+
login: loginCommand(url),
|
|
32
|
+
version: VERSION,
|
|
33
|
+
}, target.argv, io);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=remote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/cli/remote.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAA;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAenC,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,IAAuB,EAAE,GAAuC;IAC3F,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAA;QACpB,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI;YAAE,MAAK;QAC9D,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChD,MAAM,GAAG,GAAG,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;YACtE,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAChF,OAAO,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAA;QACvD,CAAC;IACH,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAA;IACxB,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IACnF,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,uBAAuB;AACvB,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,oBAAoB,GAAG,EAAE,CAAA;AAClC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAoB,EAAE,EAAS;IAC9D,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAA;IACtB,OAAO,SAAS,CACd;QACE,GAAG,EAAE,MAAM;QACX,GAAG;QACH,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,UAAU,GAAG,EAAE;QAC5D,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC;QACxB,OAAO,EAAE,OAAO;KACjB,EACD,MAAM,CAAC,IAAI,EACX,EAAE,CACH,CAAA;AACH,CAAC","sourcesContent":["import { runRemote } from \"../client/runner.ts\"\nimport { VERSION } from \"../version.ts\"\nimport type { CliIo } from \"./io.ts\"\nimport { RUN_PROG } from \"./run.ts\"\n\n/**\n * mext run --url <url> (또는 MEXT_URL) — 원격 실행. 고객 CLI 와 같은 runner (client/runner.ts, bin = mext, 서버 help).\n * 토큰 = MEXT_TOKEN, 없으면 저장된 토큰 (mext login --url). 출력 · exit code 는 로컬 모드와 같다.\n */\n\nexport interface RemoteTarget {\n url: string\n /** --url 을 뺀 mext run argv */\n argv: string[]\n /** MEXT_URL 에서 왔는지 (help 의 prog 표기) */\n fromEnv: boolean\n}\n\n/** mext run argv 앞쪽 플래그의 --url <url> · --url=<url>, 없으면 MEXT_URL. 둘 다 없으면 undefined (로컬) */\nexport function remoteTarget(argv: readonly string[], env: Record<string, string | undefined>): RemoteTarget | undefined {\n for (let i = 0; i < argv.length; i++) {\n const tok = argv[i]!\n if (!tok.startsWith(\"-\") || tok === \"-\" || tok === \"--\") break\n if (tok === \"--url\" || tok.startsWith(\"--url=\")) {\n const url = tok === \"--url\" ? argv[i + 1] : tok.slice(\"--url=\".length)\n const rest = [...argv.slice(0, i), ...argv.slice(i + (tok === \"--url\" ? 2 : 1))]\n return { url: url ?? \"\", argv: rest, fromEnv: false }\n }\n }\n const url = env.MEXT_URL\n if (url !== undefined && url !== \"\") return { url, argv: [...argv], fromEnv: true }\n return undefined\n}\n\n/** mext login 안내 문구 */\nexport function loginCommand(url: string): string {\n return `mext login --url ${url}`\n}\n\nexport function runRemoteCommand(target: RemoteTarget, io: CliIo): Promise<number> {\n const { url } = target\n return runRemote(\n {\n bin: \"mext\",\n url,\n prog: target.fromEnv ? RUN_PROG : `${RUN_PROG} --url ${url}`,\n login: loginCommand(url),\n version: VERSION,\n },\n target.argv,\n io,\n )\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Registry } from "../registry.ts";
|
|
2
|
+
import { type CliIo } from "./io.ts";
|
|
3
|
+
export declare const RUN_PROG = "mext run";
|
|
4
|
+
/**
|
|
5
|
+
* mext run <ns> <cmd> [flags] — 로컬 실행 (ctx.principal.kind = local, mext.config.ts 의 localPrincipal 로 변경 가능).
|
|
6
|
+
* mext run help [target] · mext run [<ns> [<cmd>]] --help · --json · --yes.
|
|
7
|
+
* --url <url> (또는 MEXT_URL) = 원격 실행 (cli/remote.ts)
|
|
8
|
+
*/
|
|
9
|
+
export declare function runCommand(argv: readonly string[], io: CliIo): Promise<number>;
|
|
10
|
+
/** 앞쪽 경로 토큰 → 가장 긴 네임스페이스 · 명령어 (별칭 포함) ID */
|
|
11
|
+
export declare function resolvePath(registry: Registry, tokens: readonly string[]): {
|
|
12
|
+
id: string;
|
|
13
|
+
consumed: number;
|
|
14
|
+
};
|