@seed-design/cli 1.3.1 → 1.3.3
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/bin/index.mjs +21 -21
- package/package.json +1 -1
- package/src/commands/add-all.ts +32 -3
- package/src/commands/add.ts +32 -3
- package/src/commands/compat.ts +52 -6
- package/src/commands/docs.ts +151 -67
- package/src/commands/init.ts +32 -3
- package/src/index.ts +1 -2
- package/src/tests/analytics.test.ts +95 -0
- package/src/tests/command-telemetry.test.ts +185 -0
- package/src/utils/analytics.ts +65 -1
- package/src/utils/fetch.ts +53 -21
- package/src/commands/upgrade.ts +0 -387
package/src/utils/analytics.ts
CHANGED
|
@@ -3,12 +3,27 @@ import * as p from "@clack/prompts";
|
|
|
3
3
|
import { getRawConfig } from "./get-config";
|
|
4
4
|
|
|
5
5
|
const EVENT_PREFIX = "seed_cli";
|
|
6
|
+
const COMMAND_STATUSES = ["completed", "cancelled", "failed"] as const;
|
|
6
7
|
|
|
7
8
|
interface TrackOptions {
|
|
8
9
|
event: string;
|
|
9
10
|
properties?: Record<string, unknown>;
|
|
10
11
|
}
|
|
11
12
|
|
|
13
|
+
interface TrackCommandOutcomeOptions {
|
|
14
|
+
command: string;
|
|
15
|
+
status: (typeof COMMAND_STATUSES)[number];
|
|
16
|
+
result?: string;
|
|
17
|
+
properties?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface TrackCommandFailureOptions {
|
|
21
|
+
command: string;
|
|
22
|
+
error: unknown;
|
|
23
|
+
result?: string;
|
|
24
|
+
properties?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
12
27
|
/**
|
|
13
28
|
* 텔레메트리 활성화 여부를 확인합니다.
|
|
14
29
|
* 우선순위:
|
|
@@ -48,6 +63,24 @@ const sessionId = generateSessionId();
|
|
|
48
63
|
// 세션당 한 번만 메시지 표시
|
|
49
64
|
let hasShownMessage = false;
|
|
50
65
|
|
|
66
|
+
function omitUndefined(properties: Record<string, unknown>): Record<string, unknown> {
|
|
67
|
+
return Object.fromEntries(
|
|
68
|
+
Object.entries(properties).filter(([, value]) => value !== undefined),
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getSafeErrorType(error: unknown): string {
|
|
73
|
+
if (error instanceof Error && error.name) {
|
|
74
|
+
return error.name;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (typeof error === "object" && error !== null) {
|
|
78
|
+
return error.constructor?.name ?? "Object";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return typeof error;
|
|
82
|
+
}
|
|
83
|
+
|
|
51
84
|
/**
|
|
52
85
|
* PostHog에 이벤트를 전송합니다.
|
|
53
86
|
*/
|
|
@@ -76,7 +109,7 @@ async function track(cwd: string, { event, properties = {} }: TrackOptions): Pro
|
|
|
76
109
|
// PostHog API 호출 (fire-and-forget)
|
|
77
110
|
try {
|
|
78
111
|
if (!process.env.POSTHOG_HOST || !process.env.POSTHOG_API_KEY) {
|
|
79
|
-
console.
|
|
112
|
+
console.error("[Telemetry] POSTHOG_HOST 또는 POSTHOG_API_KEY가 없어서 이벤트를 전송하지 않아요.");
|
|
80
113
|
return;
|
|
81
114
|
}
|
|
82
115
|
|
|
@@ -113,6 +146,37 @@ async function track(cwd: string, { event, properties = {} }: TrackOptions): Pro
|
|
|
113
146
|
}
|
|
114
147
|
}
|
|
115
148
|
|
|
149
|
+
async function trackCommandOutcome(
|
|
150
|
+
cwd: string,
|
|
151
|
+
{ command, status, result, properties = {} }: TrackCommandOutcomeOptions,
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
await track(cwd, {
|
|
154
|
+
event: command,
|
|
155
|
+
properties: omitUndefined({
|
|
156
|
+
status,
|
|
157
|
+
result,
|
|
158
|
+
...properties,
|
|
159
|
+
}),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function trackCommandFailure(
|
|
164
|
+
cwd: string,
|
|
165
|
+
{ command, error, result, properties = {} }: TrackCommandFailureOptions,
|
|
166
|
+
): Promise<void> {
|
|
167
|
+
await trackCommandOutcome(cwd, {
|
|
168
|
+
command,
|
|
169
|
+
status: "failed",
|
|
170
|
+
result,
|
|
171
|
+
properties: omitUndefined({
|
|
172
|
+
error_type: getSafeErrorType(error),
|
|
173
|
+
...properties,
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
116
178
|
export const analytics = {
|
|
117
179
|
track,
|
|
180
|
+
trackCommandFailure,
|
|
181
|
+
trackCommandOutcome,
|
|
118
182
|
};
|
package/src/utils/fetch.ts
CHANGED
|
@@ -121,44 +121,76 @@ async function fetchRegistryItem({
|
|
|
121
121
|
return parsedItem;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
export async function
|
|
125
|
-
const response = await fetchWithTimeout(
|
|
124
|
+
export async function fetchLlmsTxt({ url }: { url: string }): Promise<string> {
|
|
125
|
+
const response = await fetchWithTimeout(url);
|
|
126
126
|
|
|
127
127
|
if (!response.ok) {
|
|
128
128
|
throw new CliError({
|
|
129
|
-
message:
|
|
129
|
+
message: `llms.txt를 가져오지 못했어요: ${response.status} ${response.statusText}`,
|
|
130
|
+
hint: `${url} 에 접근할 수 있는지 확인해주세요.`,
|
|
130
131
|
});
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
if (!data || typeof data !== "object" || typeof data.version !== "string") {
|
|
135
|
-
throw new CliError({
|
|
136
|
-
message: `${packageName} 최신 버전 응답 형식이 올바르지 않아요.`,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
return data.version;
|
|
134
|
+
return response.text();
|
|
140
135
|
}
|
|
141
136
|
|
|
142
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Try fetching llms.txt content with fallback URL patterns.
|
|
139
|
+
* 1. {baseUrl}/llms/{query}.txt
|
|
140
|
+
* 2. {baseUrl}/llms/{query}/llms.txt (for package changelog index)
|
|
141
|
+
*/
|
|
142
|
+
export async function tryFetchLlmsTxt({
|
|
143
143
|
baseUrl,
|
|
144
|
-
|
|
145
|
-
version,
|
|
144
|
+
query,
|
|
146
145
|
}: {
|
|
147
146
|
baseUrl: string;
|
|
148
|
-
|
|
149
|
-
version: string;
|
|
147
|
+
query: string;
|
|
150
148
|
}): Promise<string> {
|
|
151
|
-
const
|
|
152
|
-
|
|
149
|
+
const normalizedQuery = query.startsWith("/") ? query.slice(1) : query;
|
|
150
|
+
|
|
151
|
+
const urls = [
|
|
152
|
+
`${baseUrl}/llms/${normalizedQuery}.txt`,
|
|
153
|
+
`${baseUrl}/llms/${normalizedQuery}/llms.txt`,
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
let lastError: unknown;
|
|
157
|
+
|
|
158
|
+
for (const url of urls) {
|
|
159
|
+
let response: Response;
|
|
160
|
+
try {
|
|
161
|
+
response = await fetchWithTimeout(url);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
lastError = error;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
153
166
|
|
|
154
|
-
|
|
167
|
+
if (response.ok) {
|
|
168
|
+
return response.text();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// 404 → try next URL candidate
|
|
172
|
+
if (response.status === 404) {
|
|
173
|
+
lastError = new CliError({
|
|
174
|
+
message: `llms.txt를 찾을 수 없어요: ${normalizedQuery}`,
|
|
175
|
+
hint: `다음 경로를 시도했어요:\n${urls.map((u) => ` - ${u}`).join("\n")}`,
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Non-404 errors (5xx, 401, etc.) — propagate immediately
|
|
155
181
|
throw new CliError({
|
|
156
|
-
message:
|
|
157
|
-
hint:
|
|
182
|
+
message: `llms.txt 요청이 실패했어요: ${response.status} ${response.statusText}`,
|
|
183
|
+
hint: `URL: ${url}`,
|
|
158
184
|
});
|
|
159
185
|
}
|
|
160
186
|
|
|
161
|
-
|
|
187
|
+
throw (
|
|
188
|
+
lastError ??
|
|
189
|
+
new CliError({
|
|
190
|
+
message: `llms.txt를 찾을 수 없어요: ${normalizedQuery}`,
|
|
191
|
+
hint: `다음 경로를 시도했어요:\n${urls.map((u) => ` - ${u}`).join("\n")}`,
|
|
192
|
+
})
|
|
193
|
+
);
|
|
162
194
|
}
|
|
163
195
|
|
|
164
196
|
export async function fetchRegistryItems({
|
package/src/commands/upgrade.ts
DELETED
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import { getPackageInfo } from "@/src/utils/get-package-info";
|
|
2
|
-
import * as p from "@clack/prompts";
|
|
3
|
-
import { coerce, valid } from "semver";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
|
|
6
|
-
import type { CAC } from "cac";
|
|
7
|
-
import { BASE_URL } from "../constants";
|
|
8
|
-
import { analytics } from "../utils/analytics";
|
|
9
|
-
import { highlight } from "../utils/color";
|
|
10
|
-
import {
|
|
11
|
-
CliCancelError,
|
|
12
|
-
CliError,
|
|
13
|
-
handleCliError,
|
|
14
|
-
isCliCancelError,
|
|
15
|
-
isVerboseMode,
|
|
16
|
-
} from "../utils/error";
|
|
17
|
-
import { fetchChangelog, fetchLatestVersion } from "../utils/fetch";
|
|
18
|
-
|
|
19
|
-
const SEED_SCOPE = "@seed-design/";
|
|
20
|
-
|
|
21
|
-
const upgradeOptionsSchema = z.object({
|
|
22
|
-
packageName: z.string().optional(),
|
|
23
|
-
cwd: z.string(),
|
|
24
|
-
baseUrl: z.string(),
|
|
25
|
-
raw: z.boolean(),
|
|
26
|
-
all: z.boolean(),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
function toFullPackageName(input: string): string {
|
|
30
|
-
return input.startsWith(SEED_SCOPE) ? input : `${SEED_SCOPE}${input}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function toSlug(packageName: string): string {
|
|
34
|
-
return packageName.replace(SEED_SCOPE, "");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function findInstalledSeedPackages(cwd: string): Record<string, string> {
|
|
38
|
-
const packageInfo = getPackageInfo(cwd);
|
|
39
|
-
const allDeps = {
|
|
40
|
-
...packageInfo.dependencies,
|
|
41
|
-
...packageInfo.devDependencies,
|
|
42
|
-
...packageInfo.peerDependencies,
|
|
43
|
-
...packageInfo.optionalDependencies,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const seedPackages: Record<string, string> = {};
|
|
47
|
-
for (const [name, version] of Object.entries(allDeps)) {
|
|
48
|
-
if (name.startsWith(SEED_SCOPE) && version) {
|
|
49
|
-
seedPackages[name] = version;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return seedPackages;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function resolveExactVersion(versionSpec: string): string | null {
|
|
57
|
-
let normalized = versionSpec.trim();
|
|
58
|
-
|
|
59
|
-
if (normalized.startsWith("workspace:")) {
|
|
60
|
-
normalized = normalized.slice("workspace:".length).trim();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (normalized.startsWith("npm:")) {
|
|
64
|
-
const lastAt = normalized.lastIndexOf("@");
|
|
65
|
-
if (lastAt > 4) {
|
|
66
|
-
normalized = normalized.slice(lastAt + 1);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (valid(normalized)) return normalized;
|
|
71
|
-
|
|
72
|
-
const coerced = coerce(normalized);
|
|
73
|
-
if (coerced) return coerced.version;
|
|
74
|
-
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
interface UpgradeOneResult {
|
|
79
|
-
package: string;
|
|
80
|
-
currentVersion: string;
|
|
81
|
-
latestVersion: string;
|
|
82
|
-
upToDate: boolean;
|
|
83
|
-
changelog: string | null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function upgradeOne({
|
|
87
|
-
targetPackage,
|
|
88
|
-
currentVersionSpec,
|
|
89
|
-
baseUrl,
|
|
90
|
-
}: {
|
|
91
|
-
targetPackage: string;
|
|
92
|
-
currentVersionSpec: string;
|
|
93
|
-
baseUrl: string;
|
|
94
|
-
}): Promise<UpgradeOneResult> {
|
|
95
|
-
const currentVersion = resolveExactVersion(currentVersionSpec);
|
|
96
|
-
|
|
97
|
-
if (!currentVersion) {
|
|
98
|
-
throw new CliError({
|
|
99
|
-
message: `${targetPackage}의 버전을 파싱할 수 없어요: ${currentVersionSpec}`,
|
|
100
|
-
hint: "package.json에서 버전 형식을 확인해주세요.",
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const latestVersion = await fetchLatestVersion(targetPackage);
|
|
105
|
-
|
|
106
|
-
if (currentVersion === latestVersion) {
|
|
107
|
-
return {
|
|
108
|
-
package: targetPackage,
|
|
109
|
-
currentVersion,
|
|
110
|
-
latestVersion,
|
|
111
|
-
upToDate: true,
|
|
112
|
-
changelog: null,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const slug = toSlug(targetPackage);
|
|
117
|
-
const changelog = await fetchChangelog({
|
|
118
|
-
baseUrl,
|
|
119
|
-
packageSlug: slug,
|
|
120
|
-
version: currentVersion,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
package: targetPackage,
|
|
125
|
-
currentVersion,
|
|
126
|
-
latestVersion,
|
|
127
|
-
upToDate: false,
|
|
128
|
-
changelog,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function printResultRaw(result: UpgradeOneResult & { error?: string }) {
|
|
133
|
-
if (result.error) {
|
|
134
|
-
console.error(`## ${result.package}\n\nError: ${result.error}\n`);
|
|
135
|
-
} else if (result.upToDate) {
|
|
136
|
-
console.log(`${result.package}@${result.currentVersion} is already up to date.\n`);
|
|
137
|
-
} else {
|
|
138
|
-
console.log(result.changelog);
|
|
139
|
-
console.log("");
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function printResultInteractive(result: UpgradeOneResult & { error?: string }) {
|
|
144
|
-
if (result.error) {
|
|
145
|
-
p.log.error(`${highlight(result.package)}: ${result.error}`);
|
|
146
|
-
} else if (result.upToDate) {
|
|
147
|
-
p.log.info(
|
|
148
|
-
`${highlight(result.package)}: ${highlight(result.currentVersion)} — 이미 최신 버전이에요.`,
|
|
149
|
-
);
|
|
150
|
-
} else {
|
|
151
|
-
p.log.info(
|
|
152
|
-
`${highlight(result.package)}: ${highlight(result.currentVersion)} → ${highlight(result.latestVersion)}`,
|
|
153
|
-
);
|
|
154
|
-
p.log.message(result.changelog ?? "");
|
|
155
|
-
p.log.info(
|
|
156
|
-
`업그레이드하려면: ${highlight(`bun add ${result.package}@${result.latestVersion}`)}`,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async function trackResults(cwd: string, results: UpgradeOneResult[], startTime: number) {
|
|
162
|
-
try {
|
|
163
|
-
for (const result of results) {
|
|
164
|
-
await analytics.track(cwd, {
|
|
165
|
-
event: "upgrade",
|
|
166
|
-
properties: {
|
|
167
|
-
package: result.package,
|
|
168
|
-
current_version: result.currentVersion,
|
|
169
|
-
latest_version: result.latestVersion,
|
|
170
|
-
up_to_date: result.upToDate,
|
|
171
|
-
duration_ms: Date.now() - startTime,
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
} catch {}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export const upgradeCommand = (cli: CAC) => {
|
|
179
|
-
cli
|
|
180
|
-
.command(
|
|
181
|
-
"upgrade [package-name]",
|
|
182
|
-
"패키지의 현재 버전과 최신 버전 사이의 변경사항을 확인합니다",
|
|
183
|
-
)
|
|
184
|
-
.option("-c, --cwd <cwd>", "작업 디렉토리. 기본값은 현재 디렉토리입니다.", {
|
|
185
|
-
default: process.cwd(),
|
|
186
|
-
})
|
|
187
|
-
.option("-u, --baseUrl <baseUrl>", "changelog를 조회할 base URL입니다.", { default: BASE_URL })
|
|
188
|
-
.option("--raw", "UI 없이 순수 마크다운만 출력합니다. LLM 파이프에 유용합니다.", {
|
|
189
|
-
default: false,
|
|
190
|
-
})
|
|
191
|
-
.option("-a, --all", "설치된 모든 @seed-design 패키지의 변경사항을 확인합니다.", {
|
|
192
|
-
default: false,
|
|
193
|
-
})
|
|
194
|
-
.example("seed-design upgrade")
|
|
195
|
-
.example("seed-design upgrade react")
|
|
196
|
-
.example("seed-design upgrade --all")
|
|
197
|
-
.example("seed-design upgrade --all --raw")
|
|
198
|
-
.action(async (packageName, opts) => {
|
|
199
|
-
const startTime = Date.now();
|
|
200
|
-
const verbose = isVerboseMode(opts);
|
|
201
|
-
const parsed = upgradeOptionsSchema.safeParse({ packageName, ...opts });
|
|
202
|
-
if (!parsed.success) {
|
|
203
|
-
if (opts.raw) {
|
|
204
|
-
console.error(parsed.error.message);
|
|
205
|
-
process.exit(1);
|
|
206
|
-
}
|
|
207
|
-
p.intro("seed-design upgrade");
|
|
208
|
-
handleCliError(parsed.error, {
|
|
209
|
-
defaultMessage: "업그레이드 확인에 실패했어요.",
|
|
210
|
-
defaultHint: "`--verbose` 옵션으로 상세 오류를 확인해보세요.",
|
|
211
|
-
verbose,
|
|
212
|
-
});
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const { data: options } = parsed;
|
|
217
|
-
const { raw, all } = options;
|
|
218
|
-
|
|
219
|
-
if (!raw) p.intro("seed-design upgrade");
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
const seedPackages = findInstalledSeedPackages(options.cwd);
|
|
223
|
-
const packageNames = Object.keys(seedPackages);
|
|
224
|
-
|
|
225
|
-
if (packageNames.length === 0) {
|
|
226
|
-
throw new CliError({
|
|
227
|
-
message: "프로젝트에 설치된 @seed-design 패키지를 찾을 수 없어요.",
|
|
228
|
-
hint: "`bun add @seed-design/react`로 패키지를 설치해보세요.",
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (options.packageName && all) {
|
|
233
|
-
throw new CliError({
|
|
234
|
-
message: "패키지명과 --all 옵션을 동시에 사용할 수 없어요.",
|
|
235
|
-
hint: "`seed-design upgrade --all` 또는 `seed-design upgrade react` 중 하나만 사용해주세요.",
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// --all: iterate all packages
|
|
240
|
-
if (all) {
|
|
241
|
-
if (raw) {
|
|
242
|
-
const results = await Promise.all(
|
|
243
|
-
packageNames.map((name) =>
|
|
244
|
-
upgradeOne({
|
|
245
|
-
targetPackage: name,
|
|
246
|
-
currentVersionSpec: seedPackages[name],
|
|
247
|
-
baseUrl: options.baseUrl,
|
|
248
|
-
}).catch((error): UpgradeOneResult & { error: string } => ({
|
|
249
|
-
package: name,
|
|
250
|
-
currentVersion: seedPackages[name],
|
|
251
|
-
latestVersion: "unknown",
|
|
252
|
-
upToDate: false,
|
|
253
|
-
changelog: null,
|
|
254
|
-
error: error instanceof Error ? error.message : String(error),
|
|
255
|
-
})),
|
|
256
|
-
),
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
for (const result of results) {
|
|
260
|
-
printResultRaw(result);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
await trackResults(options.cwd, results, startTime);
|
|
264
|
-
const hasErrors = results.some((r) => "error" in r && Boolean(r.error));
|
|
265
|
-
process.exit(hasErrors ? 1 : 0);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// --all interactive
|
|
269
|
-
const { start, stop } = p.spinner();
|
|
270
|
-
start("모든 패키지의 변경사항을 가져오고 있어요...");
|
|
271
|
-
const results = await Promise.all(
|
|
272
|
-
packageNames.map((name) =>
|
|
273
|
-
upgradeOne({
|
|
274
|
-
targetPackage: name,
|
|
275
|
-
currentVersionSpec: seedPackages[name],
|
|
276
|
-
baseUrl: options.baseUrl,
|
|
277
|
-
}).catch((error): UpgradeOneResult & { error: string } => ({
|
|
278
|
-
package: name,
|
|
279
|
-
currentVersion: seedPackages[name],
|
|
280
|
-
latestVersion: "unknown",
|
|
281
|
-
upToDate: false,
|
|
282
|
-
changelog: null,
|
|
283
|
-
error: error instanceof Error ? error.message : String(error),
|
|
284
|
-
})),
|
|
285
|
-
),
|
|
286
|
-
);
|
|
287
|
-
stop("변경사항을 가져왔어요.");
|
|
288
|
-
|
|
289
|
-
for (const result of results) {
|
|
290
|
-
printResultInteractive(result);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
p.outro("완료했어요.");
|
|
294
|
-
await trackResults(options.cwd, results, startTime);
|
|
295
|
-
const hasErrors = results.some((r) => "error" in r && Boolean(r.error));
|
|
296
|
-
process.exit(hasErrors ? 1 : 0);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// resolve target package
|
|
300
|
-
let targetPackage: string;
|
|
301
|
-
|
|
302
|
-
if (options.packageName) {
|
|
303
|
-
targetPackage = toFullPackageName(options.packageName);
|
|
304
|
-
|
|
305
|
-
if (!seedPackages[targetPackage]) {
|
|
306
|
-
throw new CliError({
|
|
307
|
-
message: `${highlight(targetPackage)}: 프로젝트에 설치되어 있지 않아요.`,
|
|
308
|
-
hint: `설치된 패키지: ${packageNames.map((n) => highlight(n)).join(", ")}`,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
} else {
|
|
312
|
-
// no package, no --all: interactive select
|
|
313
|
-
if (raw) {
|
|
314
|
-
throw new CliError({
|
|
315
|
-
message: "--raw 모드에서는 패키지명 또는 --all 옵션이 필요해요.",
|
|
316
|
-
hint: "예: `seed-design upgrade react --raw` 또는 `seed-design upgrade --all --raw`",
|
|
317
|
-
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
if (packageNames.length === 1) {
|
|
321
|
-
targetPackage = packageNames[0];
|
|
322
|
-
} else {
|
|
323
|
-
const selected = await p.select({
|
|
324
|
-
message: "변경사항을 확인할 패키지를 선택해주세요",
|
|
325
|
-
options: packageNames.map((name) => ({
|
|
326
|
-
label: name,
|
|
327
|
-
value: name,
|
|
328
|
-
hint: seedPackages[name],
|
|
329
|
-
})),
|
|
330
|
-
});
|
|
331
|
-
if (p.isCancel(selected)) throw new CliCancelError();
|
|
332
|
-
targetPackage = selected;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// single package
|
|
337
|
-
if (raw) {
|
|
338
|
-
const result = await upgradeOne({
|
|
339
|
-
targetPackage,
|
|
340
|
-
currentVersionSpec: seedPackages[targetPackage],
|
|
341
|
-
baseUrl: options.baseUrl,
|
|
342
|
-
});
|
|
343
|
-
printResultRaw(result);
|
|
344
|
-
await trackResults(options.cwd, [result], startTime);
|
|
345
|
-
process.exit(0);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// single package interactive
|
|
349
|
-
const { start, stop } = p.spinner();
|
|
350
|
-
start("최신 버전을 확인하고 있어요...");
|
|
351
|
-
let result: UpgradeOneResult;
|
|
352
|
-
try {
|
|
353
|
-
result = await upgradeOne({
|
|
354
|
-
targetPackage,
|
|
355
|
-
currentVersionSpec: seedPackages[targetPackage],
|
|
356
|
-
baseUrl: options.baseUrl,
|
|
357
|
-
});
|
|
358
|
-
stop("변경사항을 가져왔어요.");
|
|
359
|
-
} catch (error) {
|
|
360
|
-
stop("변경사항을 가져오지 못했어요.");
|
|
361
|
-
throw error;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
printResultInteractive(result);
|
|
365
|
-
p.outro("완료했어요.");
|
|
366
|
-
await trackResults(options.cwd, [result], startTime);
|
|
367
|
-
} catch (error) {
|
|
368
|
-
if (isCliCancelError(error)) {
|
|
369
|
-
if (!raw) p.outro(highlight(error.message));
|
|
370
|
-
process.exit(0);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (raw) {
|
|
374
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
375
|
-
console.error(msg);
|
|
376
|
-
process.exit(1);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
handleCliError(error, {
|
|
380
|
-
defaultMessage: "업그레이드 확인에 실패했어요.",
|
|
381
|
-
defaultHint: "`--verbose` 옵션으로 상세 오류를 확인해보세요.",
|
|
382
|
-
verbose,
|
|
383
|
-
});
|
|
384
|
-
process.exit(1);
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
};
|