@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
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { RolldownModule } from "./build.ts";
|
|
2
|
+
import { type CliConfig } from "./config.ts";
|
|
3
|
+
import { type BuildIssue } from "./errors.ts";
|
|
4
|
+
import type { BuildManifest } from "./manifest.ts";
|
|
5
|
+
import type { Registry } from "./registry.ts";
|
|
6
|
+
/**
|
|
7
|
+
* 고객 CLI 패키지 (.mext/cli/, §5). mext build 가 config.cli 가 있을 때 호출.
|
|
8
|
+
* bin.js = client/main.ts (고객 CLI 런타임) + 인라인 설정 (CustomerCliConfig) 의 rolldown 단일 번들. 외부 = Node 내장만
|
|
9
|
+
* (의존성 전부 번들 → package.json dependencies 없음). 런타임은 Zod · MCP 서버 SDK · 핸들러 · 스캔 · 빌드 코드를 import 하지 않는다
|
|
10
|
+
*/
|
|
11
|
+
/** .mext/ 아래 고객 CLI 폴더 */
|
|
12
|
+
export declare const CLI_DIR = "cli";
|
|
13
|
+
export declare const CLI_BIN_FILE = "bin.js";
|
|
14
|
+
/** 고객 CLI package.json engines.node (번들 target node22, 타입 제거 불필요) */
|
|
15
|
+
export declare const CLI_NODE_ENGINE = ">=22";
|
|
16
|
+
export interface CustomerCliBuild {
|
|
17
|
+
/** 절대 경로 (.mext/cli) */
|
|
18
|
+
dir: string;
|
|
19
|
+
bin: string;
|
|
20
|
+
packageName: string;
|
|
21
|
+
version: string;
|
|
22
|
+
form: "bundled" | "universal";
|
|
23
|
+
/** .mext/cli 기준 생성 파일 (정렬) */
|
|
24
|
+
files: string[];
|
|
25
|
+
/** bin.js 바이트 */
|
|
26
|
+
size: number;
|
|
27
|
+
}
|
|
28
|
+
/** cli 설정이 있을 때 루트 세그먼트가 고객 CLI 내장 명령 (login · logout · mcp) 과 겹치면 RESERVED_ID */
|
|
29
|
+
export declare function customerCliIssues(cli: CliConfig | undefined, registry: Registry): BuildIssue[];
|
|
30
|
+
export interface BuildCustomerCliOptions {
|
|
31
|
+
rolldown: RolldownModule;
|
|
32
|
+
/** 도입 프로젝트 루트 (package.json version) */
|
|
33
|
+
cwd: string;
|
|
34
|
+
/** .mext/ 절대 경로 */
|
|
35
|
+
outDir: string;
|
|
36
|
+
cli: CliConfig;
|
|
37
|
+
manifest: BuildManifest;
|
|
38
|
+
}
|
|
39
|
+
export declare function buildCustomerCli(options: BuildCustomerCliOptions): Promise<CustomerCliBuild>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { isBuiltin } from "node:module";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { clientManifest } from "./client/manifest.js";
|
|
6
|
+
import { CLI_BUILTIN_COMMANDS } from "./config.js";
|
|
7
|
+
import { MextBuildError } from "./errors.js";
|
|
8
|
+
import { envPrefix } from "./remote/credentials.js";
|
|
9
|
+
/**
|
|
10
|
+
* 고객 CLI 패키지 (.mext/cli/, §5). mext build 가 config.cli 가 있을 때 호출.
|
|
11
|
+
* bin.js = client/main.ts (고객 CLI 런타임) + 인라인 설정 (CustomerCliConfig) 의 rolldown 단일 번들. 외부 = Node 내장만
|
|
12
|
+
* (의존성 전부 번들 → package.json dependencies 없음). 런타임은 Zod · MCP 서버 SDK · 핸들러 · 스캔 · 빌드 코드를 import 하지 않는다
|
|
13
|
+
*/
|
|
14
|
+
/** .mext/ 아래 고객 CLI 폴더 */
|
|
15
|
+
export const CLI_DIR = "cli";
|
|
16
|
+
export const CLI_BIN_FILE = "bin.js";
|
|
17
|
+
/** 고객 CLI package.json engines.node (번들 target node22, 타입 제거 불필요) */
|
|
18
|
+
export const CLI_NODE_ENGINE = ">=22";
|
|
19
|
+
/** 고객 CLI 런타임 엔트리 (src = .ts, dist = .js: 이 파일과 같은 확장자) */
|
|
20
|
+
const RUNTIME_ENTRY = fileURLToPath(new URL(`./client/main${import.meta.url.slice(import.meta.url.lastIndexOf("."))}`, import.meta.url));
|
|
21
|
+
const ENTRY_ID = "\0mext-customer-cli";
|
|
22
|
+
/** cli 설정이 있을 때 루트 세그먼트가 고객 CLI 내장 명령 (login · logout · mcp) 과 겹치면 RESERVED_ID */
|
|
23
|
+
export function customerCliIssues(cli, registry) {
|
|
24
|
+
if (!cli)
|
|
25
|
+
return [];
|
|
26
|
+
// 루트 별칭은 Registry 가 금지하므로 루트 명령어 · 네임스페이스만 본다
|
|
27
|
+
const root = registry.namespaces.get("");
|
|
28
|
+
const ids = new Set([...(root?.commands ?? []), ...(root?.namespaces ?? [])]);
|
|
29
|
+
return [...ids]
|
|
30
|
+
.filter((id) => CLI_BUILTIN_COMMANDS.includes(id))
|
|
31
|
+
.sort()
|
|
32
|
+
.map((id) => {
|
|
33
|
+
const file = registry.commands.get(id)?.file;
|
|
34
|
+
return {
|
|
35
|
+
code: "RESERVED_ID",
|
|
36
|
+
message: `"${id}" is a built-in command of the customer CLI (${cli.bin} ${id}). Rename it or remove cli from mext.config.ts`,
|
|
37
|
+
id,
|
|
38
|
+
...(file !== undefined ? { file } : {}),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export async function buildCustomerCli(options) {
|
|
43
|
+
const { cli, manifest } = options;
|
|
44
|
+
const dir = join(options.outDir, CLI_DIR);
|
|
45
|
+
mkdirSync(dir, { recursive: true });
|
|
46
|
+
const version = cli.version ?? projectVersion(options.cwd) ?? "0.0.0";
|
|
47
|
+
const packageName = cli.packageName ?? cli.bin;
|
|
48
|
+
const bundled = cli.manifest !== false;
|
|
49
|
+
const config = {
|
|
50
|
+
bin: cli.bin,
|
|
51
|
+
url: cli.url,
|
|
52
|
+
version,
|
|
53
|
+
packageName,
|
|
54
|
+
mextVersion: manifest.mextVersion,
|
|
55
|
+
manifestHash: manifest.manifestHash,
|
|
56
|
+
...(bundled ? { manifest: clientManifest(manifest) } : {}),
|
|
57
|
+
};
|
|
58
|
+
const errorsFile = cli.errors === undefined ? undefined : resolve(options.cwd, cli.errors);
|
|
59
|
+
if (errorsFile !== undefined && !existsSync(errorsFile)) {
|
|
60
|
+
throw new MextBuildError([{ code: "INVALID_CONFIG", message: `cli.errors file not found: ${cli.errors}` }]);
|
|
61
|
+
}
|
|
62
|
+
const entry = [
|
|
63
|
+
`import { main } from ${JSON.stringify(RUNTIME_ENTRY)}`,
|
|
64
|
+
...(errorsFile !== undefined ? [`import formatError from ${JSON.stringify(errorsFile)}`] : []),
|
|
65
|
+
"",
|
|
66
|
+
errorsFile !== undefined
|
|
67
|
+
? `await main(${JSON.stringify(config)}, { formatError })`
|
|
68
|
+
: `await main(${JSON.stringify(config)})`,
|
|
69
|
+
"",
|
|
70
|
+
].join("\n");
|
|
71
|
+
let output;
|
|
72
|
+
try {
|
|
73
|
+
;
|
|
74
|
+
({ output } = await options.rolldown.build({
|
|
75
|
+
cwd: options.cwd,
|
|
76
|
+
input: { bin: ENTRY_ID },
|
|
77
|
+
platform: "node",
|
|
78
|
+
external: (id) => id.startsWith("node:") || isBuiltin(id),
|
|
79
|
+
plugins: [
|
|
80
|
+
{
|
|
81
|
+
name: "mext-customer-cli",
|
|
82
|
+
resolveId: (id) => (id === ENTRY_ID ? ENTRY_ID : null),
|
|
83
|
+
load: (id) => (id === ENTRY_ID ? entry : null),
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
transform: { target: "node22" },
|
|
87
|
+
logLevel: "warn",
|
|
88
|
+
output: {
|
|
89
|
+
dir,
|
|
90
|
+
format: "esm",
|
|
91
|
+
entryFileNames: CLI_BIN_FILE,
|
|
92
|
+
banner: "#!/usr/bin/env node",
|
|
93
|
+
codeSplitting: false,
|
|
94
|
+
sourcemap: false,
|
|
95
|
+
},
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
throw new MextBuildError([
|
|
100
|
+
{ code: "BUNDLE_FAILED", message: `customer CLI: ${error instanceof Error ? error.message : String(error)}` },
|
|
101
|
+
]);
|
|
102
|
+
}
|
|
103
|
+
const emitted = output.map((o) => o.fileName);
|
|
104
|
+
if (emitted.length !== 1 || emitted[0] !== CLI_BIN_FILE) {
|
|
105
|
+
throw new Error(`mext build: customer CLI bundle must be a single ${CLI_BIN_FILE} (got ${emitted.join(", ")})`);
|
|
106
|
+
}
|
|
107
|
+
const binPath = join(dir, CLI_BIN_FILE);
|
|
108
|
+
chmodSync(binPath, 0o755);
|
|
109
|
+
const pkg = {
|
|
110
|
+
name: packageName,
|
|
111
|
+
version,
|
|
112
|
+
description: `Command-line client for ${cli.url}`,
|
|
113
|
+
type: "module",
|
|
114
|
+
bin: { [cli.bin]: `./${CLI_BIN_FILE}` },
|
|
115
|
+
files: [CLI_BIN_FILE],
|
|
116
|
+
engines: { node: CLI_NODE_ENGINE },
|
|
117
|
+
};
|
|
118
|
+
writeFileSync(join(dir, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
|
|
119
|
+
writeFileSync(join(dir, "README.md"), readme(cli, packageName));
|
|
120
|
+
return {
|
|
121
|
+
dir,
|
|
122
|
+
bin: cli.bin,
|
|
123
|
+
packageName,
|
|
124
|
+
version,
|
|
125
|
+
form: bundled ? "bundled" : "universal",
|
|
126
|
+
files: [CLI_BIN_FILE, "README.md", "package.json"],
|
|
127
|
+
size: statSync(binPath).size,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function projectVersion(cwd) {
|
|
131
|
+
try {
|
|
132
|
+
const pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
133
|
+
return typeof pkg.version === "string" && pkg.version !== "" ? pkg.version : undefined;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function readme(cli, packageName) {
|
|
140
|
+
const { bin } = cli;
|
|
141
|
+
const prefix = envPrefix(bin);
|
|
142
|
+
return [
|
|
143
|
+
`# ${packageName}`,
|
|
144
|
+
"",
|
|
145
|
+
`Command-line client for ${cli.url}. Generated by mext build.`,
|
|
146
|
+
"",
|
|
147
|
+
"```sh",
|
|
148
|
+
`npm install -g ${packageName}`,
|
|
149
|
+
`${bin} login # log in (browser, device code, or --with-token)`,
|
|
150
|
+
`${bin} help # commands`,
|
|
151
|
+
`${bin} <namespace> <command> --help`,
|
|
152
|
+
`${bin} mcp install # MCP client registration commands`,
|
|
153
|
+
`${bin} logout`,
|
|
154
|
+
"```",
|
|
155
|
+
"",
|
|
156
|
+
"Environment:",
|
|
157
|
+
"",
|
|
158
|
+
`- \`${prefix}_TOKEN\`: API token (used instead of the stored login)`,
|
|
159
|
+
`- \`${prefix}_URL\`: server URL (default: ${cli.url})`,
|
|
160
|
+
`- \`${prefix}_NO_KEYRING=1\`: store the token in \`~/.config/${bin}/credentials.json\` instead of the OS keychain`,
|
|
161
|
+
`- \`${prefix}_CACHE_DIR\`: server schema cache folder (default: \`\${XDG_CACHE_HOME:-~/.cache}/${bin}\`)`,
|
|
162
|
+
"",
|
|
163
|
+
"Exit codes: 0 success · 1 error · 2 invalid arguments or unknown command · 77 authentication or permission · 130 interrupted.",
|
|
164
|
+
"",
|
|
165
|
+
].join("\n");
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=build-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-cli.js","sourceRoot":"","sources":["../src/build-cli.ts"],"names":[],"mappings":"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","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\"\nimport { isBuiltin } from \"node:module\"\nimport { join, resolve } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport type { RolldownModule, RolldownOutputItem } from \"./build.ts\"\nimport { clientManifest } from \"./client/manifest.ts\"\nimport type { CustomerCliConfig } from \"./client/main.ts\"\nimport { CLI_BUILTIN_COMMANDS, type CliConfig } from \"./config.ts\"\nimport { MextBuildError, type BuildIssue } from \"./errors.ts\"\nimport type { BuildManifest } from \"./manifest.ts\"\nimport type { Registry } from \"./registry.ts\"\nimport { envPrefix } from \"./remote/credentials.ts\"\n\n/**\n * 고객 CLI 패키지 (.mext/cli/, §5). mext build 가 config.cli 가 있을 때 호출.\n * bin.js = client/main.ts (고객 CLI 런타임) + 인라인 설정 (CustomerCliConfig) 의 rolldown 단일 번들. 외부 = Node 내장만\n * (의존성 전부 번들 → package.json dependencies 없음). 런타임은 Zod · MCP 서버 SDK · 핸들러 · 스캔 · 빌드 코드를 import 하지 않는다\n */\n\n/** .mext/ 아래 고객 CLI 폴더 */\nexport const CLI_DIR = \"cli\"\nexport const CLI_BIN_FILE = \"bin.js\"\n/** 고객 CLI package.json engines.node (번들 target node22, 타입 제거 불필요) */\nexport const CLI_NODE_ENGINE = \">=22\"\n\n/** 고객 CLI 런타임 엔트리 (src = .ts, dist = .js: 이 파일과 같은 확장자) */\nconst RUNTIME_ENTRY = fileURLToPath(new URL(`./client/main${import.meta.url.slice(import.meta.url.lastIndexOf(\".\"))}`, import.meta.url))\nconst ENTRY_ID = \"\\0mext-customer-cli\"\n\nexport interface CustomerCliBuild {\n /** 절대 경로 (.mext/cli) */\n dir: string\n bin: string\n packageName: string\n version: string\n form: \"bundled\" | \"universal\"\n /** .mext/cli 기준 생성 파일 (정렬) */\n files: string[]\n /** bin.js 바이트 */\n size: number\n}\n\n/** cli 설정이 있을 때 루트 세그먼트가 고객 CLI 내장 명령 (login · logout · mcp) 과 겹치면 RESERVED_ID */\nexport function customerCliIssues(cli: CliConfig | undefined, registry: Registry): BuildIssue[] {\n if (!cli) return []\n // 루트 별칭은 Registry 가 금지하므로 루트 명령어 · 네임스페이스만 본다\n const root = registry.namespaces.get(\"\")\n const ids = new Set([...(root?.commands ?? []), ...(root?.namespaces ?? [])])\n return [...ids]\n .filter((id) => CLI_BUILTIN_COMMANDS.includes(id))\n .sort()\n .map((id) => {\n const file = registry.commands.get(id)?.file\n return {\n code: \"RESERVED_ID\",\n message: `\"${id}\" is a built-in command of the customer CLI (${cli.bin} ${id}). Rename it or remove cli from mext.config.ts`,\n id,\n ...(file !== undefined ? { file } : {}),\n }\n })\n}\n\nexport interface BuildCustomerCliOptions {\n rolldown: RolldownModule\n /** 도입 프로젝트 루트 (package.json version) */\n cwd: string\n /** .mext/ 절대 경로 */\n outDir: string\n cli: CliConfig\n manifest: BuildManifest\n}\n\nexport async function buildCustomerCli(options: BuildCustomerCliOptions): Promise<CustomerCliBuild> {\n const { cli, manifest } = options\n const dir = join(options.outDir, CLI_DIR)\n mkdirSync(dir, { recursive: true })\n const version = cli.version ?? projectVersion(options.cwd) ?? \"0.0.0\"\n const packageName = cli.packageName ?? cli.bin\n const bundled = cli.manifest !== false\n const config: CustomerCliConfig = {\n bin: cli.bin,\n url: cli.url,\n version,\n packageName,\n mextVersion: manifest.mextVersion,\n manifestHash: manifest.manifestHash,\n ...(bundled ? { manifest: clientManifest(manifest) } : {}),\n }\n const errorsFile = cli.errors === undefined ? undefined : resolve(options.cwd, cli.errors)\n if (errorsFile !== undefined && !existsSync(errorsFile)) {\n throw new MextBuildError([{ code: \"INVALID_CONFIG\", message: `cli.errors file not found: ${cli.errors}` }])\n }\n const entry = [\n `import { main } from ${JSON.stringify(RUNTIME_ENTRY)}`,\n ...(errorsFile !== undefined ? [`import formatError from ${JSON.stringify(errorsFile)}`] : []),\n \"\",\n errorsFile !== undefined\n ? `await main(${JSON.stringify(config)}, { formatError })`\n : `await main(${JSON.stringify(config)})`,\n \"\",\n ].join(\"\\n\")\n\n let output: readonly RolldownOutputItem[]\n try {\n ;({ output } = await options.rolldown.build({\n cwd: options.cwd,\n input: { bin: ENTRY_ID },\n platform: \"node\",\n external: (id: string): boolean => id.startsWith(\"node:\") || isBuiltin(id),\n plugins: [\n {\n name: \"mext-customer-cli\",\n resolveId: (id: string) => (id === ENTRY_ID ? ENTRY_ID : null),\n load: (id: string) => (id === ENTRY_ID ? entry : null),\n },\n ],\n transform: { target: \"node22\" },\n logLevel: \"warn\",\n output: {\n dir,\n format: \"esm\",\n entryFileNames: CLI_BIN_FILE,\n banner: \"#!/usr/bin/env node\",\n codeSplitting: false,\n sourcemap: false,\n },\n }))\n } catch (error) {\n throw new MextBuildError([\n { code: \"BUNDLE_FAILED\", message: `customer CLI: ${error instanceof Error ? error.message : String(error)}` },\n ])\n }\n const emitted = output.map((o) => o.fileName)\n if (emitted.length !== 1 || emitted[0] !== CLI_BIN_FILE) {\n throw new Error(`mext build: customer CLI bundle must be a single ${CLI_BIN_FILE} (got ${emitted.join(\", \")})`)\n }\n const binPath = join(dir, CLI_BIN_FILE)\n chmodSync(binPath, 0o755)\n\n const pkg = {\n name: packageName,\n version,\n description: `Command-line client for ${cli.url}`,\n type: \"module\",\n bin: { [cli.bin]: `./${CLI_BIN_FILE}` },\n files: [CLI_BIN_FILE],\n engines: { node: CLI_NODE_ENGINE },\n }\n writeFileSync(join(dir, \"package.json\"), `${JSON.stringify(pkg, null, 2)}\\n`)\n writeFileSync(join(dir, \"README.md\"), readme(cli, packageName))\n\n return {\n dir,\n bin: cli.bin,\n packageName,\n version,\n form: bundled ? \"bundled\" : \"universal\",\n files: [CLI_BIN_FILE, \"README.md\", \"package.json\"],\n size: statSync(binPath).size,\n }\n}\n\nfunction projectVersion(cwd: string): string | undefined {\n try {\n const pkg = JSON.parse(readFileSync(join(cwd, \"package.json\"), \"utf8\")) as { version?: unknown }\n return typeof pkg.version === \"string\" && pkg.version !== \"\" ? pkg.version : undefined\n } catch {\n return undefined\n }\n}\n\nfunction readme(cli: CliConfig, packageName: string): string {\n const { bin } = cli\n const prefix = envPrefix(bin)\n return [\n `# ${packageName}`,\n \"\",\n `Command-line client for ${cli.url}. Generated by mext build.`,\n \"\",\n \"```sh\",\n `npm install -g ${packageName}`,\n `${bin} login # log in (browser, device code, or --with-token)`,\n `${bin} help # commands`,\n `${bin} <namespace> <command> --help`,\n `${bin} mcp install # MCP client registration commands`,\n `${bin} logout`,\n \"```\",\n \"\",\n \"Environment:\",\n \"\",\n `- \\`${prefix}_TOKEN\\`: API token (used instead of the stored login)`,\n `- \\`${prefix}_URL\\`: server URL (default: ${cli.url})`,\n `- \\`${prefix}_NO_KEYRING=1\\`: store the token in \\`~/.config/${bin}/credentials.json\\` instead of the OS keychain`,\n `- \\`${prefix}_CACHE_DIR\\`: server schema cache folder (default: \\`\\${XDG_CACHE_HOME:-~/.cache}/${bin}\\`)`,\n \"\",\n \"Exit codes: 0 success · 1 error · 2 invalid arguments or unknown command · 77 authentication or permission · 130 interrupted.\",\n \"\",\n ].join(\"\\n\")\n}\n"]}
|
package/dist/build.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type CustomerCliBuild } from "./build-cli.ts";
|
|
2
|
+
import { type BuildManifest } from "./manifest.ts";
|
|
3
|
+
/** mext build 산출 폴더 (도입 프로젝트 루트 기준) */
|
|
4
|
+
export declare const BUILD_DIR = ".mext";
|
|
5
|
+
export declare const BUILD_MANIFEST_FILE = "manifest.json";
|
|
6
|
+
export declare const INSTRUMENTATION_FILE = "instrumentation.ts";
|
|
7
|
+
/** 생성 파일 (server.js · handler.js) 이 import 하는 런타임 엔트리 */
|
|
8
|
+
export declare const START_ENTRY = "@obov/mext/start";
|
|
9
|
+
/** rolldown 모듈 중 mext build 가 쓰는 부분 */
|
|
10
|
+
export interface RolldownModule {
|
|
11
|
+
build(options: Record<string, unknown>): Promise<{
|
|
12
|
+
output: readonly RolldownOutputItem[];
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export interface RolldownOutputItem {
|
|
16
|
+
type: "chunk" | "asset";
|
|
17
|
+
fileName: string;
|
|
18
|
+
/** chunk 코드 */
|
|
19
|
+
code?: string;
|
|
20
|
+
isEntry?: boolean;
|
|
21
|
+
name?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface BuildOptions {
|
|
24
|
+
/** 도입 프로젝트 루트 (mext.config.ts · package.json · instrumentation.ts 위치). 기본 process.cwd() */
|
|
25
|
+
cwd?: string;
|
|
26
|
+
/** 산출 폴더. 기본 <cwd>/.mext. 기존 내용은 dev/ 외 삭제 */
|
|
27
|
+
outDir?: string;
|
|
28
|
+
/** rolldown 로더 (테스트 주입). 기본 import("rolldown") (선택 peer dependency) */
|
|
29
|
+
importRolldown?: () => Promise<RolldownModule>;
|
|
30
|
+
}
|
|
31
|
+
export interface BuildWarning {
|
|
32
|
+
/** UNDECLARED_DEPENDENCY = 외부로 남긴 패키지가 package.json dependencies · peerDependencies · optionalDependencies 에 없음 */
|
|
33
|
+
code: "UNDECLARED_DEPENDENCY" | "BUNDLER_WARNING";
|
|
34
|
+
message: string;
|
|
35
|
+
}
|
|
36
|
+
export interface BuildResult {
|
|
37
|
+
/** 산출 폴더 절대 경로 */
|
|
38
|
+
outDir: string;
|
|
39
|
+
manifest: BuildManifest;
|
|
40
|
+
/** outDir 기준 생성 파일 (posix, 정렬) */
|
|
41
|
+
files: string[];
|
|
42
|
+
warnings: BuildWarning[];
|
|
43
|
+
/** 고객 CLI (.mext/cli/). config.cli 가 있을 때 */
|
|
44
|
+
cli?: CustomerCliBuild;
|
|
45
|
+
}
|
|
46
|
+
/** rolldown 을 찾을 수 없음. message 에 설치 명령 */
|
|
47
|
+
export declare class RolldownUnavailableError extends Error {
|
|
48
|
+
readonly installCommand: string;
|
|
49
|
+
constructor(installCommand: string, cause?: unknown);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* mext build: commands/ → .mext/ (§10).
|
|
53
|
+
* 1. rolldown 확인 (없으면 RolldownUnavailableError)
|
|
54
|
+
* 2. 검증: 파일 규칙 · import · Registry (ID · 별칭 · 스키마) · CLI 변환 issue 를 모두 모아 MextBuildError 한 번
|
|
55
|
+
* 3. rolldown 다중 엔트리 번들: commands/**\/*.js (명령어별) · config.js · instrumentation.js · chunks/. 패키지 import 는 외부
|
|
56
|
+
* 4. server.js · handler.js (얇은 생성 파일, 런타임 = @obov/mext/start) · package.json (type module) · manifest.json (마지막)
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildProject(options?: BuildOptions): Promise<BuildResult>;
|
|
59
|
+
/**
|
|
60
|
+
* .mext/server.js. 계약: startFromBuild(.mext 폴더 URL, { argv, env }) → exit code.
|
|
61
|
+
* argv = server.js 뒤 인자 (--stdio), env = PORT · HOST
|
|
62
|
+
*/
|
|
63
|
+
export declare const SERVER_JS = "// Generated by mext build. Do not edit.\n// node .mext/server.js [--stdio] (= mext start). Environment: PORT \u00B7 HOST\nimport { startFromBuild } from \"@obov/mext/start\"\n\nprocess.exitCode = await startFromBuild(new URL(\".\", import.meta.url), { argv: process.argv.slice(2), env: process.env })\n";
|
|
64
|
+
/** .mext/handler.js. 계약: createFetchHandler(.mext 폴더 URL) → { fetch(request: Request): Promise<Response> } (동기 반환) */
|
|
65
|
+
export declare const HANDLER_JS = "// Generated by mext build. Do not edit.\n// Web fetch handler (Request \u2192 Response) for other runtimes and platforms\nimport { createFetchHandler } from \"@obov/mext/start\"\n\nexport default createFetchHandler(new URL(\".\", import.meta.url))\n";
|
package/dist/build.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { isBuiltin } from "node:module";
|
|
12
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
13
|
+
import { buildCustomerCli, CLI_DIR, customerCliIssues } from "./build-cli.js";
|
|
14
|
+
import { cliBuildIssues } from "./cli/args.js";
|
|
15
|
+
import { CONFIG_FILE, loadConfig, resolveCommandsDir } from "./config.js";
|
|
16
|
+
import { MextBuildError } from "./errors.js";
|
|
17
|
+
import { buildManifest, manifestHash, MANIFEST_SCHEMA_VERSION } from "./manifest.js";
|
|
18
|
+
import { collectRegistry } from "./registry.js";
|
|
19
|
+
import { collectCommands } from "./scan.js";
|
|
20
|
+
import { mextVersion } from "./version.js";
|
|
21
|
+
/** mext build 산출 폴더 (도입 프로젝트 루트 기준) */
|
|
22
|
+
export const BUILD_DIR = ".mext";
|
|
23
|
+
export const BUILD_MANIFEST_FILE = "manifest.json";
|
|
24
|
+
export const INSTRUMENTATION_FILE = "instrumentation.ts";
|
|
25
|
+
/** 생성 파일 (server.js · handler.js) 이 import 하는 런타임 엔트리 */
|
|
26
|
+
export const START_ENTRY = "@obov/mext/start";
|
|
27
|
+
/** 빌드가 지우지 않는 .mext/ 하위 (mext dev 산출 · 잠금) */
|
|
28
|
+
const PRESERVED_ENTRIES = new Set(["dev"]);
|
|
29
|
+
/** rolldown 을 찾을 수 없음. message 에 설치 명령 */
|
|
30
|
+
export class RolldownUnavailableError extends Error {
|
|
31
|
+
installCommand;
|
|
32
|
+
constructor(installCommand, cause) {
|
|
33
|
+
super(`mext build requires rolldown (optional peer dependency of @obov/mext). Install it: ${installCommand}`, { cause });
|
|
34
|
+
this.name = "RolldownUnavailableError";
|
|
35
|
+
this.installCommand = installCommand;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* mext build: commands/ → .mext/ (§10).
|
|
40
|
+
* 1. rolldown 확인 (없으면 RolldownUnavailableError)
|
|
41
|
+
* 2. 검증: 파일 규칙 · import · Registry (ID · 별칭 · 스키마) · CLI 변환 issue 를 모두 모아 MextBuildError 한 번
|
|
42
|
+
* 3. rolldown 다중 엔트리 번들: commands/**\/*.js (명령어별) · config.js · instrumentation.js · chunks/. 패키지 import 는 외부
|
|
43
|
+
* 4. server.js · handler.js (얇은 생성 파일, 런타임 = @obov/mext/start) · package.json (type module) · manifest.json (마지막)
|
|
44
|
+
*/
|
|
45
|
+
export async function buildProject(options = {}) {
|
|
46
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
47
|
+
const outDir = resolve(cwd, options.outDir ?? BUILD_DIR);
|
|
48
|
+
// 빌드 전 outDir 을 비우므로 프로젝트 폴더 자체 · 상위는 거부
|
|
49
|
+
const up = relative(outDir, cwd);
|
|
50
|
+
if (up === "" || (!up.startsWith("..") && !isAbsolute(up))) {
|
|
51
|
+
throw new Error(`mext build: outDir must not be the project directory or its parent (${outDir})`);
|
|
52
|
+
}
|
|
53
|
+
const rolldown = await (options.importRolldown ?? importRolldown)().catch((error) => {
|
|
54
|
+
if (isModuleNotFound(error, "rolldown"))
|
|
55
|
+
throw new RolldownUnavailableError(installCommand(cwd), error);
|
|
56
|
+
throw error;
|
|
57
|
+
});
|
|
58
|
+
const config = await loadConfig(cwd);
|
|
59
|
+
const commandsDir = resolveCommandsDir(cwd, config);
|
|
60
|
+
const scanned = await collectCommands(commandsDir);
|
|
61
|
+
const { registry, issues: registryIssues } = collectRegistry(scanned);
|
|
62
|
+
const issues = [...scanned.issues, ...registryIssues, ...cliBuildIssues(registry), ...customerCliIssues(config.cli, registry)];
|
|
63
|
+
if (issues.length > 0)
|
|
64
|
+
throw new MextBuildError(issues);
|
|
65
|
+
// 엔트리 이름 = 출력 경로 (확장자 제외). 명령어 = commands/ 아래 소스 경로 그대로
|
|
66
|
+
const input = {};
|
|
67
|
+
const moduleOf = new Map();
|
|
68
|
+
for (const entry of registry.commands.values()) {
|
|
69
|
+
const file = entry.file;
|
|
70
|
+
const name = `commands/${file.replace(/\.ts$/, "")}`;
|
|
71
|
+
input[name] = join(commandsDir, file);
|
|
72
|
+
moduleOf.set(entry.id, `${name}.js`);
|
|
73
|
+
}
|
|
74
|
+
const hasConfig = existsSync(join(cwd, CONFIG_FILE));
|
|
75
|
+
if (hasConfig)
|
|
76
|
+
input.config = join(cwd, CONFIG_FILE);
|
|
77
|
+
const hasInstrumentation = existsSync(join(cwd, INSTRUMENTATION_FILE));
|
|
78
|
+
if (hasInstrumentation)
|
|
79
|
+
input.instrumentation = join(cwd, INSTRUMENTATION_FILE);
|
|
80
|
+
cleanOutDir(outDir);
|
|
81
|
+
const warnings = [];
|
|
82
|
+
/** 외부 패키지 이름 → 처음 import 한 파일 */
|
|
83
|
+
const externals = new Map();
|
|
84
|
+
let output;
|
|
85
|
+
try {
|
|
86
|
+
;
|
|
87
|
+
({ output } = await rolldown.build({
|
|
88
|
+
cwd,
|
|
89
|
+
input,
|
|
90
|
+
platform: "node",
|
|
91
|
+
// 상대 · 절대 경로 · # (package.json imports) = 번들. 그 외 bare import = 외부 (도입 프로젝트 node_modules 에서 해석)
|
|
92
|
+
external: (id, importer) => {
|
|
93
|
+
if (id.startsWith(".") || id.startsWith("#") || id.startsWith("\0") || isAbsolute(id))
|
|
94
|
+
return false;
|
|
95
|
+
if (id.startsWith("node:") || isBuiltin(id))
|
|
96
|
+
return true;
|
|
97
|
+
const pkg = packageName(id);
|
|
98
|
+
if (!externals.has(pkg) && importer !== undefined && !importer.startsWith("\0"))
|
|
99
|
+
externals.set(pkg, importer);
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
transform: { target: "node22" },
|
|
103
|
+
logLevel: "warn",
|
|
104
|
+
onLog: (_level, log) => warnings.push({ code: "BUNDLER_WARNING", message: log.message }),
|
|
105
|
+
output: {
|
|
106
|
+
dir: outDir,
|
|
107
|
+
format: "esm",
|
|
108
|
+
entryFileNames: "[name].js",
|
|
109
|
+
chunkFileNames: "chunks/[name]-[hash].js",
|
|
110
|
+
sourcemap: true,
|
|
111
|
+
},
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw new MextBuildError([{ code: "BUNDLE_FAILED", message: error instanceof Error ? error.message : String(error) }]);
|
|
116
|
+
}
|
|
117
|
+
const declared = declaredDependencies(cwd);
|
|
118
|
+
for (const [pkg, importer] of externals) {
|
|
119
|
+
if (declared.has(pkg))
|
|
120
|
+
continue;
|
|
121
|
+
const from = importer === undefined ? "" : ` (imported by ${toPosix(relative(cwd, importer))})`;
|
|
122
|
+
warnings.push({
|
|
123
|
+
code: "UNDECLARED_DEPENDENCY",
|
|
124
|
+
message: `"${pkg}" is not in package.json dependencies${from}. It stays external and must be installed where .mext/ runs`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
const emitted = new Set(output.map((o) => o.fileName));
|
|
128
|
+
for (const name of Object.keys(input)) {
|
|
129
|
+
if (!emitted.has(`${name}.js`))
|
|
130
|
+
throw new Error(`mext build: rolldown did not emit ${name}.js`);
|
|
131
|
+
}
|
|
132
|
+
const base = buildManifest(registry);
|
|
133
|
+
const hash = manifestHash(base.commands);
|
|
134
|
+
const version = mextVersion();
|
|
135
|
+
const manifest = {
|
|
136
|
+
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
137
|
+
mextVersion: version,
|
|
138
|
+
buildId: buildIdOf(version, hash, output),
|
|
139
|
+
manifestHash: hash,
|
|
140
|
+
...(hasConfig ? { config: "config.js" } : {}),
|
|
141
|
+
...(hasInstrumentation ? { instrumentation: "instrumentation.js" } : {}),
|
|
142
|
+
...(base.description !== undefined ? { description: base.description } : {}),
|
|
143
|
+
namespaces: base.namespaces,
|
|
144
|
+
commands: base.commands.map((c) => ({ ...c, module: moduleOf.get(c.id) })),
|
|
145
|
+
};
|
|
146
|
+
const generated = {
|
|
147
|
+
"package.json": `${JSON.stringify({ type: "module" }, null, 2)}\n`,
|
|
148
|
+
"server.js": SERVER_JS,
|
|
149
|
+
"handler.js": HANDLER_JS,
|
|
150
|
+
};
|
|
151
|
+
for (const [file, content] of Object.entries(generated))
|
|
152
|
+
writeFileSync(join(outDir, file), content);
|
|
153
|
+
// 고객 CLI (.mext/cli/, §5): manifest · buildId 확정 후, manifest.json 쓰기 전
|
|
154
|
+
const cli = config.cli ? await buildCustomerCli({ rolldown, cwd, outDir, cli: config.cli, manifest }) : undefined;
|
|
155
|
+
// manifest.json 은 마지막: 중간 실패한 빌드는 manifest 가 없어 mext start 가 "mext build 먼저" 로 거부
|
|
156
|
+
writeFileSync(join(outDir, BUILD_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
157
|
+
const files = [
|
|
158
|
+
...emitted,
|
|
159
|
+
...Object.keys(generated),
|
|
160
|
+
BUILD_MANIFEST_FILE,
|
|
161
|
+
...(cli?.files.map((f) => `${CLI_DIR}/${f}`) ?? []),
|
|
162
|
+
].sort();
|
|
163
|
+
return { outDir, manifest, files, warnings, ...(cli ? { cli } : {}) };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* .mext/server.js. 계약: startFromBuild(.mext 폴더 URL, { argv, env }) → exit code.
|
|
167
|
+
* argv = server.js 뒤 인자 (--stdio), env = PORT · HOST
|
|
168
|
+
*/
|
|
169
|
+
export const SERVER_JS = `// Generated by mext build. Do not edit.
|
|
170
|
+
// node .mext/server.js [--stdio] (= mext start). Environment: PORT · HOST
|
|
171
|
+
import { startFromBuild } from "${START_ENTRY}"
|
|
172
|
+
|
|
173
|
+
process.exitCode = await startFromBuild(new URL(".", import.meta.url), { argv: process.argv.slice(2), env: process.env })
|
|
174
|
+
`;
|
|
175
|
+
/** .mext/handler.js. 계약: createFetchHandler(.mext 폴더 URL) → { fetch(request: Request): Promise<Response> } (동기 반환) */
|
|
176
|
+
export const HANDLER_JS = `// Generated by mext build. Do not edit.
|
|
177
|
+
// Web fetch handler (Request → Response) for other runtimes and platforms
|
|
178
|
+
import { createFetchHandler } from "${START_ENTRY}"
|
|
179
|
+
|
|
180
|
+
export default createFetchHandler(new URL(".", import.meta.url))
|
|
181
|
+
`;
|
|
182
|
+
async function importRolldown() {
|
|
183
|
+
// 문자열 변수: 타입 검사 · 번들러가 선택 peer 를 정적 의존으로 취급하지 않게
|
|
184
|
+
const specifier = "rolldown";
|
|
185
|
+
return (await import(__rewriteRelativeImportExtension(specifier)));
|
|
186
|
+
}
|
|
187
|
+
function isModuleNotFound(error, pkg) {
|
|
188
|
+
const e = error;
|
|
189
|
+
if (typeof e !== "object" || e === null)
|
|
190
|
+
return false;
|
|
191
|
+
if (e.code !== "ERR_MODULE_NOT_FOUND" && e.code !== "MODULE_NOT_FOUND")
|
|
192
|
+
return false;
|
|
193
|
+
return typeof e.message !== "string" || e.message.includes(`'${pkg}'`) || e.message.includes(`"${pkg}"`);
|
|
194
|
+
}
|
|
195
|
+
/** 도입 프로젝트의 lock 파일로 패키지 관리자 추정 */
|
|
196
|
+
function installCommand(cwd) {
|
|
197
|
+
for (let dir = cwd;; dir = resolve(dir, "..")) {
|
|
198
|
+
if (existsSync(join(dir, "pnpm-lock.yaml")))
|
|
199
|
+
return "pnpm add -D rolldown";
|
|
200
|
+
if (existsSync(join(dir, "yarn.lock")))
|
|
201
|
+
return "yarn add -D rolldown";
|
|
202
|
+
if (existsSync(join(dir, "bun.lock")) || existsSync(join(dir, "bun.lockb")))
|
|
203
|
+
return "bun add -d rolldown";
|
|
204
|
+
if (existsSync(join(dir, "package-lock.json")) || resolve(dir, "..") === dir)
|
|
205
|
+
return "npm install --save-dev rolldown";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function cleanOutDir(outDir) {
|
|
209
|
+
mkdirSync(outDir, { recursive: true });
|
|
210
|
+
for (const name of readdirSync(outDir)) {
|
|
211
|
+
if (!PRESERVED_ENTRIES.has(name))
|
|
212
|
+
rmSync(join(outDir, name), { recursive: true, force: true });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** "@scope/pkg/sub" → "@scope/pkg" · "pkg/sub" → "pkg" */
|
|
216
|
+
function packageName(specifier) {
|
|
217
|
+
const parts = specifier.split("/");
|
|
218
|
+
return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
219
|
+
}
|
|
220
|
+
function declaredDependencies(cwd) {
|
|
221
|
+
let pkg;
|
|
222
|
+
try {
|
|
223
|
+
pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf8"));
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return new Set();
|
|
227
|
+
}
|
|
228
|
+
const names = new Set();
|
|
229
|
+
if (typeof pkg.name === "string")
|
|
230
|
+
names.add(pkg.name);
|
|
231
|
+
for (const field of ["dependencies", "peerDependencies", "optionalDependencies"]) {
|
|
232
|
+
const deps = pkg[field];
|
|
233
|
+
if (typeof deps === "object" && deps !== null)
|
|
234
|
+
for (const name of Object.keys(deps))
|
|
235
|
+
names.add(name);
|
|
236
|
+
}
|
|
237
|
+
return names;
|
|
238
|
+
}
|
|
239
|
+
/** D3: mextVersion · manifestHash · 번들 chunk (파일 이름 · 코드) 의 sha256 앞 16자. 같은 입력 = 같은 buildId */
|
|
240
|
+
function buildIdOf(version, hash, output) {
|
|
241
|
+
const h = createHash("sha256").update(`${version}\n${hash}\n`);
|
|
242
|
+
for (const item of [...output].sort((a, b) => (a.fileName < b.fileName ? -1 : 1))) {
|
|
243
|
+
if (item.type !== "chunk")
|
|
244
|
+
continue;
|
|
245
|
+
h.update(`${item.fileName}\0${item.code ?? ""}\0`);
|
|
246
|
+
}
|
|
247
|
+
return h.digest("hex").slice(0, 16);
|
|
248
|
+
}
|
|
249
|
+
function toPosix(path) {
|
|
250
|
+
return path.split(sep).join("/");
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=build.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from \"node:fs\"\nimport { isBuiltin } from \"node:module\"\nimport { isAbsolute, join, relative, resolve, sep } from \"node:path\"\nimport { buildCustomerCli, CLI_DIR, customerCliIssues, type CustomerCliBuild } from \"./build-cli.ts\"\nimport { cliBuildIssues } from \"./cli/args.ts\"\nimport { CONFIG_FILE, loadConfig, resolveCommandsDir } from \"./config.ts\"\nimport { MextBuildError } from \"./errors.ts\"\nimport { buildManifest, manifestHash, MANIFEST_SCHEMA_VERSION, type BuildManifest } from \"./manifest.ts\"\nimport { collectRegistry } from \"./registry.ts\"\nimport { collectCommands } from \"./scan.ts\"\nimport { mextVersion } from \"./version.ts\"\n\n/** mext build 산출 폴더 (도입 프로젝트 루트 기준) */\nexport const BUILD_DIR = \".mext\"\nexport const BUILD_MANIFEST_FILE = \"manifest.json\"\nexport const INSTRUMENTATION_FILE = \"instrumentation.ts\"\n/** 생성 파일 (server.js · handler.js) 이 import 하는 런타임 엔트리 */\nexport const START_ENTRY = \"@obov/mext/start\"\n/** 빌드가 지우지 않는 .mext/ 하위 (mext dev 산출 · 잠금) */\nconst PRESERVED_ENTRIES = new Set([\"dev\"])\n\n/** rolldown 모듈 중 mext build 가 쓰는 부분 */\nexport interface RolldownModule {\n build(options: Record<string, unknown>): Promise<{ output: readonly RolldownOutputItem[] }>\n}\n\nexport interface RolldownOutputItem {\n type: \"chunk\" | \"asset\"\n fileName: string\n /** chunk 코드 */\n code?: string\n isEntry?: boolean\n name?: string\n}\n\nexport interface BuildOptions {\n /** 도입 프로젝트 루트 (mext.config.ts · package.json · instrumentation.ts 위치). 기본 process.cwd() */\n cwd?: string\n /** 산출 폴더. 기본 <cwd>/.mext. 기존 내용은 dev/ 외 삭제 */\n outDir?: string\n /** rolldown 로더 (테스트 주입). 기본 import(\"rolldown\") (선택 peer dependency) */\n importRolldown?: () => Promise<RolldownModule>\n}\n\nexport interface BuildWarning {\n /** UNDECLARED_DEPENDENCY = 외부로 남긴 패키지가 package.json dependencies · peerDependencies · optionalDependencies 에 없음 */\n code: \"UNDECLARED_DEPENDENCY\" | \"BUNDLER_WARNING\"\n message: string\n}\n\nexport interface BuildResult {\n /** 산출 폴더 절대 경로 */\n outDir: string\n manifest: BuildManifest\n /** outDir 기준 생성 파일 (posix, 정렬) */\n files: string[]\n warnings: BuildWarning[]\n /** 고객 CLI (.mext/cli/). config.cli 가 있을 때 */\n cli?: CustomerCliBuild\n}\n\n/** rolldown 을 찾을 수 없음. message 에 설치 명령 */\nexport class RolldownUnavailableError extends Error {\n readonly installCommand: string\n\n constructor(installCommand: string, cause?: unknown) {\n super(`mext build requires rolldown (optional peer dependency of @obov/mext). Install it: ${installCommand}`, { cause })\n this.name = \"RolldownUnavailableError\"\n this.installCommand = installCommand\n }\n}\n\n/**\n * mext build: commands/ → .mext/ (§10).\n * 1. rolldown 확인 (없으면 RolldownUnavailableError)\n * 2. 검증: 파일 규칙 · import · Registry (ID · 별칭 · 스키마) · CLI 변환 issue 를 모두 모아 MextBuildError 한 번\n * 3. rolldown 다중 엔트리 번들: commands/**\\/*.js (명령어별) · config.js · instrumentation.js · chunks/. 패키지 import 는 외부\n * 4. server.js · handler.js (얇은 생성 파일, 런타임 = @obov/mext/start) · package.json (type module) · manifest.json (마지막)\n */\nexport async function buildProject(options: BuildOptions = {}): Promise<BuildResult> {\n const cwd = resolve(options.cwd ?? process.cwd())\n const outDir = resolve(cwd, options.outDir ?? BUILD_DIR)\n // 빌드 전 outDir 을 비우므로 프로젝트 폴더 자체 · 상위는 거부\n const up = relative(outDir, cwd)\n if (up === \"\" || (!up.startsWith(\"..\") && !isAbsolute(up))) {\n throw new Error(`mext build: outDir must not be the project directory or its parent (${outDir})`)\n }\n const rolldown = await (options.importRolldown ?? importRolldown)().catch((error: unknown) => {\n if (isModuleNotFound(error, \"rolldown\")) throw new RolldownUnavailableError(installCommand(cwd), error)\n throw error\n })\n\n const config = await loadConfig(cwd)\n const commandsDir = resolveCommandsDir(cwd, config)\n const scanned = await collectCommands(commandsDir)\n const { registry, issues: registryIssues } = collectRegistry(scanned)\n const issues = [...scanned.issues, ...registryIssues, ...cliBuildIssues(registry), ...customerCliIssues(config.cli, registry)]\n if (issues.length > 0) throw new MextBuildError(issues)\n\n // 엔트리 이름 = 출력 경로 (확장자 제외). 명령어 = commands/ 아래 소스 경로 그대로\n const input: Record<string, string> = {}\n const moduleOf = new Map<string, string>()\n for (const entry of registry.commands.values()) {\n const file = entry.file!\n const name = `commands/${file.replace(/\\.ts$/, \"\")}`\n input[name] = join(commandsDir, file)\n moduleOf.set(entry.id, `${name}.js`)\n }\n const hasConfig = existsSync(join(cwd, CONFIG_FILE))\n if (hasConfig) input.config = join(cwd, CONFIG_FILE)\n const hasInstrumentation = existsSync(join(cwd, INSTRUMENTATION_FILE))\n if (hasInstrumentation) input.instrumentation = join(cwd, INSTRUMENTATION_FILE)\n\n cleanOutDir(outDir)\n\n const warnings: BuildWarning[] = []\n /** 외부 패키지 이름 → 처음 import 한 파일 */\n const externals = new Map<string, string | undefined>()\n let output: readonly RolldownOutputItem[]\n try {\n ;({ output } = await rolldown.build({\n cwd,\n input,\n platform: \"node\",\n // 상대 · 절대 경로 · # (package.json imports) = 번들. 그 외 bare import = 외부 (도입 프로젝트 node_modules 에서 해석)\n external: (id: string, importer: string | undefined): boolean => {\n if (id.startsWith(\".\") || id.startsWith(\"#\") || id.startsWith(\"\\0\") || isAbsolute(id)) return false\n if (id.startsWith(\"node:\") || isBuiltin(id)) return true\n const pkg = packageName(id)\n if (!externals.has(pkg) && importer !== undefined && !importer.startsWith(\"\\0\")) externals.set(pkg, importer)\n return true\n },\n transform: { target: \"node22\" },\n logLevel: \"warn\",\n onLog: (_level: string, log: { message: string }) => warnings.push({ code: \"BUNDLER_WARNING\", message: log.message }),\n output: {\n dir: outDir,\n format: \"esm\",\n entryFileNames: \"[name].js\",\n chunkFileNames: \"chunks/[name]-[hash].js\",\n sourcemap: true,\n },\n }))\n } catch (error) {\n throw new MextBuildError([{ code: \"BUNDLE_FAILED\", message: error instanceof Error ? error.message : String(error) }])\n }\n\n const declared = declaredDependencies(cwd)\n for (const [pkg, importer] of externals) {\n if (declared.has(pkg)) continue\n const from = importer === undefined ? \"\" : ` (imported by ${toPosix(relative(cwd, importer))})`\n warnings.push({\n code: \"UNDECLARED_DEPENDENCY\",\n message: `\"${pkg}\" is not in package.json dependencies${from}. It stays external and must be installed where .mext/ runs`,\n })\n }\n\n const emitted = new Set(output.map((o) => o.fileName))\n for (const name of Object.keys(input)) {\n if (!emitted.has(`${name}.js`)) throw new Error(`mext build: rolldown did not emit ${name}.js`)\n }\n\n const base = buildManifest(registry)\n const hash = manifestHash(base.commands)\n const version = mextVersion()\n const manifest: BuildManifest = {\n schemaVersion: MANIFEST_SCHEMA_VERSION,\n mextVersion: version,\n buildId: buildIdOf(version, hash, output),\n manifestHash: hash,\n ...(hasConfig ? { config: \"config.js\" } : {}),\n ...(hasInstrumentation ? { instrumentation: \"instrumentation.js\" } : {}),\n ...(base.description !== undefined ? { description: base.description } : {}),\n namespaces: base.namespaces,\n commands: base.commands.map((c) => ({ ...c, module: moduleOf.get(c.id)! })),\n }\n\n const generated: Record<string, string> = {\n \"package.json\": `${JSON.stringify({ type: \"module\" }, null, 2)}\\n`,\n \"server.js\": SERVER_JS,\n \"handler.js\": HANDLER_JS,\n }\n for (const [file, content] of Object.entries(generated)) writeFileSync(join(outDir, file), content)\n\n // 고객 CLI (.mext/cli/, §5): manifest · buildId 확정 후, manifest.json 쓰기 전\n const cli = config.cli ? await buildCustomerCli({ rolldown, cwd, outDir, cli: config.cli, manifest }) : undefined\n\n // manifest.json 은 마지막: 중간 실패한 빌드는 manifest 가 없어 mext start 가 \"mext build 먼저\" 로 거부\n writeFileSync(join(outDir, BUILD_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\\n`)\n\n const files = [\n ...emitted,\n ...Object.keys(generated),\n BUILD_MANIFEST_FILE,\n ...(cli?.files.map((f) => `${CLI_DIR}/${f}`) ?? []),\n ].sort()\n return { outDir, manifest, files, warnings, ...(cli ? { cli } : {}) }\n}\n\n/**\n * .mext/server.js. 계약: startFromBuild(.mext 폴더 URL, { argv, env }) → exit code.\n * argv = server.js 뒤 인자 (--stdio), env = PORT · HOST\n */\nexport const SERVER_JS = `// Generated by mext build. Do not edit.\n// node .mext/server.js [--stdio] (= mext start). Environment: PORT · HOST\nimport { startFromBuild } from \"${START_ENTRY}\"\n\nprocess.exitCode = await startFromBuild(new URL(\".\", import.meta.url), { argv: process.argv.slice(2), env: process.env })\n`\n\n/** .mext/handler.js. 계약: createFetchHandler(.mext 폴더 URL) → { fetch(request: Request): Promise<Response> } (동기 반환) */\nexport const HANDLER_JS = `// Generated by mext build. Do not edit.\n// Web fetch handler (Request → Response) for other runtimes and platforms\nimport { createFetchHandler } from \"${START_ENTRY}\"\n\nexport default createFetchHandler(new URL(\".\", import.meta.url))\n`\n\nasync function importRolldown(): Promise<RolldownModule> {\n // 문자열 변수: 타입 검사 · 번들러가 선택 peer 를 정적 의존으로 취급하지 않게\n const specifier = \"rolldown\"\n return (await import(specifier)) as RolldownModule\n}\n\nfunction isModuleNotFound(error: unknown, pkg: string): boolean {\n const e = error as { code?: unknown; message?: unknown } | null\n if (typeof e !== \"object\" || e === null) return false\n if (e.code !== \"ERR_MODULE_NOT_FOUND\" && e.code !== \"MODULE_NOT_FOUND\") return false\n return typeof e.message !== \"string\" || e.message.includes(`'${pkg}'`) || e.message.includes(`\"${pkg}\"`)\n}\n\n/** 도입 프로젝트의 lock 파일로 패키지 관리자 추정 */\nfunction installCommand(cwd: string): string {\n for (let dir = cwd; ; dir = resolve(dir, \"..\")) {\n if (existsSync(join(dir, \"pnpm-lock.yaml\"))) return \"pnpm add -D rolldown\"\n if (existsSync(join(dir, \"yarn.lock\"))) return \"yarn add -D rolldown\"\n if (existsSync(join(dir, \"bun.lock\")) || existsSync(join(dir, \"bun.lockb\"))) return \"bun add -d rolldown\"\n if (existsSync(join(dir, \"package-lock.json\")) || resolve(dir, \"..\") === dir) return \"npm install --save-dev rolldown\"\n }\n}\n\nfunction cleanOutDir(outDir: string): void {\n mkdirSync(outDir, { recursive: true })\n for (const name of readdirSync(outDir)) {\n if (!PRESERVED_ENTRIES.has(name)) rmSync(join(outDir, name), { recursive: true, force: true })\n }\n}\n\n/** \"@scope/pkg/sub\" → \"@scope/pkg\" · \"pkg/sub\" → \"pkg\" */\nfunction packageName(specifier: string): string {\n const parts = specifier.split(\"/\")\n return specifier.startsWith(\"@\") ? parts.slice(0, 2).join(\"/\") : parts[0]!\n}\n\nfunction declaredDependencies(cwd: string): Set<string> {\n let pkg: Record<string, unknown>\n try {\n pkg = JSON.parse(readFileSync(join(cwd, \"package.json\"), \"utf8\")) as Record<string, unknown>\n } catch {\n return new Set()\n }\n const names = new Set<string>()\n if (typeof pkg.name === \"string\") names.add(pkg.name)\n for (const field of [\"dependencies\", \"peerDependencies\", \"optionalDependencies\"]) {\n const deps = pkg[field]\n if (typeof deps === \"object\" && deps !== null) for (const name of Object.keys(deps)) names.add(name)\n }\n return names\n}\n\n/** D3: mextVersion · manifestHash · 번들 chunk (파일 이름 · 코드) 의 sha256 앞 16자. 같은 입력 = 같은 buildId */\nfunction buildIdOf(version: string, hash: string, output: readonly RolldownOutputItem[]): string {\n const h = createHash(\"sha256\").update(`${version}\\n${hash}\\n`)\n for (const item of [...output].sort((a, b) => (a.fileName < b.fileName ? -1 : 1))) {\n if (item.type !== \"chunk\") continue\n h.update(`${item.fileName}\\0${item.code ?? \"\"}\\0`)\n }\n return h.digest(\"hex\").slice(0, 16)\n}\n\nfunction toPosix(path: string): string {\n return path.split(sep).join(\"/\")\n}\n"]}
|