cf-yoyo 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.
Files changed (141) hide show
  1. package/.eslintrc.json +28 -0
  2. package/.github/workflows/ci.yml +96 -0
  3. package/.prettierrc.json +10 -0
  4. package/CHANGELOG.md +55 -0
  5. package/README.md +138 -0
  6. package/__tests__/cli-e2e.test.ts +145 -0
  7. package/__tests__/config.test.ts +268 -0
  8. package/__tests__/filesystem.test.ts +453 -0
  9. package/__tests__/logger.test.ts +274 -0
  10. package/__tests__/template-engine.test.ts +450 -0
  11. package/__tests__/types.test.ts +25 -0
  12. package/deep_todos.md +766 -0
  13. package/dist/cli/commands/create.d.ts +26 -0
  14. package/dist/cli/commands/create.d.ts.map +1 -0
  15. package/dist/cli/commands/create.js +308 -0
  16. package/dist/cli/commands/create.js.map +1 -0
  17. package/dist/cli/commands/git.d.ts +10 -0
  18. package/dist/cli/commands/git.d.ts.map +1 -0
  19. package/dist/cli/commands/git.js +887 -0
  20. package/dist/cli/commands/git.js.map +1 -0
  21. package/dist/cli/commands/list.d.ts +10 -0
  22. package/dist/cli/commands/list.d.ts.map +1 -0
  23. package/dist/cli/commands/list.js +90 -0
  24. package/dist/cli/commands/list.js.map +1 -0
  25. package/dist/cli/index.d.ts +15 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +62 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/core/config.d.ts +35 -0
  30. package/dist/core/config.d.ts.map +1 -0
  31. package/dist/core/config.js +260 -0
  32. package/dist/core/config.js.map +1 -0
  33. package/dist/core/filesystem.d.ts +84 -0
  34. package/dist/core/filesystem.d.ts.map +1 -0
  35. package/dist/core/filesystem.js +417 -0
  36. package/dist/core/filesystem.js.map +1 -0
  37. package/dist/core/git-token.d.ts +81 -0
  38. package/dist/core/git-token.d.ts.map +1 -0
  39. package/dist/core/git-token.js +244 -0
  40. package/dist/core/git-token.js.map +1 -0
  41. package/dist/core/git.d.ts +70 -0
  42. package/dist/core/git.d.ts.map +1 -0
  43. package/dist/core/git.js +367 -0
  44. package/dist/core/git.js.map +1 -0
  45. package/dist/core/prompt.d.ts +28 -0
  46. package/dist/core/prompt.d.ts.map +1 -0
  47. package/dist/core/prompt.js +253 -0
  48. package/dist/core/prompt.js.map +1 -0
  49. package/dist/core/template-engine.d.ts +52 -0
  50. package/dist/core/template-engine.d.ts.map +1 -0
  51. package/dist/core/template-engine.js +308 -0
  52. package/dist/core/template-engine.js.map +1 -0
  53. package/dist/core/template-manager.d.ts +54 -0
  54. package/dist/core/template-manager.d.ts.map +1 -0
  55. package/dist/core/template-manager.js +330 -0
  56. package/dist/core/template-manager.js.map +1 -0
  57. package/dist/index.d.ts +12 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +19 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/types/index.d.ts +244 -0
  62. package/dist/types/index.d.ts.map +1 -0
  63. package/dist/types/index.js +51 -0
  64. package/dist/types/index.js.map +1 -0
  65. package/dist/utils/logger.d.ts +68 -0
  66. package/dist/utils/logger.d.ts.map +1 -0
  67. package/dist/utils/logger.js +140 -0
  68. package/dist/utils/logger.js.map +1 -0
  69. package/memory.md +241 -0
  70. package/need-debug.md +395 -0
  71. package/package.json +42 -0
  72. package/src/cli/commands/create.ts +326 -0
  73. package/src/cli/commands/git.ts +1001 -0
  74. package/src/cli/commands/list.ts +97 -0
  75. package/src/cli/index.ts +71 -0
  76. package/src/core/config.ts +262 -0
  77. package/src/core/filesystem.ts +408 -0
  78. package/src/core/git-token.ts +248 -0
  79. package/src/core/git.ts +384 -0
  80. package/src/core/prompt.ts +345 -0
  81. package/src/core/template-engine.ts +324 -0
  82. package/src/core/template-manager.ts +338 -0
  83. package/src/index.ts +19 -0
  84. package/src/types/index.ts +259 -0
  85. package/src/utils/logger.ts +150 -0
  86. package/templates/pages/basic/README.md.mustache +63 -0
  87. package/templates/pages/basic/package.json.mustache +23 -0
  88. package/templates/pages/basic/public/css/style.css +199 -0
  89. package/templates/pages/basic/public/index.html.mustache +72 -0
  90. package/templates/pages/basic/public/js/main.js +103 -0
  91. package/templates/pages/basic/template.json +38 -0
  92. package/templates/pages/basic/tsconfig.json +21 -0
  93. package/templates/pages/basic/wrangler.toml.mustache +14 -0
  94. package/templates/pages/basic-js/README.md.mustache +62 -0
  95. package/templates/pages/basic-js/package.json.mustache +25 -0
  96. package/templates/pages/basic-js/public/css/style.css +212 -0
  97. package/templates/pages/basic-js/public/index.html.mustache +53 -0
  98. package/templates/pages/basic-js/public/js/main.js +134 -0
  99. package/templates/pages/basic-js/template.json +35 -0
  100. package/templates/pages/basic-js/wrangler.toml.mustache +14 -0
  101. package/templates/pages/react/README.md.mustache +97 -0
  102. package/templates/pages/react/index.html.mustache +14 -0
  103. package/templates/pages/react/package.json.mustache +34 -0
  104. package/templates/pages/react/src/App.css +168 -0
  105. package/templates/pages/react/src/App.tsx.mustache +62 -0
  106. package/templates/pages/react/src/index.css +53 -0
  107. package/templates/pages/react/src/main.tsx.mustache +10 -0
  108. package/templates/pages/react/src/vite-env.d.ts +1 -0
  109. package/templates/pages/react/template.json +54 -0
  110. package/templates/pages/react/tsconfig.json +21 -0
  111. package/templates/pages/react/tsconfig.node.json +10 -0
  112. package/templates/pages/react/vite.config.ts +16 -0
  113. package/templates/worker/basic/README.md.mustache +56 -0
  114. package/templates/worker/basic/package.json.mustache +29 -0
  115. package/templates/worker/basic/src/index.ts.mustache +125 -0
  116. package/templates/worker/basic/template.json +30 -0
  117. package/templates/worker/basic/tsconfig.json +24 -0
  118. package/templates/worker/basic/wrangler.toml.mustache +33 -0
  119. package/templates/worker/basic-js/README.md.mustache +55 -0
  120. package/templates/worker/basic-js/package.json.mustache +25 -0
  121. package/templates/worker/basic-js/src/index.js.mustache +146 -0
  122. package/templates/worker/basic-js/template.json +27 -0
  123. package/templates/worker/basic-js/wrangler.toml.mustache +33 -0
  124. package/templates/worker/hono/README.md.mustache +79 -0
  125. package/templates/worker/hono/package.json.mustache +33 -0
  126. package/templates/worker/hono/src/index.ts.mustache +64 -0
  127. package/templates/worker/hono/src/routes/index.ts.mustache +165 -0
  128. package/templates/worker/hono/template.json +34 -0
  129. package/templates/worker/hono/tsconfig.json +24 -0
  130. package/templates/worker/hono/wrangler.toml.mustache +36 -0
  131. package/templates/worker/hono-js/README.md.mustache +67 -0
  132. package/templates/worker/hono-js/package.json.mustache +29 -0
  133. package/templates/worker/hono-js/src/index.js.mustache +55 -0
  134. package/templates/worker/hono-js/src/routes/index.js.mustache +127 -0
  135. package/templates/worker/hono-js/template.json +31 -0
  136. package/templates/worker/hono-js/wrangler.toml.mustache +36 -0
  137. package/thoughts/ledgers/CONTINUITY_ses_287e.md +74 -0
  138. package/thoughts/ledgers/CONTINUITY_ses_28b5.md +85 -0
  139. package/tsconfig.json +30 -0
  140. package/vitest.config.ts +20 -0
  141. package//351/240/205/347/233/256/350/241/250.md +140 -0
@@ -0,0 +1,887 @@
1
+ "use strict";
2
+ /**
3
+ * Git 命令模組
4
+ * 提供子命令(init/push/clone)和互動式選單
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.createGitCommand = createGitCommand;
41
+ const logger_1 = require("../../utils/logger");
42
+ const git_1 = require("../../core/git");
43
+ const git_token_1 = require("../../core/git-token");
44
+ const execa_1 = require("execa");
45
+ // Git 操作類型
46
+ var GitOperationType;
47
+ (function (GitOperationType) {
48
+ GitOperationType["INIT"] = "init";
49
+ GitOperationType["PUSH"] = "push";
50
+ GitOperationType["CLONE"] = "clone";
51
+ GitOperationType["MANAGE_TOKENS"] = "manage-tokens";
52
+ GitOperationType["REMOVE_REPO"] = "remove-repo";
53
+ })(GitOperationType || (GitOperationType = {}));
54
+ // 操作選項定義
55
+ const operationChoices = [
56
+ { name: '初始化 Git 倉庫', value: GitOperationType.INIT },
57
+ { name: '推送到遠端倉庫', value: GitOperationType.PUSH },
58
+ { name: '克隆遠端倉庫', value: GitOperationType.CLONE },
59
+ { name: '管理 Git Token', value: GitOperationType.MANAGE_TOKENS },
60
+ { name: '取消 Git 倉庫', value: GitOperationType.REMOVE_REPO },
61
+ { name: '❌ 退出', value: 'exit' },
62
+ ];
63
+ /**
64
+ * Git 命令主處理函數
65
+ * 透過互動式問答選擇操作類型,然後執行對應功能
66
+ */
67
+ async function gitCommandHandler() {
68
+ let running = true;
69
+ while (running) {
70
+ try {
71
+ logger_1.logger.empty();
72
+ logger_1.logger.icon('🔧', 'Git 操作選單');
73
+ logger_1.logger.divider();
74
+ logger_1.logger.empty();
75
+ // 第一步:詢問操作類型
76
+ const { operation } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
77
+ {
78
+ type: 'rawlist',
79
+ name: 'operation',
80
+ message: '請選擇要執行的 Git 操作:',
81
+ choices: operationChoices,
82
+ },
83
+ ]));
84
+ // 根據選擇的操作執行對應功能
85
+ switch (operation) {
86
+ case GitOperationType.INIT:
87
+ await handleGitInit();
88
+ break;
89
+ case GitOperationType.PUSH:
90
+ await handleGitPush();
91
+ break;
92
+ case GitOperationType.CLONE:
93
+ await handleGitClone();
94
+ break;
95
+ case GitOperationType.MANAGE_TOKENS:
96
+ await handleManageGitToken();
97
+ break;
98
+ case GitOperationType.REMOVE_REPO:
99
+ await handleRemoveGitRepo();
100
+ break;
101
+ case 'exit':
102
+ logger_1.logger.info('再見!');
103
+ running = false;
104
+ break;
105
+ default:
106
+ logger_1.logger.error('無效的操作類型');
107
+ }
108
+ }
109
+ catch (error) {
110
+ const errorMessage = error instanceof Error ? error.message : String(error);
111
+ logger_1.logger.error(`Git 操作失敗:${errorMessage}`);
112
+ running = false;
113
+ }
114
+ }
115
+ }
116
+ /**
117
+ * 處理 Git 初始化操作
118
+ */
119
+ async function handleGitInit() {
120
+ logger_1.logger.empty();
121
+ logger_1.logger.icon('🔧', '初始化 Git 倉庫');
122
+ logger_1.logger.divider();
123
+ logger_1.logger.empty();
124
+ // 檢查是否已為 Git 倉庫
125
+ const alreadyGit = await (0, git_1.isGitRepository)();
126
+ if (alreadyGit) {
127
+ logger_1.logger.warn('此目錄已經是 Git 倉庫');
128
+ return;
129
+ }
130
+ // 執行 git init
131
+ logger_1.logger.info('正在執行 git init...');
132
+ const initResult = await (0, git_1.gitInit)();
133
+ if (!initResult.success) {
134
+ logger_1.logger.error(`Git 初始化失敗:${initResult.message}`);
135
+ process.exit(1);
136
+ }
137
+ logger_1.logger.success('✓ Git 倉庫初始化成功');
138
+ // 建立 .gitignore(若不存在)
139
+ const gitignoreCreated = (0, git_1.createGitignore)();
140
+ if (gitignoreCreated) {
141
+ logger_1.logger.success('✓ 已建立 .gitignore 檔案');
142
+ }
143
+ else {
144
+ logger_1.logger.info('ℹ️ .gitignore 檔案已存在');
145
+ }
146
+ // 詢問是否進行初始提交
147
+ const { confirm } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
148
+ {
149
+ type: 'confirm',
150
+ name: 'confirm',
151
+ message: '是否進行初始提交?',
152
+ default: true,
153
+ },
154
+ ]));
155
+ if (confirm) {
156
+ logger_1.logger.info('正在執行 git add...');
157
+ const commitResult = await (0, git_1.gitCommit)('Initial commit');
158
+ if (commitResult.success) {
159
+ logger_1.logger.success('✓ 初始提交完成');
160
+ }
161
+ else {
162
+ logger_1.logger.warn(`初始提交失敗:${commitResult.message}`);
163
+ }
164
+ }
165
+ logger_1.logger.empty();
166
+ logger_1.logger.success('✅ Git 初始化完成!');
167
+ logger_1.logger.empty();
168
+ }
169
+ /**
170
+ * 選擇並執行推送用的 Token
171
+ *
172
+ * 選擇策略說明:
173
+ * 1. 若無 Token:直接推送,不附加認證
174
+ * 2. 若有 1 個 Token:自動使用第一個 Token 進行推送
175
+ * 3. 若有 2+ 個 Token:進入互動選擇流程,讓用戶選擇要使用的 Token
176
+ *
177
+ * @param remoteName - 遠端倉庫名稱(通常為 'origin')
178
+ * @param branch - 分支名稱
179
+ * @returns GitResult - 推送操作的結果,包含成功狀態和輸出訊息
180
+ */
181
+ async function selectAndExecutePushToken(remoteName, branch) {
182
+ const tokens = (0, git_token_1.loadTokens)();
183
+ let pushResult;
184
+ // 根據 Token 數量決定選擇策略
185
+ const strategy = tokens.tokens.length === 0 ? 'none' : tokens.tokens.length === 1 ? 'first' : 'interactive';
186
+ switch (strategy) {
187
+ case 'none': {
188
+ // 無 Token:使用一般推送
189
+ logger_1.logger.info(`正在推送到 ${remoteName}/${branch}...`);
190
+ pushResult = await (0, git_1.gitPush)(remoteName, branch);
191
+ break;
192
+ }
193
+ case 'first': {
194
+ // 單一 Token:自動使用第一個
195
+ const firstToken = tokens.tokens[0];
196
+ if (firstToken) {
197
+ logger_1.logger.info(`正在使用 Token「${firstToken.name}」推送到 ${remoteName}/${branch}...`);
198
+ pushResult = await (0, git_1.gitPush)(remoteName, branch, undefined, firstToken.token);
199
+ }
200
+ else {
201
+ // 防禦性檢查:理論上不會進入此分支
202
+ logger_1.logger.warn('Token 列表異常,使用一般推送');
203
+ pushResult = await (0, git_1.gitPush)(remoteName, branch);
204
+ }
205
+ break;
206
+ }
207
+ case 'interactive': {
208
+ // 多個 Token:讓用戶互動選擇
209
+ const { selectedTokenId } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
210
+ {
211
+ type: 'list',
212
+ name: 'selectedTokenId',
213
+ message: '選擇要使用的 Git Token:',
214
+ choices: [
215
+ ...tokens.tokens.map((tokenItem) => ({
216
+ name: tokenItem.name,
217
+ value: tokenItem.id,
218
+ })),
219
+ {
220
+ name: '不使用 Token(一般推送)',
221
+ value: 'none',
222
+ },
223
+ ],
224
+ },
225
+ ]));
226
+ if (selectedTokenId === 'none') {
227
+ logger_1.logger.info(`正在推送到 ${remoteName}/${branch}...`);
228
+ pushResult = await (0, git_1.gitPush)(remoteName, branch);
229
+ }
230
+ else {
231
+ const selectedToken = tokens.tokens.find((t) => t.id === selectedTokenId);
232
+ if (selectedToken) {
233
+ logger_1.logger.info(`正在使用 Token「${selectedToken.name}」推送到 ${remoteName}/${branch}...`);
234
+ pushResult = await (0, git_1.gitPush)(remoteName, branch, undefined, selectedToken.token);
235
+ }
236
+ else {
237
+ // 防禦性檢查:選擇的 Token 不存在
238
+ logger_1.logger.warn('選擇的 Token 不存在,使用一般推送');
239
+ pushResult = await (0, git_1.gitPush)(remoteName, branch);
240
+ }
241
+ }
242
+ break;
243
+ }
244
+ default: {
245
+ // 防禦性預設:使用一般推送
246
+ logger_1.logger.info(`正在推送到 ${remoteName}/${branch}...`);
247
+ pushResult = await (0, git_1.gitPush)(remoteName, branch);
248
+ }
249
+ }
250
+ return pushResult;
251
+ }
252
+ /**
253
+ * 處理 Git 推送操作
254
+ */
255
+ async function handleGitPush() {
256
+ logger_1.logger.empty();
257
+ logger_1.logger.icon('📤', '推送到遠端倉庫');
258
+ logger_1.logger.divider();
259
+ logger_1.logger.empty();
260
+ // 檢查是否為 Git 倉庫
261
+ const isGit = await (0, git_1.isGitRepository)();
262
+ if (!isGit) {
263
+ logger_1.logger.error('此目錄不是 Git 倉庫,請先執行 git init');
264
+ process.exit(1);
265
+ }
266
+ // 獲取當前分支
267
+ const currentBranch = await (0, git_1.getCurrentBranch)();
268
+ if (!currentBranch) {
269
+ logger_1.logger.error('無法獲取當前分支');
270
+ process.exit(1);
271
+ }
272
+ // 檢查是否有未提交的更改
273
+ const hasUncommittedChanges = await checkForUncommittedChanges();
274
+ if (hasUncommittedChanges) {
275
+ logger_1.logger.info('檢測到有未提交的更改,正在自動提交...');
276
+ const commitResult = await (0, git_1.gitCommit)('chore: auto-commit before push');
277
+ if (commitResult.success) {
278
+ logger_1.logger.success('✓ 已自動提交更改');
279
+ }
280
+ else {
281
+ logger_1.logger.error(`自動提交失敗:${commitResult.message}`);
282
+ process.exit(1);
283
+ }
284
+ }
285
+ // 獲取 remotes
286
+ const remotes = await (0, git_1.getRemotes)();
287
+ let remoteName = 'origin';
288
+ // 如果沒有 remotes,詢問遠端 URL
289
+ if (remotes.length === 0 || !remotes.includes(remoteName)) {
290
+ const { remoteUrl } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
291
+ {
292
+ type: 'input',
293
+ name: 'remoteUrl',
294
+ message: '請輸入遠端倉庫 URL:',
295
+ default: '',
296
+ validate: (input) => {
297
+ if (!input.trim()) {
298
+ return '遠端 URL 不能為空';
299
+ }
300
+ // 簡單驗證 URL 格式
301
+ const urlPattern = /^(https?:\/\/|git@|ssh:\/\/)/;
302
+ if (!urlPattern.test(input.trim())) {
303
+ return '請輸入有效的 Git URL(以 https://、git@ 或 ssh:// 開頭)';
304
+ }
305
+ return true;
306
+ },
307
+ },
308
+ ]));
309
+ // 添加 remote
310
+ logger_1.logger.info(`正在添加 remote "${remoteName}"...`);
311
+ const addRemoteResult = await (0, git_1.gitAddRemote)(remoteName, remoteUrl);
312
+ if (!addRemoteResult.success) {
313
+ logger_1.logger.error(`添加 remote 失敗:${addRemoteResult.message}`);
314
+ process.exit(1);
315
+ }
316
+ logger_1.logger.success(`✓ 已添加 remote "${remoteName}"`);
317
+ }
318
+ // 詢問分支名稱
319
+ const { branch } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
320
+ {
321
+ type: 'input',
322
+ name: 'branch',
323
+ message: '要推送到哪個分支?',
324
+ default: currentBranch,
325
+ validate: (input) => {
326
+ if (!input.trim()) {
327
+ return '分支名稱不能為空';
328
+ }
329
+ return true;
330
+ },
331
+ },
332
+ ]));
333
+ // 確認推送
334
+ const { confirm } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
335
+ {
336
+ type: 'confirm',
337
+ name: 'confirm',
338
+ message: `確認推送到 ${remoteName}/${branch}?`,
339
+ default: true,
340
+ },
341
+ ]));
342
+ if (!confirm) {
343
+ logger_1.logger.info('已取消推送');
344
+ return;
345
+ }
346
+ // 選擇並執行推送用的 Token
347
+ const pushResult = await selectAndExecutePushToken(remoteName, branch);
348
+ // 調試:輸出推送結果
349
+ logger_1.logger.info(`推送結果:success=${pushResult.success}, message=${pushResult.message}`);
350
+ if (pushResult.output) {
351
+ logger_1.logger.info(`推送輸出:${pushResult.output}`);
352
+ }
353
+ if (!pushResult.success) {
354
+ // 檢查是否為認證錯誤且尚未使用 Token
355
+ const isAuthError = pushResult.output?.includes('Authentication failed') ||
356
+ pushResult.output?.includes('could not read Username') ||
357
+ pushResult.output?.includes('fatal: Authentication failed');
358
+ if (isAuthError) {
359
+ // 沒有 Token 且為認證錯誤,嘗試使用 Token 認證
360
+ logger_1.logger.warn('檢測到認證錯誤,嘗試使用 Git Token...');
361
+ const tokenUsed = await handleGitTokenAuth(remoteName, branch);
362
+ if (tokenUsed) {
363
+ return; // Token 認證後重新推送成功
364
+ }
365
+ }
366
+ logger_1.logger.error(`推送失敗:${pushResult.message}`);
367
+ if (pushResult.output) {
368
+ logger_1.logger.info(pushResult.output);
369
+ }
370
+ process.exit(1);
371
+ }
372
+ logger_1.logger.success('✓ 推送成功');
373
+ logger_1.logger.empty();
374
+ logger_1.logger.success('✅ Git push 完成!');
375
+ logger_1.logger.empty();
376
+ }
377
+ /**
378
+ * 處理 Git Token 認證
379
+ * @param remote remote 名稱
380
+ * @param branch 分支名稱
381
+ * @returns 是否成功使用 Token 認證
382
+ */
383
+ async function handleGitTokenAuth(remote, branch) {
384
+ const tokens = (0, git_token_1.loadTokens)();
385
+ // 若無 Token
386
+ if (tokens.tokens.length === 0) {
387
+ logger_1.logger.info('尚未儲存任何 Git Token');
388
+ const { token, name } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
389
+ {
390
+ type: 'input',
391
+ name: 'name',
392
+ message: '為 Token 設定名稱:',
393
+ default: 'GitHub 個人',
394
+ validate: (input) => {
395
+ if (!input.trim()) {
396
+ return '名稱不能為空';
397
+ }
398
+ return true;
399
+ },
400
+ },
401
+ {
402
+ type: 'password',
403
+ name: 'token',
404
+ message: '請輸入 Git Token:',
405
+ validate: (input) => {
406
+ if (!input.trim()) {
407
+ return 'Token 不能為空';
408
+ }
409
+ return true;
410
+ },
411
+ },
412
+ ]));
413
+ const newToken = (0, git_token_1.addToken)(name, token);
414
+ logger_1.logger.success('✓ Token 已保存');
415
+ // 重新推送(使用 Token)
416
+ logger_1.logger.info('正在使用新 Token 重新推送...');
417
+ const pushResult = await (0, git_1.gitPush)(remote, branch, undefined, newToken.token);
418
+ if (!pushResult.success) {
419
+ logger_1.logger.error(`推送失敗:${pushResult.message}`);
420
+ if (pushResult.output) {
421
+ logger_1.logger.info(pushResult.output);
422
+ }
423
+ return false;
424
+ }
425
+ logger_1.logger.success('✓ 推送成功');
426
+ return true;
427
+ }
428
+ // 若只有 1 個 Token
429
+ if (tokens.tokens.length === 1) {
430
+ const firstToken = tokens.tokens[0];
431
+ if (!firstToken) {
432
+ return false;
433
+ }
434
+ const { useExisting } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
435
+ {
436
+ type: 'confirm',
437
+ name: 'useExisting',
438
+ message: `是否使用已儲存的 Token「${firstToken.name}」?`,
439
+ default: true,
440
+ },
441
+ ]));
442
+ if (useExisting) {
443
+ // 使用已存在的 Token 重新推送
444
+ logger_1.logger.info('正在使用已儲存的 Token 重新推送...');
445
+ const pushResult = await (0, git_1.gitPush)(remote, branch, undefined, firstToken.token);
446
+ if (!pushResult.success) {
447
+ logger_1.logger.error(`推送失敗:${pushResult.message}`);
448
+ if (pushResult.output) {
449
+ logger_1.logger.info(pushResult.output);
450
+ }
451
+ return false;
452
+ }
453
+ logger_1.logger.success('✓ 推送成功');
454
+ return true;
455
+ }
456
+ else {
457
+ // 不使用原 Token,詢問新 Token
458
+ const { token, name } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
459
+ {
460
+ type: 'input',
461
+ name: 'name',
462
+ message: '為 Token 設定名稱:',
463
+ default: 'GitHub 個人',
464
+ validate: (input) => {
465
+ if (!input.trim()) {
466
+ return '名稱不能為空';
467
+ }
468
+ return true;
469
+ },
470
+ },
471
+ {
472
+ type: 'password',
473
+ name: 'token',
474
+ message: '請輸入 Git Token:',
475
+ validate: (input) => {
476
+ if (!input.trim()) {
477
+ return 'Token 不能為空';
478
+ }
479
+ return true;
480
+ },
481
+ },
482
+ ]));
483
+ const newToken = (0, git_token_1.addToken)(name, token);
484
+ logger_1.logger.success('✓ Token 已保存');
485
+ // 重新推送
486
+ logger_1.logger.info('正在使用新 Token 重新推送...');
487
+ const pushResult = await (0, git_1.gitPush)(remote, branch, undefined, newToken.token);
488
+ if (!pushResult.success) {
489
+ logger_1.logger.error(`推送失敗:${pushResult.message}`);
490
+ if (pushResult.output) {
491
+ logger_1.logger.info(pushResult.output);
492
+ }
493
+ return false;
494
+ }
495
+ logger_1.logger.success('✓ 推送成功');
496
+ return true;
497
+ }
498
+ }
499
+ // 若有多個 Token,顯示列表讓用戶選擇
500
+ const tokenList = (0, git_token_1.getTokenList)();
501
+ const { selectedTokenId } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
502
+ {
503
+ type: 'rawlist',
504
+ name: 'selectedTokenId',
505
+ message: '選擇要使用的 Git Token:',
506
+ choices: [
507
+ ...tokenList.map((t) => ({
508
+ name: t.name,
509
+ value: t.id,
510
+ })),
511
+ {
512
+ name: '➕ 使用新 Token',
513
+ value: 'new',
514
+ },
515
+ {
516
+ name: '❌ 退出',
517
+ value: 'exit',
518
+ },
519
+ ],
520
+ },
521
+ ]));
522
+ if (selectedTokenId === 'exit') {
523
+ logger_1.logger.info('已取消推送');
524
+ return false;
525
+ }
526
+ if (selectedTokenId === 'new') {
527
+ // 使用新 Token
528
+ const { token, name } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
529
+ {
530
+ type: 'input',
531
+ name: 'name',
532
+ message: '為 Token 設定名稱:',
533
+ default: 'GitHub 個人',
534
+ validate: (input) => {
535
+ if (!input.trim()) {
536
+ return '名稱不能為空';
537
+ }
538
+ return true;
539
+ },
540
+ },
541
+ {
542
+ type: 'password',
543
+ name: 'token',
544
+ message: '請輸入 Git Token:',
545
+ validate: (input) => {
546
+ if (!input.trim()) {
547
+ return 'Token 不能為空';
548
+ }
549
+ return true;
550
+ },
551
+ },
552
+ ]));
553
+ const newToken = (0, git_token_1.addToken)(name, token);
554
+ logger_1.logger.success('✓ Token 已保存');
555
+ // 重新推送
556
+ logger_1.logger.info('正在使用新 Token 重新推送...');
557
+ const pushResult = await (0, git_1.gitPush)(remote, branch, undefined, newToken.token);
558
+ if (!pushResult.success) {
559
+ logger_1.logger.error(`推送失敗:${pushResult.message}`);
560
+ if (pushResult.output) {
561
+ logger_1.logger.info(pushResult.output);
562
+ }
563
+ return false;
564
+ }
565
+ logger_1.logger.success('✓ 推送成功');
566
+ return true;
567
+ }
568
+ // 使用已選擇的 Token
569
+ const selectedToken = tokens.tokens.find((t) => t.id === selectedTokenId);
570
+ if (selectedToken) {
571
+ logger_1.logger.info(`使用 Token「${selectedToken.name}」重新推送...`);
572
+ const pushResult = await (0, git_1.gitPush)(remote, branch, undefined, selectedToken.token);
573
+ if (!pushResult.success) {
574
+ logger_1.logger.error(`推送失敗:${pushResult.message}`);
575
+ if (pushResult.output) {
576
+ logger_1.logger.info(pushResult.output);
577
+ }
578
+ return false;
579
+ }
580
+ logger_1.logger.success('✓ 推送成功');
581
+ return true;
582
+ }
583
+ return false;
584
+ }
585
+ /**
586
+ * 處理 Git 克隆操作
587
+ */
588
+ async function handleGitClone() {
589
+ logger_1.logger.empty();
590
+ logger_1.logger.icon('📥', '克隆 Git 倉庫');
591
+ logger_1.logger.divider();
592
+ logger_1.logger.empty();
593
+ // 詢問倉庫 URL
594
+ const { url } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
595
+ {
596
+ type: 'input',
597
+ name: 'url',
598
+ message: '請輸入 Git 倉庫 URL:',
599
+ default: '',
600
+ validate: (input) => {
601
+ if (!input.trim()) {
602
+ return '倉庫 URL 不能為空';
603
+ }
604
+ // 簡單驗證 URL 格式
605
+ const urlPattern = /^(https?:\/\/|git@|ssh:\/\/)/;
606
+ if (!urlPattern.test(input.trim())) {
607
+ return '請輸入有效的 Git URL(以 https://、git@ 或 ssh:// 開頭)';
608
+ }
609
+ return true;
610
+ },
611
+ },
612
+ ]));
613
+ // 詢問目標目錄
614
+ const { directory } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
615
+ {
616
+ type: 'input',
617
+ name: 'directory',
618
+ message: '目標目錄名稱:',
619
+ default: url.split('/').pop()?.replace('.git', '') || '',
620
+ validate: (input) => {
621
+ if (!input.trim()) {
622
+ return '目錄名稱不能為空';
623
+ }
624
+ return true;
625
+ },
626
+ },
627
+ ]));
628
+ // 確認克隆
629
+ const { confirm } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
630
+ {
631
+ type: 'confirm',
632
+ name: 'confirm',
633
+ message: `確認克隆到目錄 "${directory}"?`,
634
+ default: true,
635
+ },
636
+ ]));
637
+ if (!confirm) {
638
+ logger_1.logger.info('已取消克隆');
639
+ return;
640
+ }
641
+ // 執行 git clone
642
+ logger_1.logger.info(`正在克隆 ${url} 到 ${directory}...`);
643
+ const cloneResult = await (0, git_1.gitClone)(url, directory);
644
+ if (!cloneResult.success) {
645
+ logger_1.logger.error(`克隆失敗:${cloneResult.message}`);
646
+ if (cloneResult.output) {
647
+ logger_1.logger.info(cloneResult.output);
648
+ }
649
+ process.exit(1);
650
+ }
651
+ logger_1.logger.success('✓ 克隆成功');
652
+ logger_1.logger.empty();
653
+ logger_1.logger.success('✅ Git clone 完成!');
654
+ logger_1.logger.empty();
655
+ logger_1.logger.icon('📂', `專案位置:${directory}`);
656
+ logger_1.logger.empty();
657
+ }
658
+ /**
659
+ * 管理 Git Token
660
+ */
661
+ async function handleManageGitToken() {
662
+ let running = true;
663
+ while (running) {
664
+ logger_1.logger.empty();
665
+ logger_1.logger.icon('🔑', '管理 Git Token');
666
+ logger_1.logger.divider();
667
+ logger_1.logger.empty();
668
+ const tokenList = (0, git_token_1.getTokenList)();
669
+ if (tokenList.length === 0) {
670
+ logger_1.logger.info('目前沒有已儲存的 Token');
671
+ }
672
+ else {
673
+ logger_1.logger.info(`目前共有 ${tokenList.length} 個 Token:`);
674
+ tokenList.forEach((t, index) => {
675
+ logger_1.logger.info(` ${index + 1}. ${t.name}`);
676
+ });
677
+ logger_1.logger.empty();
678
+ }
679
+ const { action } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
680
+ {
681
+ type: 'rawlist',
682
+ name: 'action',
683
+ message: '選擇操作:',
684
+ choices: [
685
+ { name: '➕ 新增 Token', value: 'add' },
686
+ { name: '🗑️ 刪除 Token', value: 'delete' },
687
+ { name: '✏️ 編輯 Token 名稱', value: 'edit' },
688
+ { name: '❌ 返回主選單', value: 'back' },
689
+ ],
690
+ },
691
+ ]));
692
+ switch (action) {
693
+ case 'add':
694
+ await handleAddToken();
695
+ break;
696
+ case 'delete':
697
+ await handleDeleteToken();
698
+ break;
699
+ case 'edit':
700
+ await handleEditToken();
701
+ break;
702
+ case 'back':
703
+ logger_1.logger.info('返回主選單');
704
+ running = false;
705
+ break;
706
+ default:
707
+ logger_1.logger.error('無效的操作');
708
+ }
709
+ }
710
+ }
711
+ /**
712
+ * 新增 Token
713
+ */
714
+ async function handleAddToken() {
715
+ logger_1.logger.empty();
716
+ logger_1.logger.icon('➕', '新增 Token');
717
+ logger_1.logger.divider();
718
+ logger_1.logger.empty();
719
+ const { name } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
720
+ {
721
+ type: 'input',
722
+ name: 'name',
723
+ message: '為 Token 設定名稱:',
724
+ default: 'GitHub 個人',
725
+ validate: (input) => {
726
+ if (!input.trim()) {
727
+ return '名稱不能為空';
728
+ }
729
+ return true;
730
+ },
731
+ },
732
+ ]));
733
+ const { token } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
734
+ {
735
+ type: 'password',
736
+ name: 'token',
737
+ message: '請輸入 Git Token:',
738
+ validate: (input) => {
739
+ if (!input.trim()) {
740
+ return 'Token 不能為空';
741
+ }
742
+ return true;
743
+ },
744
+ },
745
+ ]));
746
+ (0, git_token_1.addToken)(name, token);
747
+ logger_1.logger.success(`✓ Token「${name}」已保存`);
748
+ logger_1.logger.empty();
749
+ }
750
+ /**
751
+ * 刪除 Token
752
+ */
753
+ async function handleDeleteToken() {
754
+ logger_1.logger.empty();
755
+ logger_1.logger.icon('🗑️', '刪除 Token');
756
+ logger_1.logger.divider();
757
+ logger_1.logger.empty();
758
+ const tokenList = (0, git_token_1.getTokenList)();
759
+ if (tokenList.length === 0) {
760
+ logger_1.logger.warn('目前沒有已儲存的 Token');
761
+ return;
762
+ }
763
+ const { tokenId } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
764
+ {
765
+ type: 'rawlist',
766
+ name: 'tokenId',
767
+ message: '選擇要刪除的 Token:',
768
+ choices: tokenList.map((t) => ({
769
+ name: t.name,
770
+ value: t.id,
771
+ })),
772
+ },
773
+ ]));
774
+ const deleted = (0, git_token_1.deleteToken)(tokenId);
775
+ if (deleted) {
776
+ logger_1.logger.success('✓ Token 已刪除');
777
+ }
778
+ else {
779
+ logger_1.logger.error('刪除失敗');
780
+ }
781
+ logger_1.logger.empty();
782
+ }
783
+ /**
784
+ * 編輯 Token 名稱
785
+ */
786
+ async function handleEditToken() {
787
+ logger_1.logger.empty();
788
+ logger_1.logger.icon('✏️', '編輯 Token 名稱');
789
+ logger_1.logger.divider();
790
+ logger_1.logger.empty();
791
+ const tokenList = (0, git_token_1.getTokenList)();
792
+ if (tokenList.length === 0) {
793
+ logger_1.logger.warn('目前沒有已儲存的 Token');
794
+ return;
795
+ }
796
+ const { tokenId } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
797
+ {
798
+ type: 'rawlist',
799
+ name: 'tokenId',
800
+ message: '選擇要編輯的 Token:',
801
+ choices: tokenList.map((t) => ({
802
+ name: t.name,
803
+ value: t.id,
804
+ })),
805
+ },
806
+ ]));
807
+ const { newName } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
808
+ {
809
+ type: 'input',
810
+ name: 'newName',
811
+ message: '輸入新名稱:',
812
+ validate: (input) => {
813
+ if (!input.trim()) {
814
+ return '名稱不能為空';
815
+ }
816
+ return true;
817
+ },
818
+ },
819
+ ]));
820
+ const updated = (0, git_token_1.updateTokenName)(tokenId, newName);
821
+ if (updated) {
822
+ logger_1.logger.success(`✓ Token 名稱已更新為「${newName}」`);
823
+ }
824
+ else {
825
+ logger_1.logger.error('更新失敗');
826
+ }
827
+ logger_1.logger.empty();
828
+ }
829
+ /**
830
+ * 處理取消 Git 倉庫操作
831
+ */
832
+ /**
833
+ * 檢查是否有未提交的更改
834
+ */
835
+ async function checkForUncommittedChanges() {
836
+ try {
837
+ const { stdout } = await (0, execa_1.execa)('git', ['status', '--porcelain']);
838
+ return stdout.trim().length > 0;
839
+ }
840
+ catch {
841
+ return false;
842
+ }
843
+ }
844
+ async function handleRemoveGitRepo() {
845
+ logger_1.logger.empty();
846
+ logger_1.logger.icon('🗑️', '取消 Git 倉庫');
847
+ logger_1.logger.divider();
848
+ logger_1.logger.empty();
849
+ // 檢查是否為 Git 倉庫
850
+ const isGit = await (0, git_1.isGitRepository)();
851
+ if (!isGit) {
852
+ logger_1.logger.warn('此目錄不是 Git 倉庫');
853
+ return;
854
+ }
855
+ // 確認刪除
856
+ const { confirm } = await Promise.resolve().then(() => __importStar(require('inquirer'))).then((mod) => mod.default.prompt([
857
+ {
858
+ type: 'confirm',
859
+ name: 'confirm',
860
+ message: '確定要取消 Git 倉庫嗎?這將刪除 .git 目錄(無法恢復)',
861
+ default: false,
862
+ },
863
+ ]));
864
+ if (!confirm) {
865
+ logger_1.logger.info('已取消操作');
866
+ return;
867
+ }
868
+ // 執行刪除
869
+ const result = await (0, git_1.removeGitRepo)();
870
+ if (result.success) {
871
+ logger_1.logger.success('✓ 已成功取消 Git 倉庫');
872
+ logger_1.logger.empty();
873
+ logger_1.logger.success('✅ 完成!');
874
+ }
875
+ else {
876
+ logger_1.logger.error(`取消失敗:${result.message}`);
877
+ process.exit(1);
878
+ }
879
+ logger_1.logger.empty();
880
+ }
881
+ /**
882
+ * 建立 git 命令
883
+ */
884
+ function createGitCommand(program) {
885
+ program.command('git').description('Git 相關操作(互動式選單)').action(gitCommandHandler);
886
+ }
887
+ //# sourceMappingURL=git.js.map