gz-cli-front 0.0.0-stage → 1.0.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/index.js ADDED
@@ -0,0 +1,1472 @@
1
+ // src/index.ts
2
+ import { Command } from "commander";
3
+ import { createRequire } from "module";
4
+
5
+ // src/commands/create.ts
6
+ import { confirm, input, select } from "@inquirer/prompts";
7
+ import chalk2 from "chalk";
8
+ import { execa as execa2 } from "execa";
9
+ import fs6 from "fs-extra";
10
+ import ora from "ora";
11
+ import path7 from "path";
12
+ import { performance as performance5 } from "perf_hooks";
13
+
14
+ // src/services/config.ts
15
+ import fs from "fs-extra";
16
+ import path from "path";
17
+ import { fileURLToPath } from "url";
18
+ var CONFIG_FILE_NAME = "gz-cli-front.config.json";
19
+ async function loadCliConfig() {
20
+ const configPath = await findConfigPath();
21
+ const config = await fs.readJson(configPath);
22
+ if (!isCliConfig(config)) {
23
+ throw new Error(`CLI \u914D\u7F6E\u6587\u4EF6\u65E0\u6548\uFF1A${configPath}`);
24
+ }
25
+ return config;
26
+ }
27
+ async function findConfigPath() {
28
+ const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
29
+ const configPaths = [
30
+ path.resolve(moduleDirectory, "config", CONFIG_FILE_NAME),
31
+ path.resolve(moduleDirectory, "..", "config", CONFIG_FILE_NAME),
32
+ path.resolve(moduleDirectory, "..", "..", "config", CONFIG_FILE_NAME)
33
+ ];
34
+ for (const configPath of configPaths) {
35
+ if (await fs.pathExists(configPath)) {
36
+ return configPath;
37
+ }
38
+ }
39
+ throw new Error("\u672A\u627E\u5230 gz-cli-front \u914D\u7F6E\u6587\u4EF6\u3002");
40
+ }
41
+ function isCliConfig(value) {
42
+ if (typeof value !== "object" || value === null) {
43
+ return false;
44
+ }
45
+ const templateRegistry = value.templateRegistry;
46
+ if (typeof templateRegistry !== "object" || templateRegistry === null) {
47
+ return false;
48
+ }
49
+ return isTemplateRegistryConfig(templateRegistry);
50
+ }
51
+ function isTemplateRegistryConfig(value) {
52
+ if (!isNonEmptyString(value.url)) {
53
+ return false;
54
+ }
55
+ if (value.source === void 0 || value.source === "http") {
56
+ return true;
57
+ }
58
+ return value.source === "git" && isNonEmptyString(value.file) && (value.version === void 0 || isNonEmptyString(value.version));
59
+ }
60
+ function isNonEmptyString(value) {
61
+ return typeof value === "string" && value.trim().length > 0;
62
+ }
63
+
64
+ // src/utils/pkg-manager.ts
65
+ import fs2 from "fs-extra";
66
+ import path2 from "path";
67
+ import { spawnSync } from "child_process";
68
+ var PACKAGE_MANAGERS = ["pnpm", "yarn", "npm"];
69
+ function detectPackageManager() {
70
+ return PACKAGE_MANAGERS.find(isPackageManagerAvailable) ?? "npm";
71
+ }
72
+ function isPackageManager(value) {
73
+ return PACKAGE_MANAGERS.includes(value);
74
+ }
75
+ async function updateProjectPackageJson(targetDir, projectName, packageManager) {
76
+ const packageJsonPath = path2.join(targetDir, "package.json");
77
+ if (!await fs2.pathExists(packageJsonPath)) {
78
+ return;
79
+ }
80
+ const packageJson = await fs2.readJson(packageJsonPath);
81
+ packageJson.name = projectName;
82
+ if (Object.prototype.hasOwnProperty.call(packageJson, "packageManager")) {
83
+ packageJson.packageManager = packageManager;
84
+ }
85
+ await fs2.writeJson(packageJsonPath, packageJson, { spaces: 2 });
86
+ }
87
+ function isPackageManagerAvailable(packageManager) {
88
+ const result = spawnSync(packageManager, ["--version"], {
89
+ shell: process.platform === "win32",
90
+ stdio: "ignore"
91
+ });
92
+ return result.status === 0;
93
+ }
94
+
95
+ // src/utils/template.ts
96
+ import fs4 from "fs-extra";
97
+ import path5 from "path";
98
+ import { performance as performance3 } from "perf_hooks";
99
+
100
+ // src/utils/cache.ts
101
+ import { createHash } from "crypto";
102
+ import os from "os";
103
+ import path3 from "path";
104
+ var TEMPLATE_REGISTRY_CACHE_TTL_MS = 5 * 60 * 1e3;
105
+ var TEMPLATE_ARCHIVE_CACHE_TTL_MS = 10 * 60 * 1e3;
106
+ function getTemplateRegistryCachePath(url) {
107
+ return path3.join(getCacheDirectory(), "registry", `${createCacheKey(url)}.json`);
108
+ }
109
+ function getTemplateArchiveCachePath(source) {
110
+ return path3.join(getCacheDirectory(), "templates", `${createCacheKey(source)}.tar.gz`);
111
+ }
112
+ function getGitRepositoryCachePath(namespace, source) {
113
+ return path3.join(getCacheDirectory(), namespace, `git-${createCacheKey(source)}`);
114
+ }
115
+ function isCacheFresh(updatedAt, ttlMs) {
116
+ return Date.now() - updatedAt < ttlMs;
117
+ }
118
+ function getCacheDirectory() {
119
+ const configuredDirectory = process.env.GZ_CLI_FRONT_CACHE_DIR?.trim();
120
+ return configuredDirectory || path3.join(os.homedir(), ".gz-cli-front", "cache");
121
+ }
122
+ function createCacheKey(value) {
123
+ return createHash("sha256").update(value).digest("hex").slice(0, 24);
124
+ }
125
+
126
+ // src/utils/http.ts
127
+ import { setTimeout as delay } from "timers/promises";
128
+ import {
129
+ EnvHttpProxyAgent,
130
+ ProxyAgent,
131
+ fetch as undiciFetch
132
+ } from "undici";
133
+
134
+ // src/utils/system-proxy.ts
135
+ import { execFile } from "child_process";
136
+ import { promisify } from "util";
137
+ var execFileAsync = promisify(execFile);
138
+ var WINDOWS_INTERNET_SETTINGS = String.raw`HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings`;
139
+ var SYSTEM_PROXY_TIMEOUT_MS = 1500;
140
+ var systemProxyCache = /* @__PURE__ */ new Map();
141
+ function getSystemProxy(target) {
142
+ const cacheKey = `${process.platform}:${target.protocol}`;
143
+ const cached = systemProxyCache.get(cacheKey);
144
+ if (cached) {
145
+ return cached;
146
+ }
147
+ const request = detectSystemProxy(target).catch(() => ({}));
148
+ systemProxyCache.set(cacheKey, request);
149
+ return request;
150
+ }
151
+ async function detectSystemProxy(target) {
152
+ if (process.platform === "win32") {
153
+ return detectWindowsProxy(target);
154
+ }
155
+ if (process.platform === "darwin") {
156
+ return detectMacOsProxy(target);
157
+ }
158
+ if (process.platform === "linux") {
159
+ return detectLinuxProxy(target);
160
+ }
161
+ return {};
162
+ }
163
+ async function detectWindowsProxy(target) {
164
+ const [enabledOutput, serverOutput, overrideOutput] = await Promise.all([
165
+ queryWindowsRegistry("ProxyEnable"),
166
+ queryWindowsRegistry("ProxyServer"),
167
+ queryWindowsRegistry("ProxyOverride")
168
+ ]);
169
+ const enabled = readRegistryValue(enabledOutput, "ProxyEnable");
170
+ if (enabled !== "0x1" && enabled !== "1") {
171
+ return {};
172
+ }
173
+ const proxyServer = readRegistryValue(serverOutput, "ProxyServer");
174
+ if (!proxyServer) {
175
+ return {};
176
+ }
177
+ const proxyAddress = selectWindowsProxy(proxyServer, target.protocol);
178
+ if (!proxyAddress) {
179
+ return {};
180
+ }
181
+ const proxyOverride = readRegistryValue(overrideOutput, "ProxyOverride");
182
+ return {
183
+ noProxy: proxyOverride?.split(";").filter((entry) => entry !== "<local>").join(","),
184
+ proxyUrl: normalizeProxyUrl(proxyAddress)
185
+ };
186
+ }
187
+ async function queryWindowsRegistry(valueName) {
188
+ try {
189
+ const result = await execFileAsync(
190
+ "reg.exe",
191
+ ["query", WINDOWS_INTERNET_SETTINGS, "/v", valueName],
192
+ { timeout: SYSTEM_PROXY_TIMEOUT_MS, windowsHide: true }
193
+ );
194
+ return result.stdout;
195
+ } catch {
196
+ return "";
197
+ }
198
+ }
199
+ function readRegistryValue(output, valueName) {
200
+ const line = output.split(/\r?\n/).find((entry) => entry.includes(valueName));
201
+ return line?.match(/REG_\w+\s+(.+)$/)?.[1]?.trim();
202
+ }
203
+ function selectWindowsProxy(proxyServer, protocol) {
204
+ if (!proxyServer.includes("=")) {
205
+ return proxyServer;
206
+ }
207
+ const proxies = new Map(
208
+ proxyServer.split(";").flatMap((entry) => {
209
+ const [key, value] = entry.split("=", 2).map((part) => part.trim());
210
+ return key && value ? [[key.toLowerCase(), value]] : [];
211
+ })
212
+ );
213
+ const protocolName = protocol.replace(":", "");
214
+ return proxies.get(protocolName) ?? proxies.get("http");
215
+ }
216
+ async function detectMacOsProxy(target) {
217
+ try {
218
+ const result = await execFileAsync("scutil", ["--proxy"], {
219
+ timeout: SYSTEM_PROXY_TIMEOUT_MS
220
+ });
221
+ const prefix = target.protocol === "https:" ? "HTTPS" : "HTTP";
222
+ if (readScutilValue(result.stdout, `${prefix}Enable`) !== "1") {
223
+ return {};
224
+ }
225
+ const host = readScutilValue(result.stdout, `${prefix}Proxy`);
226
+ const port = readScutilValue(result.stdout, `${prefix}Port`);
227
+ return host && port ? { proxyUrl: `http://${host}:${port}` } : {};
228
+ } catch {
229
+ return {};
230
+ }
231
+ }
232
+ function readScutilValue(output, key) {
233
+ return output.match(new RegExp(`^\\s*${key}\\s*:\\s*(.+)$`, "m"))?.[1]?.trim();
234
+ }
235
+ async function detectLinuxProxy(target) {
236
+ try {
237
+ const mode = stripQuotes(await queryGSettings("org.gnome.system.proxy", "mode"));
238
+ if (mode !== "manual") {
239
+ return {};
240
+ }
241
+ const protocol = target.protocol === "https:" ? "https" : "http";
242
+ const [hostValue, portValue, ignoredHostsValue] = await Promise.all([
243
+ queryGSettings(`org.gnome.system.proxy.${protocol}`, "host"),
244
+ queryGSettings(`org.gnome.system.proxy.${protocol}`, "port"),
245
+ queryGSettings("org.gnome.system.proxy", "ignore-hosts")
246
+ ]);
247
+ const host = stripQuotes(hostValue);
248
+ const port = Number.parseInt(portValue.trim(), 10);
249
+ if (!host || !Number.isInteger(port) || port <= 0) {
250
+ return {};
251
+ }
252
+ return {
253
+ noProxy: Array.from(
254
+ ignoredHostsValue.matchAll(/'([^']+)'/g),
255
+ (match) => match[1]
256
+ ).join(","),
257
+ proxyUrl: `http://${host}:${port}`
258
+ };
259
+ } catch {
260
+ return {};
261
+ }
262
+ }
263
+ async function queryGSettings(schema, key) {
264
+ const result = await execFileAsync("gsettings", ["get", schema, key], {
265
+ timeout: SYSTEM_PROXY_TIMEOUT_MS
266
+ });
267
+ return result.stdout;
268
+ }
269
+ function stripQuotes(value) {
270
+ return value.trim().replace(/^['"]|['"]$/g, "");
271
+ }
272
+ function normalizeProxyUrl(proxyAddress) {
273
+ return /^[a-z][a-z\d+.-]*:\/\//i.test(proxyAddress) ? proxyAddress : `http://${proxyAddress}`;
274
+ }
275
+
276
+ // src/utils/http.ts
277
+ var networkOptions = {};
278
+ function configureNetwork(options) {
279
+ if (options.proxy) {
280
+ validateProxyUrl(options.proxy);
281
+ }
282
+ networkOptions = { ...options };
283
+ }
284
+ async function requestBuffer(url, options) {
285
+ const startedAt = performance.now();
286
+ let lastError;
287
+ const proxyConfig = await resolveProxyConfig(url);
288
+ for (let attempt = 0; attempt <= options.retries; attempt += 1) {
289
+ const controller = new AbortController();
290
+ const timeout = setTimeout(() => controller.abort(), options.timeoutMs);
291
+ const dispatcher = proxyConfig.createDispatcher?.();
292
+ debugNetworkRoute(url, proxyConfig.route, attempt + 1, options.retries + 1);
293
+ try {
294
+ const response = await undiciFetch(url, {
295
+ ...dispatcher ? { dispatcher } : {},
296
+ headers: options.headers,
297
+ redirect: "follow",
298
+ signal: controller.signal
299
+ });
300
+ if (!response.ok) {
301
+ throw new Error(`HTTP ${response.status} ${response.statusText}`);
302
+ }
303
+ return {
304
+ body: Buffer.from(await response.arrayBuffer()),
305
+ elapsedMs: Math.round(performance.now() - startedAt),
306
+ route: proxyConfig.route,
307
+ status: response.status
308
+ };
309
+ } catch (error) {
310
+ lastError = error;
311
+ if (attempt < options.retries) {
312
+ await delay(250 * (attempt + 1));
313
+ }
314
+ } finally {
315
+ clearTimeout(timeout);
316
+ await dispatcher?.destroy();
317
+ }
318
+ }
319
+ throw createRequestError(url, options, proxyConfig.route, lastError);
320
+ }
321
+ async function resolveProxyConfig(url) {
322
+ const target = new URL(url);
323
+ if (networkOptions.forceDirect) {
324
+ return { route: "direct" };
325
+ }
326
+ if (networkOptions.proxy) {
327
+ const proxyUrl = networkOptions.proxy;
328
+ return {
329
+ createDispatcher: () => new ProxyAgent({ uri: proxyUrl }),
330
+ proxyUrl,
331
+ route: "proxy-environment"
332
+ };
333
+ }
334
+ const httpProxy = readEnvironmentVariable("http_proxy", "HTTP_PROXY");
335
+ const httpsProxy = readEnvironmentVariable("https_proxy", "HTTPS_PROXY");
336
+ const noProxy = readEnvironmentVariable("no_proxy", "NO_PROXY");
337
+ const environmentProxy = target.protocol === "https:" ? httpsProxy ?? httpProxy : httpProxy;
338
+ if (noProxy && shouldBypassProxy(target, noProxy)) {
339
+ return { route: "direct" };
340
+ }
341
+ if (environmentProxy) {
342
+ return {
343
+ createDispatcher: () => new EnvHttpProxyAgent({
344
+ httpProxy,
345
+ httpsProxy,
346
+ noProxy
347
+ }),
348
+ proxyUrl: environmentProxy,
349
+ route: "proxy-environment"
350
+ };
351
+ }
352
+ const systemProxy = await getSystemProxy(target);
353
+ if (systemProxy.noProxy && shouldBypassProxy(target, systemProxy.noProxy)) {
354
+ return { route: "direct" };
355
+ }
356
+ if (systemProxy.proxyUrl) {
357
+ return {
358
+ createDispatcher: () => new ProxyAgent({ uri: systemProxy.proxyUrl }),
359
+ proxyUrl: systemProxy.proxyUrl,
360
+ route: "proxy-system"
361
+ };
362
+ }
363
+ return { route: "direct" };
364
+ }
365
+ async function resolveGitNetworkConfig(repositoryUrl) {
366
+ if (!/^https?:\/\//i.test(repositoryUrl)) {
367
+ return { args: [], route: "ssh" };
368
+ }
369
+ const proxyConfig = await resolveProxyConfig(repositoryUrl);
370
+ if (proxyConfig.proxyUrl) {
371
+ return {
372
+ args: ["-c", `http.proxy=${proxyConfig.proxyUrl}`],
373
+ route: proxyConfig.route
374
+ };
375
+ }
376
+ return {
377
+ args: networkOptions.forceDirect ? ["-c", "http.proxy="] : [],
378
+ route: "direct"
379
+ };
380
+ }
381
+ function readEnvironmentVariable(primaryName, fallbackName) {
382
+ const value = process.env[primaryName] ?? process.env[fallbackName];
383
+ return value?.trim() || void 0;
384
+ }
385
+ function shouldBypassProxy(target, noProxy) {
386
+ const hostname = target.hostname.toLowerCase();
387
+ const port = Number.parseInt(target.port, 10) || (target.protocol === "https:" ? 443 : 80);
388
+ return noProxy.split(/[,\s]/).some((rawEntry) => {
389
+ const entry = rawEntry.trim().toLowerCase();
390
+ if (!entry) {
391
+ return false;
392
+ }
393
+ if (entry === "*") {
394
+ return true;
395
+ }
396
+ const parsed = entry.match(/^(.+):(\d+)$/);
397
+ const entryHostname = parsed ? parsed[1] : entry;
398
+ const entryPort = parsed ? Number.parseInt(parsed[2], 10) : 0;
399
+ if (entryPort && entryPort !== port) {
400
+ return false;
401
+ }
402
+ if (!/^[.*]/.test(entryHostname)) {
403
+ return hostname === entryHostname;
404
+ }
405
+ return hostname.endsWith(entryHostname.replace(/^\*/, ""));
406
+ });
407
+ }
408
+ function formatNetworkRoute(route) {
409
+ if (route === "proxy-environment") {
410
+ return "\u73AF\u5883\u53D8\u91CF\u4EE3\u7406";
411
+ }
412
+ if (route === "proxy-system") {
413
+ return "\u7CFB\u7EDF\u4EE3\u7406";
414
+ }
415
+ return "\u76F4\u8FDE";
416
+ }
417
+ function createRequestError(url, options, route, error) {
418
+ const cause = getErrorMessage(error);
419
+ return new Error(
420
+ `\u8BF7\u6C42\u5931\u8D25
421
+ url: ${url}
422
+ error: ${cause}
423
+ network: ${formatNetworkRoute(route)}
424
+ timeout: ${options.timeoutMs} ms
425
+ attempts: ${options.retries + 1}`
426
+ );
427
+ }
428
+ function validateProxyUrl(proxyUrl) {
429
+ try {
430
+ const parsed = new URL(proxyUrl);
431
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
432
+ throw new Error();
433
+ }
434
+ } catch {
435
+ throw new Error("\u65E0\u6548\u7684\u4EE3\u7406\u5730\u5740\uFF0C\u8BF7\u4F7F\u7528 http://host:port \u6216 https://host:port \u683C\u5F0F\u3002");
436
+ }
437
+ }
438
+ function debugNetworkRoute(url, route, attempt, attempts) {
439
+ if (process.env.GZ_CLI_FRONT_DEBUG !== "1") {
440
+ return;
441
+ }
442
+ console.log(`[network] host: ${new URL(url).host}`);
443
+ console.log(`[network] route: ${formatNetworkRoute(route)}`);
444
+ console.log(`[network] attempt: ${attempt}/${attempts}`);
445
+ }
446
+ function getErrorMessage(error) {
447
+ if (!(error instanceof Error)) {
448
+ return "\u672A\u77E5\u9519\u8BEF";
449
+ }
450
+ const cause = "cause" in error ? error.cause : void 0;
451
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? "");
452
+ return causeMessage ? `${error.message} (${causeMessage})` : error.message;
453
+ }
454
+
455
+ // src/utils/git-repository.ts
456
+ import { execa } from "execa";
457
+ import fs3 from "fs-extra";
458
+ import { randomUUID } from "crypto";
459
+ import path4 from "path";
460
+ import { performance as performance2 } from "perf_hooks";
461
+ import { setTimeout as delay2 } from "timers/promises";
462
+ var GIT_OPERATION_ATTEMPTS = 2;
463
+ var GIT_AUTH_TIMEOUT_MS = 6e4;
464
+ var GIT_SSH_CLONE_TIMEOUT_MS = 2e4;
465
+ async function ensureGitRepository(repositoryUrl, options) {
466
+ const version = normalizeVersion(options.version);
467
+ const repositoryPath = getRepositoryCachePath(repositoryUrl, options.namespace, version);
468
+ const startedAt = performance2.now();
469
+ const cacheExists = await isValidGitCache(repositoryPath);
470
+ if (await canUseGitCache(repositoryPath, cacheExists, version, options)) {
471
+ return createCacheResult(repositoryPath, startedAt, false);
472
+ }
473
+ const temporaryPath = `${repositoryPath}.${randomUUID()}.tmp`;
474
+ const candidates = getRepositoryCandidates(repositoryUrl);
475
+ let lastError;
476
+ await fs3.ensureDir(path4.dirname(repositoryPath));
477
+ for (const [index, candidate] of candidates.entries()) {
478
+ try {
479
+ const network = await resolveGitNetworkConfig(candidate);
480
+ const isSshCandidate = network.route === "ssh";
481
+ const timeoutMs = isSshCandidate ? GIT_SSH_CLONE_TIMEOUT_MS : GIT_AUTH_TIMEOUT_MS;
482
+ debugGitRepository(candidate, network.route, timeoutMs);
483
+ await cloneRepository(
484
+ candidate,
485
+ temporaryPath,
486
+ version,
487
+ network.args,
488
+ timeoutMs,
489
+ isSshCandidate ? 1 : GIT_OPERATION_ATTEMPTS
490
+ );
491
+ await replaceCacheDirectory(temporaryPath, repositoryPath);
492
+ return {
493
+ cacheHit: false,
494
+ elapsedMs: Math.round(performance2.now() - startedAt),
495
+ path: repositoryPath,
496
+ route: network.route,
497
+ staleCache: false
498
+ };
499
+ } catch (error) {
500
+ lastError = error;
501
+ await fs3.remove(temporaryPath);
502
+ if (index < candidates.length - 1) {
503
+ if (process.env.GZ_CLI_FRONT_DEBUG === "1") {
504
+ console.log("[git] SSH \u4E0D\u53EF\u7528\uFF0C\u6B63\u5728\u5C1D\u8BD5 HTTPS...");
505
+ }
506
+ continue;
507
+ }
508
+ }
509
+ }
510
+ if (cacheExists && !options.forceRefresh) {
511
+ return createCacheResult(repositoryPath, startedAt, true);
512
+ }
513
+ throw createGitError(repositoryUrl, lastError, "clone");
514
+ }
515
+ function getRepositoryCachePath(repositoryUrl, namespace, version) {
516
+ return getGitRepositoryCachePath(
517
+ namespace,
518
+ `${getRepositoryIdentity(repositoryUrl)}#${version ?? "latest"}`
519
+ );
520
+ }
521
+ function getRepositoryCandidates(repositoryUrl) {
522
+ return [repositoryUrl];
523
+ }
524
+ function getRepositoryIdentity(repositoryUrl) {
525
+ try {
526
+ const parsed = new URL(repositoryUrl);
527
+ const repositoryPath = parsed.pathname.replace(/^\/|\/$/g, "").replace(/\.git$/i, "");
528
+ return `${parsed.hostname.toLowerCase()}/${repositoryPath}`;
529
+ } catch {
530
+ const sshMatch = repositoryUrl.match(/^[^@]+@([^:]+):(.+)$/);
531
+ if (!sshMatch) {
532
+ return repositoryUrl;
533
+ }
534
+ return `${sshMatch[1].toLowerCase()}/${sshMatch[2].replace(/\.git$/i, "")}`;
535
+ }
536
+ }
537
+ async function canUseGitCache(repositoryPath, cacheExists, version, options) {
538
+ if (options.forceRefresh || !cacheExists) {
539
+ return false;
540
+ }
541
+ const stats = await fs3.stat(repositoryPath);
542
+ return Boolean(version) || isCacheFresh(stats.mtimeMs, options.ttlMs);
543
+ }
544
+ function debugGitRepository(repositoryUrl, route, timeoutMs) {
545
+ if (process.env.GZ_CLI_FRONT_DEBUG !== "1") {
546
+ return;
547
+ }
548
+ const routeLabel = route === "ssh" ? "Git/SSH" : formatNetworkRoute(route);
549
+ console.log(`[git] host: ${getRepositoryHost(repositoryUrl)}`);
550
+ console.log(`[git] route: ${routeLabel}`);
551
+ console.log(`[git] timeout: ${timeoutMs} ms`);
552
+ }
553
+ function getRepositoryHost(repositoryUrl) {
554
+ try {
555
+ return new URL(repositoryUrl).host;
556
+ } catch {
557
+ return repositoryUrl.match(/^[^@]+@([^:]+):/)?.[1] ?? "unknown";
558
+ }
559
+ }
560
+ async function cloneRepository(repositoryUrl, targetPath, version, networkArgs, timeoutMs, attempts) {
561
+ const args = [
562
+ ...networkArgs,
563
+ "clone",
564
+ "--quiet",
565
+ "--depth",
566
+ "1",
567
+ "--single-branch"
568
+ ];
569
+ if (version) {
570
+ args.push("--branch", version);
571
+ }
572
+ args.push(repositoryUrl, targetPath);
573
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
574
+ try {
575
+ await execa("git", args, {
576
+ forceKillAfterDelay: 1e3,
577
+ stdin: "inherit",
578
+ stdout: "pipe",
579
+ stderr: ["pipe", "inherit"],
580
+ timeout: timeoutMs,
581
+ windowsHide: true
582
+ });
583
+ return;
584
+ } catch (error) {
585
+ if (attempt === attempts || !isTransientGitError(error)) {
586
+ throw error;
587
+ }
588
+ if (process.env.GZ_CLI_FRONT_DEBUG === "1") {
589
+ console.log(`[git] transient failure, retry: ${attempt + 1}/${attempts}`);
590
+ }
591
+ await fs3.remove(targetPath);
592
+ await delay2(300);
593
+ }
594
+ }
595
+ }
596
+ function isTransientGitError(error) {
597
+ if (typeof error === "object" && error !== null && error.timedOut === true) {
598
+ return true;
599
+ }
600
+ const message = error instanceof Error ? error.message : String(error);
601
+ return [
602
+ /SSL\/TLS connection failed/i,
603
+ /failed to receive handshake/i,
604
+ /connection (?:was )?reset/i,
605
+ /remote end hung up unexpectedly/i,
606
+ /early EOF/i,
607
+ /failed to connect/i,
608
+ /could not resolve host/i,
609
+ /timed out|timeout/i
610
+ ].some((pattern) => pattern.test(message));
611
+ }
612
+ async function replaceCacheDirectory(sourcePath, targetPath) {
613
+ const backupPath = `${targetPath}.${randomUUID()}.bak`;
614
+ const targetExists = await fs3.pathExists(targetPath);
615
+ if (targetExists) {
616
+ await fs3.move(targetPath, backupPath);
617
+ }
618
+ try {
619
+ await fs3.move(sourcePath, targetPath);
620
+ await fs3.remove(backupPath);
621
+ } catch (error) {
622
+ await fs3.remove(targetPath);
623
+ if (await fs3.pathExists(backupPath)) {
624
+ await fs3.move(backupPath, targetPath);
625
+ }
626
+ throw error;
627
+ }
628
+ }
629
+ async function isValidGitCache(repositoryPath) {
630
+ return fs3.pathExists(path4.join(repositoryPath, ".git"));
631
+ }
632
+ function createCacheResult(repositoryPath, startedAt, staleCache) {
633
+ return {
634
+ cacheHit: true,
635
+ elapsedMs: Math.round(performance2.now() - startedAt),
636
+ path: repositoryPath,
637
+ route: staleCache ? "stale-cache" : "cache",
638
+ staleCache
639
+ };
640
+ }
641
+ function normalizeVersion(version) {
642
+ return version && version !== "latest" ? version : void 0;
643
+ }
644
+ function createGitError(repositoryUrl, error, stage) {
645
+ const message = getGitErrorMessage(error);
646
+ const safeRepository = sanitizeRepositoryUrl(repositoryUrl);
647
+ debugGitFailure(stage, message);
648
+ if (isGitTimeoutError(error, message)) {
649
+ return new Error(
650
+ `Git \u4ED3\u5E93\u8FDE\u63A5\u8D85\u65F6\uFF08\u9636\u6BB5\uFF1A${stage}\uFF0C${GIT_AUTH_TIMEOUT_MS} ms\uFF09\uFF1A${safeRepository}
651
+ \u8BF7\u68C0\u67E5\u5F53\u524D\u7F51\u7EDC\u6216\u4EE3\u7406\u8BBE\u7F6E\u540E\u91CD\u8BD5\u3002`
652
+ );
653
+ }
654
+ if (isGitPermissionError(message)) {
655
+ return new Error(
656
+ `\u65E0\u6CD5\u8BBF\u95EE Git \u4ED3\u5E93\uFF08\u9636\u6BB5\uFF1A${stage}\uFF09\uFF1A${safeRepository}
657
+ \u8BF7\u68C0\u67E5\u4ED3\u5E93\u5730\u5740\u3001\u7F51\u7EDC\u8FDE\u63A5\u53CA\u5F53\u524D Git \u51ED\u636E\u540E\u91CD\u8BD5\u3002`
658
+ );
659
+ }
660
+ return new Error(
661
+ `Git \u4ED3\u5E93\u8BBF\u95EE\u5931\u8D25\uFF08\u9636\u6BB5\uFF1A${stage}\uFF09\uFF1A${safeRepository}
662
+ \u8BF7\u786E\u8BA4\u5DF2\u5B89\u88C5 Git\uFF0C\u4E14\u5F53\u524D Git/SSH \u8D26\u53F7\u62E5\u6709\u4ED3\u5E93 Read \u6743\u9650\u3002
663
+ ${message}`
664
+ );
665
+ }
666
+ function getGitErrorMessage(error) {
667
+ return redactCredentials(error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF");
668
+ }
669
+ function debugGitFailure(stage, message) {
670
+ if (process.env.GZ_CLI_FRONT_DEBUG !== "1") {
671
+ return;
672
+ }
673
+ console.error(`[git] failed stage: ${stage}`);
674
+ console.error(`[git] error: ${message}`);
675
+ }
676
+ function isGitTimeoutError(error, message) {
677
+ if (typeof error === "object" && error !== null && error.timedOut === true) {
678
+ return true;
679
+ }
680
+ return /timed out|timeout/i.test(message);
681
+ }
682
+ function isGitPermissionError(message) {
683
+ return [
684
+ /authentication failed/i,
685
+ /permission denied/i,
686
+ /repository not found/i,
687
+ /could not read username/i,
688
+ /unable to get (?:password|username) from user/i,
689
+ /terminal prompts disabled/i,
690
+ /user interaction is disabled/i,
691
+ /interactive prompts are disabled/i,
692
+ /credential.*(?:failed|unavailable)/i,
693
+ /access denied/i,
694
+ /http (?:error )?40[13]/i,
695
+ /status code: 40[13]/i
696
+ ].some((pattern) => pattern.test(message));
697
+ }
698
+ function redactCredentials(message) {
699
+ return message.replace(/http\.proxy=\S+/gi, "http.proxy=***").replace(/(https?:\/\/)[^@\s/]+@/gi, "$1***@");
700
+ }
701
+ function sanitizeRepositoryUrl(repositoryUrl) {
702
+ try {
703
+ const parsed = new URL(repositoryUrl);
704
+ parsed.username = "";
705
+ parsed.password = "";
706
+ return parsed.toString();
707
+ } catch {
708
+ return repositoryUrl;
709
+ }
710
+ }
711
+
712
+ // src/utils/template.ts
713
+ var TEXT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
714
+ ".json",
715
+ ".ts",
716
+ ".tsx",
717
+ ".js",
718
+ ".jsx",
719
+ ".md",
720
+ ".html",
721
+ ".css",
722
+ ".less",
723
+ ".scss",
724
+ ".env"
725
+ ]);
726
+ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
727
+ var TEMPLATE_DEBUG_ENV = "GZ_CLI_FRONT_DEBUG";
728
+ var TEMPLATE_REGISTRY_TIMEOUT_MS = 5e3;
729
+ async function fetchTemplates(templateRegistry, options = {}) {
730
+ const registry = typeof templateRegistry === "string" ? { url: templateRegistry } : templateRegistry;
731
+ if (registry.source === "git") {
732
+ return fetchTemplatesFromGit(registry, options);
733
+ }
734
+ return fetchTemplatesFromHttp(registry.url, options);
735
+ }
736
+ async function fetchTemplatesFromHttp(templateRegistryUrl, options) {
737
+ const cachePath = getTemplateRegistryCachePath(templateRegistryUrl);
738
+ const cacheStartedAt = performance3.now();
739
+ let cached = await readRegistryCache(cachePath, templateRegistryUrl);
740
+ const cacheReadMs = Math.round(performance3.now() - cacheStartedAt);
741
+ debugTemplateRequest(templateRegistryUrl, cachePath, options.forceRefresh === true);
742
+ if (!options.forceRefresh && cached && isCacheFresh(cached.fetchedAt, TEMPLATE_REGISTRY_CACHE_TTL_MS)) {
743
+ try {
744
+ return parseTemplates(cached.body, true, false, cacheReadMs, "cache");
745
+ } catch {
746
+ await fs4.remove(cachePath);
747
+ cached = void 0;
748
+ }
749
+ }
750
+ const fetchStartedAt = performance3.now();
751
+ try {
752
+ const response = await requestBuffer(templateRegistryUrl, {
753
+ retries: 1,
754
+ timeoutMs: TEMPLATE_REGISTRY_TIMEOUT_MS
755
+ });
756
+ const body = response.body.toString("utf8");
757
+ const result = parseTemplates(body, false, false, response.elapsedMs, response.route);
758
+ try {
759
+ await writeRegistryCache(cachePath, {
760
+ body,
761
+ fetchedAt: Date.now(),
762
+ url: templateRegistryUrl
763
+ });
764
+ } catch (error) {
765
+ if (process.env[TEMPLATE_DEBUG_ENV] === "1") {
766
+ console.log(`template config cache write failed: ${getErrorMessage2(error)}`);
767
+ }
768
+ }
769
+ return result;
770
+ } catch (error) {
771
+ if (!options.forceRefresh && cached) {
772
+ return parseTemplates(
773
+ cached.body,
774
+ true,
775
+ true,
776
+ Math.round(performance3.now() - fetchStartedAt),
777
+ "stale-cache"
778
+ );
779
+ }
780
+ throw createTemplateRequestError(templateRegistryUrl, error);
781
+ }
782
+ }
783
+ async function fetchTemplatesFromGit(registry, options) {
784
+ const cacheKey = `git:${registry.url}#${registry.version ?? "latest"}:${registry.file}`;
785
+ const fallbackCachePath = getTemplateRegistryCachePath(cacheKey);
786
+ const fallbackCache = await readRegistryCache(fallbackCachePath, cacheKey);
787
+ const startedAt = performance3.now();
788
+ try {
789
+ const result = await ensureGitRepository(registry.url, {
790
+ forceRefresh: options.forceRefresh,
791
+ namespace: "registry",
792
+ ttlMs: TEMPLATE_REGISTRY_CACHE_TTL_MS,
793
+ version: registry.version
794
+ });
795
+ const registryFile = resolveRegistryFile(result.path, registry.file);
796
+ const body = await fs4.readFile(registryFile, "utf8");
797
+ const parsed = parseTemplates(
798
+ body,
799
+ result.cacheHit,
800
+ result.staleCache,
801
+ result.elapsedMs,
802
+ result.route
803
+ );
804
+ if (!result.cacheHit || !fallbackCache) {
805
+ await writeRegistryCache(fallbackCachePath, {
806
+ body,
807
+ fetchedAt: Date.now(),
808
+ url: cacheKey
809
+ }).catch(() => void 0);
810
+ }
811
+ return parsed;
812
+ } catch (error) {
813
+ if (!options.forceRefresh && fallbackCache) {
814
+ return parseTemplates(
815
+ fallbackCache.body,
816
+ true,
817
+ true,
818
+ Math.round(performance3.now() - startedAt),
819
+ "stale-cache"
820
+ );
821
+ }
822
+ throw new Error(`\u6A21\u677F\u6CE8\u518C\u4ED3\u5E93\u8BFB\u53D6\u5931\u8D25
823
+ error: ${getErrorMessage2(error)}`);
824
+ }
825
+ }
826
+ function resolveRegistryFile(repositoryPath, registryFile) {
827
+ const resolvedRepository = path5.resolve(repositoryPath);
828
+ const resolvedFile = path5.resolve(repositoryPath, registryFile);
829
+ if (resolvedFile !== resolvedRepository && !resolvedFile.startsWith(`${resolvedRepository}${path5.sep}`)) {
830
+ throw new Error(`\u6A21\u677F\u6CE8\u518C\u6587\u4EF6\u5FC5\u987B\u4F4D\u4E8E\u6CE8\u518C\u4ED3\u5E93\u5185\uFF1A${registryFile}`);
831
+ }
832
+ return resolvedFile;
833
+ }
834
+ function parseTemplates(body, cacheHit, staleCache, fetchMs, route) {
835
+ const parseStartedAt = performance3.now();
836
+ let data;
837
+ try {
838
+ data = JSON.parse(body);
839
+ } catch (error) {
840
+ throw new Error(`\u6A21\u677F\u914D\u7F6E JSON \u89E3\u6790\u5931\u8D25\uFF1A${getErrorMessage2(error)}`);
841
+ }
842
+ if (!Array.isArray(data) || data.length === 0) {
843
+ throw new Error("\u6A21\u677F\u6CE8\u518C\u8868\u4E3A\u7A7A");
844
+ }
845
+ const templates = data.filter(isTemplateMeta);
846
+ if (templates.length === 0) {
847
+ throw new Error("\u6A21\u677F\u6CE8\u518C\u8868\u4E2D\u6CA1\u6709\u6709\u6548\u6A21\u677F");
848
+ }
849
+ return {
850
+ cacheHit,
851
+ fetchMs,
852
+ parseMs: Math.round(performance3.now() - parseStartedAt),
853
+ route,
854
+ staleCache,
855
+ templates
856
+ };
857
+ }
858
+ function debugTemplateRequest(templateRegistryUrl, cachePath, forceRefresh) {
859
+ if (process.env[TEMPLATE_DEBUG_ENV] !== "1") {
860
+ return;
861
+ }
862
+ console.log("template config url:", templateRegistryUrl);
863
+ console.log("template config client: undici fetch");
864
+ console.log(`template config timeout: ${TEMPLATE_REGISTRY_TIMEOUT_MS} ms`);
865
+ console.log("template config cache:", cachePath);
866
+ console.log("template config force refresh:", forceRefresh);
867
+ }
868
+ function createTemplateRequestError(templateRegistryUrl, error) {
869
+ return new Error(
870
+ `\u6A21\u677F\u914D\u7F6E\u8BF7\u6C42\u5931\u8D25
871
+ url: ${templateRegistryUrl}
872
+ error: ${getErrorMessage2(error)}`
873
+ );
874
+ }
875
+ function getErrorMessage2(error) {
876
+ if (!(error instanceof Error)) {
877
+ return "\u672A\u77E5\u9519\u8BEF";
878
+ }
879
+ const cause = "cause" in error ? error.cause : void 0;
880
+ const causeMessage = cause instanceof Error ? cause.message : String(cause ?? "");
881
+ return causeMessage ? `${error.message} (${causeMessage})` : error.message;
882
+ }
883
+ async function readRegistryCache(cachePath, templateRegistryUrl) {
884
+ try {
885
+ const cache = await fs4.readJson(cachePath);
886
+ if (!isRecord(cache) || cache.url !== templateRegistryUrl || typeof cache.body !== "string" || typeof cache.fetchedAt !== "number") {
887
+ return void 0;
888
+ }
889
+ return {
890
+ body: cache.body,
891
+ fetchedAt: cache.fetchedAt,
892
+ url: cache.url
893
+ };
894
+ } catch {
895
+ return void 0;
896
+ }
897
+ }
898
+ async function writeRegistryCache(cachePath, cache) {
899
+ await fs4.ensureDir(path5.dirname(cachePath));
900
+ await fs4.writeJson(cachePath, cache);
901
+ }
902
+ async function replaceTemplateVariables(targetDir, variables) {
903
+ await visitDirectory(targetDir, async (filePath) => {
904
+ if (!shouldProcessFile(filePath)) {
905
+ return;
906
+ }
907
+ const content = await fs4.readFile(filePath, "utf8");
908
+ const replaced = Object.entries(variables).reduce(
909
+ (result, [key, value]) => result.split(`{{ ${key} }}`).join(value),
910
+ content
911
+ );
912
+ if (replaced !== content) {
913
+ await fs4.writeFile(filePath, replaced, "utf8");
914
+ }
915
+ });
916
+ }
917
+ async function visitDirectory(directory, onFile) {
918
+ const entries = await fs4.readdir(directory, { withFileTypes: true });
919
+ for (const entry of entries) {
920
+ const entryPath = path5.join(directory, entry.name);
921
+ if (entry.isDirectory()) {
922
+ if (!IGNORED_DIRECTORIES.has(entry.name)) {
923
+ await visitDirectory(entryPath, onFile);
924
+ }
925
+ } else if (entry.isFile()) {
926
+ await onFile(entryPath);
927
+ }
928
+ }
929
+ }
930
+ function shouldProcessFile(filePath) {
931
+ const filename = path5.basename(filePath);
932
+ return TEXT_FILE_EXTENSIONS.has(path5.extname(filename)) || filename.startsWith(".env");
933
+ }
934
+ function isTemplateMeta(value) {
935
+ if (!isRecord(value)) {
936
+ return false;
937
+ }
938
+ return isString(value.name) && isString(value.label) && isString(value.description) && isTemplateType(value.type) && isTemplateLocation(value) && (value.version === void 0 || isString(value.version));
939
+ }
940
+ function isRecord(value) {
941
+ return typeof value === "object" && value !== null;
942
+ }
943
+ function isString(value) {
944
+ return typeof value === "string" && value.trim().length > 0;
945
+ }
946
+ function isTemplateType(value) {
947
+ return value === "main" || value === "micro";
948
+ }
949
+ function isTemplateLocation(value) {
950
+ if (value.source !== void 0 && !isTemplateSource(value.source)) {
951
+ return false;
952
+ }
953
+ if (isString(value.url)) {
954
+ return true;
955
+ }
956
+ return value.source === void 0 && isString(value.repo);
957
+ }
958
+ function isTemplateSource(value) {
959
+ return value === "github" || value === "gitlab" || value === "git";
960
+ }
961
+
962
+ // src/utils/template-downloader.ts
963
+ import fs5 from "fs-extra";
964
+ import { randomUUID as randomUUID2 } from "crypto";
965
+ import path6 from "path";
966
+ import { performance as performance4 } from "perf_hooks";
967
+ import { x as extractTar } from "tar";
968
+ var TEMPLATE_DOWNLOAD_TIMEOUT_MS = 12e3;
969
+ var DOWNLOADERS = {
970
+ github: downloadFromGitHub,
971
+ gitlab: downloadFromGit,
972
+ git: downloadFromGit
973
+ };
974
+ async function downloadTemplate(template, targetDir, options = {}) {
975
+ const source = template.source ?? "github";
976
+ return DOWNLOADERS[source](template, targetDir, options);
977
+ }
978
+ async function downloadFromGitHub(template, targetDir, options) {
979
+ const repository = normalizeGitHubRepository(getTemplateUrl(template));
980
+ const version = template.version && template.version !== "latest" ? template.version : void 0;
981
+ const sourceKey = `${repository}${version ? `#${version}` : "#latest"}`;
982
+ const archivePath = getTemplateArchiveCachePath(sourceKey);
983
+ const archive = await ensureGitHubArchive(
984
+ repository,
985
+ version,
986
+ archivePath,
987
+ options.forceRefresh === true
988
+ );
989
+ const extractStartedAt = performance4.now();
990
+ await fs5.ensureDir(targetDir);
991
+ try {
992
+ await extractTar({
993
+ cwd: targetDir,
994
+ file: archivePath,
995
+ preservePaths: false,
996
+ strict: true,
997
+ strip: 1
998
+ });
999
+ } catch (error) {
1000
+ if (!archive.cacheHit) {
1001
+ throw error;
1002
+ }
1003
+ await fs5.remove(archivePath);
1004
+ await fs5.emptyDir(targetDir);
1005
+ return downloadFromGitHub(template, targetDir, { forceRefresh: true });
1006
+ }
1007
+ return {
1008
+ cacheHit: archive.cacheHit,
1009
+ copyMs: 0,
1010
+ downloadMs: archive.downloadMs,
1011
+ extractMs: Math.round(performance4.now() - extractStartedAt),
1012
+ route: archive.route
1013
+ };
1014
+ }
1015
+ async function downloadFromGit(template, targetDir, options) {
1016
+ if (!template.url) {
1017
+ throw new Error(`${template.source} \u6A21\u677F\u5FC5\u987B\u63D0\u4F9B Git clone URL\u3002`);
1018
+ }
1019
+ const repository = await ensureGitRepository(template.url, {
1020
+ forceRefresh: options.forceRefresh,
1021
+ namespace: "templates",
1022
+ ttlMs: TEMPLATE_ARCHIVE_CACHE_TTL_MS,
1023
+ version: template.version
1024
+ });
1025
+ const copyStartedAt = performance4.now();
1026
+ await fs5.copy(repository.path, targetDir, {
1027
+ filter: (sourcePath) => path6.basename(sourcePath) !== ".git"
1028
+ });
1029
+ return {
1030
+ cacheHit: repository.cacheHit,
1031
+ copyMs: Math.round(performance4.now() - copyStartedAt),
1032
+ downloadMs: repository.elapsedMs,
1033
+ extractMs: 0,
1034
+ route: repository.route
1035
+ };
1036
+ }
1037
+ async function ensureGitHubArchive(repository, version, archivePath, forceRefresh) {
1038
+ const startedAt = performance4.now();
1039
+ if (!forceRefresh && await canUseArchiveCache(archivePath, Boolean(version))) {
1040
+ return {
1041
+ cacheHit: true,
1042
+ downloadMs: Math.round(performance4.now() - startedAt),
1043
+ route: "cache"
1044
+ };
1045
+ }
1046
+ const archiveRequest = createGitHubArchiveRequest(repository, version);
1047
+ const response = await requestBuffer(archiveRequest.url, {
1048
+ headers: archiveRequest.headers,
1049
+ retries: 1,
1050
+ timeoutMs: TEMPLATE_DOWNLOAD_TIMEOUT_MS
1051
+ });
1052
+ const temporaryPath = `${archivePath}.${randomUUID2()}.tmp`;
1053
+ await fs5.ensureDir(path6.dirname(archivePath));
1054
+ try {
1055
+ await fs5.writeFile(temporaryPath, response.body);
1056
+ await fs5.move(temporaryPath, archivePath, { overwrite: true });
1057
+ } finally {
1058
+ await fs5.remove(temporaryPath);
1059
+ }
1060
+ return {
1061
+ cacheHit: false,
1062
+ downloadMs: Math.round(performance4.now() - startedAt),
1063
+ route: response.route
1064
+ };
1065
+ }
1066
+ function createGitHubArchiveRequest(repository, version) {
1067
+ const token = process.env.GITHUB_TOKEN?.trim();
1068
+ if (token) {
1069
+ return {
1070
+ headers: createGitHubApiHeaders(token),
1071
+ url: `https://api.github.com/repos/${repository}/tarball${version ? `/${encodeURIComponent(version)}` : ""}`
1072
+ };
1073
+ }
1074
+ return {
1075
+ headers: { "user-agent": "gz-cli-front" },
1076
+ url: `https://codeload.github.com/${repository}/tar.gz/${encodeURIComponent(version ?? "HEAD")}`
1077
+ };
1078
+ }
1079
+ async function canUseArchiveCache(archivePath, immutableVersion) {
1080
+ try {
1081
+ const stats = await fs5.stat(archivePath);
1082
+ return stats.isFile() && (immutableVersion || isCacheFresh(stats.mtimeMs, TEMPLATE_ARCHIVE_CACHE_TTL_MS));
1083
+ } catch {
1084
+ return false;
1085
+ }
1086
+ }
1087
+ function normalizeGitHubRepository(repository) {
1088
+ const normalized = repository.replace(/^https:\/\/github\.com\//i, "").replace(/\.git$/i, "").replace(/^\/+|\/+$/g, "");
1089
+ if (!/^[^/\s]+\/[^/\s]+$/.test(normalized)) {
1090
+ throw new Error(`\u65E0\u6548\u7684 GitHub \u6A21\u677F\u5730\u5740\uFF1A${repository}`);
1091
+ }
1092
+ return normalized;
1093
+ }
1094
+ function createGitHubApiHeaders(token) {
1095
+ return {
1096
+ accept: "application/vnd.github+json",
1097
+ authorization: `Bearer ${token}`,
1098
+ "user-agent": "gz-cli-front",
1099
+ "x-github-api-version": "2022-11-28"
1100
+ };
1101
+ }
1102
+ function getTemplateUrl(template) {
1103
+ if (template.url) {
1104
+ return template.url;
1105
+ }
1106
+ if (template.repo) {
1107
+ return template.repo;
1108
+ }
1109
+ throw new Error("\u6A21\u677F\u914D\u7F6E\u7F3A\u5C11 url\u3002");
1110
+ }
1111
+
1112
+ // src/utils/stable-spinner.ts
1113
+ import chalk from "chalk";
1114
+ import { clearLine, cursorTo } from "readline";
1115
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1116
+ var SPINNER_INTERVAL_MS = 80;
1117
+ var RESTORE_CURSOR = "\x1B[u";
1118
+ var SAVE_CURSOR = "\x1B[s";
1119
+ function createStableSpinner() {
1120
+ return new StableTextSpinner(process.stderr);
1121
+ }
1122
+ var StableTextSpinner = class {
1123
+ constructor(stream) {
1124
+ this.frameIndex = 0;
1125
+ this.value = "";
1126
+ this.stream = stream;
1127
+ }
1128
+ get isSpinning() {
1129
+ return this.interval !== void 0;
1130
+ }
1131
+ get text() {
1132
+ return this.value;
1133
+ }
1134
+ set text(value) {
1135
+ this.value = value;
1136
+ if (this.isSpinning) {
1137
+ this.renderLine();
1138
+ }
1139
+ }
1140
+ start(text) {
1141
+ if (text !== void 0) {
1142
+ this.value = text;
1143
+ }
1144
+ if (!this.stream.isTTY || this.isSpinning) {
1145
+ if (!this.stream.isTTY && this.value) {
1146
+ this.stream.write(`- ${this.value}
1147
+ `);
1148
+ }
1149
+ return;
1150
+ }
1151
+ this.stream.write(
1152
+ `${chalk.cyan(SPINNER_FRAMES[this.frameIndex])} ${this.value}${SAVE_CURSOR}`
1153
+ );
1154
+ this.interval = setInterval(() => this.renderFrame(), SPINNER_INTERVAL_MS);
1155
+ }
1156
+ succeed(text) {
1157
+ this.persist(chalk.green("\u2714"), text);
1158
+ }
1159
+ fail(text) {
1160
+ this.persist(chalk.red("\u2716"), text);
1161
+ }
1162
+ renderLine() {
1163
+ this.restoreAndClearLine();
1164
+ this.stream.write(`${chalk.cyan(SPINNER_FRAMES[this.frameIndex])} ${this.value}${SAVE_CURSOR}`);
1165
+ }
1166
+ renderFrame() {
1167
+ this.frameIndex = (this.frameIndex + 1) % SPINNER_FRAMES.length;
1168
+ this.stream.write(
1169
+ `${RESTORE_CURSOR}\r${chalk.cyan(SPINNER_FRAMES[this.frameIndex])}${RESTORE_CURSOR}`
1170
+ );
1171
+ }
1172
+ persist(symbol, text) {
1173
+ const finalText = text ?? this.value;
1174
+ this.stop();
1175
+ this.stream.write(`${symbol}${finalText ? ` ${finalText}` : ""}
1176
+ `);
1177
+ }
1178
+ stop() {
1179
+ if (!this.interval) {
1180
+ return false;
1181
+ }
1182
+ clearInterval(this.interval);
1183
+ this.interval = void 0;
1184
+ this.restoreAndClearLine();
1185
+ return true;
1186
+ }
1187
+ restoreAndClearLine() {
1188
+ this.stream.write(RESTORE_CURSOR);
1189
+ cursorTo(this.stream, 0);
1190
+ clearLine(this.stream, 0);
1191
+ }
1192
+ };
1193
+
1194
+ // src/commands/create.ts
1195
+ async function create(projectName, options = {}) {
1196
+ try {
1197
+ configureNetwork({
1198
+ forceDirect: options.proxy === false,
1199
+ proxy: typeof options.proxy === "string" ? options.proxy : void 0
1200
+ });
1201
+ const templateTiming = createTemplateTiming();
1202
+ const context = await createInitContext(projectName, options, templateTiming);
1203
+ const targetPath = path7.resolve(process.cwd(), context.projectName);
1204
+ if (!await prepareTargetDirectory(targetPath, context.projectName)) {
1205
+ return;
1206
+ }
1207
+ const install = await confirm({
1208
+ message: "\u662F\u5426\u81EA\u52A8\u5B89\u88C5\u4F9D\u8D56\uFF1F",
1209
+ default: true
1210
+ });
1211
+ await downloadAndGenerate(context, targetPath, templateTiming, options);
1212
+ await installDependencies(install, targetPath, context.packageManager);
1213
+ await initializeGit(targetPath);
1214
+ printNextSteps(context, install);
1215
+ } catch (error) {
1216
+ if (isUserCancellation(error)) {
1217
+ console.log(chalk2.yellow("\n\u5DF2\u53D6\u6D88\u64CD\u4F5C\u3002"));
1218
+ return;
1219
+ }
1220
+ console.error(chalk2.red(`\u521B\u5EFA\u9879\u76EE\u5931\u8D25\uFF1A${getErrorMessage3(error)}`));
1221
+ }
1222
+ }
1223
+ async function createInitContext(projectName, options, templateTiming) {
1224
+ const finalProjectName = await getProjectName(projectName);
1225
+ const templates = await loadTemplates(templateTiming, options);
1226
+ const template = await chooseTemplate(templates);
1227
+ return {
1228
+ projectName: finalProjectName,
1229
+ template,
1230
+ packageManager: resolvePackageManager(options.pm)
1231
+ };
1232
+ }
1233
+ async function getProjectName(projectName) {
1234
+ if (projectName?.trim()) {
1235
+ return projectName.trim();
1236
+ }
1237
+ return input({
1238
+ message: "\u8BF7\u8F93\u5165\u9879\u76EE\u540D\u79F0\uFF1A",
1239
+ validate: (value) => value.trim() ? true : "\u9879\u76EE\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A"
1240
+ });
1241
+ }
1242
+ async function loadTemplates(templateTiming, options) {
1243
+ const spinner = createStableSpinner();
1244
+ try {
1245
+ const config = await loadCliConfig();
1246
+ spinner.start("\u6B63\u5728\u83B7\u53D6\u8FDC\u7A0B\u6A21\u677F\u914D\u7F6E...");
1247
+ const result = await fetchTemplates(config.templateRegistry, {
1248
+ forceRefresh: options.refresh === true
1249
+ });
1250
+ assignFetchTiming(templateTiming, result);
1251
+ spinner.succeed("\u8FDC\u7A0B\u6A21\u677F\u914D\u7F6E\u83B7\u53D6\u6210\u529F");
1252
+ if (result.staleCache) {
1253
+ console.log(chalk2.yellow("\u8FDC\u7A0B\u6A21\u677F\u914D\u7F6E\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u5DF2\u4F7F\u7528\u672C\u5730\u7F13\u5B58\u3002"));
1254
+ }
1255
+ return result.templates;
1256
+ } catch (error) {
1257
+ if (spinner.isSpinning) {
1258
+ spinner.fail("\u83B7\u53D6\u8FDC\u7A0B\u6A21\u677F\u914D\u7F6E\u5931\u8D25");
1259
+ } else {
1260
+ console.error(chalk2.red("\u83B7\u53D6\u8FDC\u7A0B\u6A21\u677F\u914D\u7F6E\u5931\u8D25"));
1261
+ }
1262
+ const message = error instanceof Error && error.message ? error.message : "\u672A\u77E5\u9519\u8BEF";
1263
+ throw new Error(`\u83B7\u53D6\u8FDC\u7A0B\u6A21\u677F\u914D\u7F6E\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u6A21\u677F\u914D\u7F6E\u5730\u5740\u3002
1264
+ ${message}`);
1265
+ }
1266
+ }
1267
+ async function chooseTemplate(templates) {
1268
+ return select({
1269
+ message: "\u8BF7\u9009\u62E9\u9879\u76EE\u6A21\u677F\uFF1A",
1270
+ choices: templates.map((template) => ({
1271
+ name: `${template.label} (${template.name}) - ${template.description}`,
1272
+ value: template
1273
+ })),
1274
+ default: templates.find((template) => template.type === "micro") ?? templates[0]
1275
+ });
1276
+ }
1277
+ function resolvePackageManager(requestedPackageManager) {
1278
+ if (requestedPackageManager !== void 0) {
1279
+ if (!isPackageManager(requestedPackageManager)) {
1280
+ throw new Error(
1281
+ `\u4E0D\u652F\u6301\u7684 packageManager: ${requestedPackageManager}
1282
+ \u652F\u6301: npm\u3001pnpm\u3001yarn`
1283
+ );
1284
+ }
1285
+ return requestedPackageManager;
1286
+ }
1287
+ const packageManager = detectPackageManager();
1288
+ if (packageManager === "npm") {
1289
+ console.log(chalk2.yellow("\u672A\u68C0\u6D4B\u5230 pnpm \u6216 yarn\uFF0C\u81EA\u52A8\u4F7F\u7528 npm\u3002"));
1290
+ }
1291
+ return packageManager;
1292
+ }
1293
+ async function prepareTargetDirectory(targetPath, projectName) {
1294
+ if (!await fs6.pathExists(targetPath)) {
1295
+ return true;
1296
+ }
1297
+ const overwrite = await confirm({
1298
+ message: `\u76EE\u5F55 ${projectName} \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u8986\u76D6\uFF1F`,
1299
+ default: false
1300
+ });
1301
+ if (!overwrite) {
1302
+ console.log(chalk2.yellow("\u5DF2\u53D6\u6D88\u521B\u5EFA\u3002"));
1303
+ return false;
1304
+ }
1305
+ await fs6.remove(targetPath);
1306
+ return true;
1307
+ }
1308
+ async function downloadAndGenerate(context, targetPath, templateTiming, options) {
1309
+ const { template, projectName, packageManager } = context;
1310
+ const spinner = createStableSpinner();
1311
+ const downloadStartedAt = performance5.now();
1312
+ try {
1313
+ spinner.start(`\u6B63\u5728\u4E0B\u8F7D\u6A21\u677F ${template.label}...`);
1314
+ const downloadTiming = await downloadTemplate(template, targetPath, {
1315
+ forceRefresh: options.refresh === true
1316
+ });
1317
+ assignDownloadTiming(templateTiming, downloadTiming);
1318
+ } catch (error) {
1319
+ templateTiming.downloadMs = Math.round(performance5.now() - downloadStartedAt);
1320
+ await fs6.remove(targetPath);
1321
+ if (spinner.isSpinning) {
1322
+ spinner.fail("\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25");
1323
+ } else {
1324
+ console.error(chalk2.red("\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25"));
1325
+ }
1326
+ printTemplateTiming(templateTiming);
1327
+ const message = error instanceof Error && error.message ? error.message : "\u672A\u77E5\u9519\u8BEF";
1328
+ throw new Error(`\u6A21\u677F\u4E0B\u8F7D\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u4ED3\u5E93\u5730\u5740\u6216\u7F51\u7EDC\u8FDE\u63A5\u3002
1329
+ ${message}`);
1330
+ }
1331
+ try {
1332
+ spinner.text = "\u6B63\u5728\u66FF\u6362\u6A21\u677F\u53D8\u91CF...";
1333
+ const startedAt = performance5.now();
1334
+ await replaceTemplateVariables(targetPath, { projectName });
1335
+ await updateProjectPackageJson(targetPath, projectName, packageManager);
1336
+ templateTiming.renderMs = Math.round(performance5.now() - startedAt);
1337
+ spinner.succeed("\u6A21\u677F\u4E0B\u8F7D\u5E76\u751F\u6210\u5B8C\u6210");
1338
+ printTemplateTiming(templateTiming);
1339
+ } catch {
1340
+ await fs6.remove(targetPath);
1341
+ spinner.fail("\u6A21\u677F\u4E0B\u8F7D\u6216\u53D8\u91CF\u66FF\u6362\u5931\u8D25");
1342
+ throw new Error("\u6A21\u677F\u53D8\u91CF\u66FF\u6362\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u6A21\u677F\u6587\u4EF6\u5185\u5BB9\u3002");
1343
+ }
1344
+ }
1345
+ function createTemplateTiming() {
1346
+ return {
1347
+ configRoute: "direct",
1348
+ copyMs: 0,
1349
+ fetchConfigMs: 0,
1350
+ parseConfigMs: 0,
1351
+ templateRoute: "direct",
1352
+ downloadMs: 0,
1353
+ extractMs: 0,
1354
+ renderMs: 0
1355
+ };
1356
+ }
1357
+ function assignFetchTiming(templateTiming, result) {
1358
+ templateTiming.fetchConfigMs = result.fetchMs;
1359
+ templateTiming.parseConfigMs = result.parseMs;
1360
+ templateTiming.configRoute = result.route;
1361
+ }
1362
+ function assignDownloadTiming(templateTiming, downloadTiming) {
1363
+ templateTiming.copyMs = downloadTiming.copyMs;
1364
+ templateTiming.downloadMs = downloadTiming.downloadMs;
1365
+ templateTiming.extractMs = downloadTiming.extractMs;
1366
+ templateTiming.templateRoute = downloadTiming.route;
1367
+ }
1368
+ function printTemplateTiming(templateTiming) {
1369
+ console.log("\n[template]");
1370
+ console.log(`fetch config: ${templateTiming.fetchConfigMs} ms (${formatRoute(templateTiming.configRoute)})`);
1371
+ console.log(`parse config: ${templateTiming.parseConfigMs} ms`);
1372
+ console.log(`download: ${templateTiming.downloadMs} ms (${formatRoute(templateTiming.templateRoute)})`);
1373
+ console.log(`extract: ${templateTiming.extractMs} ms`);
1374
+ console.log(`copy: ${templateTiming.copyMs} ms`);
1375
+ console.log(`render: ${templateTiming.renderMs} ms`);
1376
+ }
1377
+ function formatRoute(route) {
1378
+ if (route === "proxy-environment") {
1379
+ return "remote, \u73AF\u5883\u53D8\u91CF\u4EE3\u7406";
1380
+ }
1381
+ if (route === "proxy-system") {
1382
+ return "remote, \u7CFB\u7EDF\u4EE3\u7406";
1383
+ }
1384
+ if (route === "direct") {
1385
+ return "remote, \u76F4\u8FDE";
1386
+ }
1387
+ if (route === "ssh") {
1388
+ return "remote, Git/SSH";
1389
+ }
1390
+ return route.replace("-", " ");
1391
+ }
1392
+ async function installDependencies(install, targetPath, packageManager) {
1393
+ if (!install) {
1394
+ return;
1395
+ }
1396
+ const installCommand = packageManager === "yarn" ? [] : ["install"];
1397
+ const spinner = ora(`\u6B63\u5728\u5B89\u88C5\u4F9D\u8D56 (${packageManager} ${installCommand.join(" ")})...`).start();
1398
+ try {
1399
+ await execa2(packageManager, installCommand, { cwd: targetPath });
1400
+ spinner.succeed("\u4F9D\u8D56\u5B89\u88C5\u6210\u529F");
1401
+ } catch {
1402
+ spinner.warn("\u4F9D\u8D56\u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u8FDB\u5165\u9879\u76EE\u76EE\u5F55\u540E\u624B\u52A8\u5B89\u88C5");
1403
+ }
1404
+ }
1405
+ async function initializeGit(targetPath) {
1406
+ const spinner = ora("\u6B63\u5728\u521D\u59CB\u5316 Git \u4ED3\u5E93...").start();
1407
+ try {
1408
+ await execa2("git", ["init"], { cwd: targetPath });
1409
+ spinner.succeed("Git \u4ED3\u5E93\u521D\u59CB\u5316\u6210\u529F");
1410
+ } catch {
1411
+ spinner.warn("Git \u4ED3\u5E93\u521D\u59CB\u5316\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4 Git \u5DF2\u5B89\u88C5");
1412
+ }
1413
+ }
1414
+ function printNextSteps(context, installed) {
1415
+ const { projectName, packageManager } = context;
1416
+ const installCommand = packageManager === "yarn" ? "yarn" : `${packageManager} install`;
1417
+ const devCommand = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
1418
+ console.log(chalk2.green(`
1419
+ \u9879\u76EE ${projectName} \u521B\u5EFA\u6210\u529F\uFF01`));
1420
+ console.log("\n\u63A5\u4E0B\u6765\u4F60\u53EF\u4EE5\uFF1A\n");
1421
+ console.log(chalk2.cyan(` cd ${projectName}`));
1422
+ if (!installed) {
1423
+ console.log(chalk2.cyan(` ${installCommand}`));
1424
+ }
1425
+ console.log(chalk2.cyan(` ${devCommand}`));
1426
+ }
1427
+ function isUserCancellation(error) {
1428
+ return error instanceof Error && /User force closed|User cancelled/i.test(error.message);
1429
+ }
1430
+ function getErrorMessage3(error) {
1431
+ return error instanceof Error && error.message ? ` ${error.message}` : "";
1432
+ }
1433
+
1434
+ // src/pre/index.ts
1435
+ import chalk3 from "chalk";
1436
+ function checkNodeVersion() {
1437
+ const currentVersion = process.version;
1438
+ const version = currentVersion.replace(/^v/, "");
1439
+ const [major] = version.split(".").map(Number);
1440
+ if (major < 22) {
1441
+ console.error(chalk3.red(`
1442
+ \u274C \u9519\u8BEF\uFF1A\u5F53\u524D Node.js \u7248\u672C (${currentVersion}) \u8FC7\u4F4E\uFF01`));
1443
+ console.error(chalk3.yellow("\u26A0\uFE0F \u8BF7\u5347\u7EA7 Node.js \u7248\u672C\uFF0C\u4E0D\u4F4E\u4E8E v22.19.0"));
1444
+ process.exit(1);
1445
+ }
1446
+ }
1447
+
1448
+ // src/index.ts
1449
+ checkNodeVersion();
1450
+ var program = new Command();
1451
+ var require2 = createRequire(import.meta.url);
1452
+ var packageMetadata = require2("../package.json");
1453
+ var packageVersion = getPackageVersion(packageMetadata);
1454
+ program.name("gz-cli-front").description("\u524D\u7AEF\u811A\u624B\u67B6\u5DE5\u5177").version(packageVersion);
1455
+ program.command("init [project-name]", { isDefault: true }).description("\u521B\u5EFA\u4E00\u4E2A\u65B0\u9879\u76EE").option("--pm <package-manager>", "\u6307\u5B9A\u5305\u7BA1\u7406\u5668\uFF1Anpm\u3001pnpm \u6216 yarn").option("--proxy <url>", "\u6307\u5B9A\u672C\u6B21\u521D\u59CB\u5316\u4F7F\u7528\u7684 HTTP(S) \u4EE3\u7406").option("--no-proxy", "\u5F3A\u5236\u76F4\u8FDE\uFF0C\u4E0D\u8BFB\u53D6\u73AF\u5883\u53D8\u91CF\u6216\u7CFB\u7EDF\u4EE3\u7406").option("--refresh", "\u5FFD\u7565\u6A21\u677F\u914D\u7F6E\u548C\u6A21\u677F\u7F13\u5B58\uFF0C\u5F3A\u5236\u4ECE\u8FDC\u7A0B\u4ED3\u5E93\u5237\u65B0").action((projectName, options) => {
1456
+ void create(projectName, options);
1457
+ });
1458
+ program.command("create [project-name]").description("\u521B\u5EFA\u4E00\u4E2A\u65B0\u9879\u76EE\uFF08\u517C\u5BB9\u65E7\u547D\u4EE4\uFF09").option("--pm <package-manager>", "\u6307\u5B9A\u5305\u7BA1\u7406\u5668\uFF1Anpm\u3001pnpm \u6216 yarn").option("--proxy <url>", "\u6307\u5B9A\u672C\u6B21\u521D\u59CB\u5316\u4F7F\u7528\u7684 HTTP(S) \u4EE3\u7406").option("--no-proxy", "\u5F3A\u5236\u76F4\u8FDE\uFF0C\u4E0D\u8BFB\u53D6\u73AF\u5883\u53D8\u91CF\u6216\u7CFB\u7EDF\u4EE3\u7406").option("--refresh", "\u5FFD\u7565\u6A21\u677F\u914D\u7F6E\u548C\u6A21\u677F\u7F13\u5B58\uFF0C\u5F3A\u5236\u4ECE\u8FDC\u7A0B\u4ED3\u5E93\u5237\u65B0").action((projectName, options) => {
1459
+ void create(projectName, options);
1460
+ });
1461
+ program.parse(process.argv);
1462
+ function getPackageVersion(value) {
1463
+ if (typeof value !== "object" || value === null) {
1464
+ throw new Error("\u65E0\u6CD5\u8BFB\u53D6 gz-cli-front \u7248\u672C\u53F7\u3002");
1465
+ }
1466
+ const version = value.version;
1467
+ if (typeof version !== "string" || !version.trim()) {
1468
+ throw new Error("gz-cli-front package.json \u4E2D\u7F3A\u5C11\u6709\u6548\u7248\u672C\u53F7\u3002");
1469
+ }
1470
+ return version;
1471
+ }
1472
+ //# sourceMappingURL=index.js.map