@axis-ds/cli 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  // src/template/registry-client.ts
2
2
  import fetch from "node-fetch";
3
- var TEMPLATE_REGISTRY_URL = process.env.AXIS_TEMPLATE_URL || "https://ds.minu.best/templates";
3
+ var TEMPLATE_REGISTRY_URL = process.env.AXIS_TEMPLATE_URL || "https://axis.minu.best/templates";
4
4
  async function fetchTemplateIndex() {
5
5
  const url = `${TEMPLATE_REGISTRY_URL}/index.json`;
6
6
  const res = await fetch(url);
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  fetchTemplateDetail,
13
13
  fetchTemplateIndex
14
- } from "./chunk-CGFESSCW.js";
14
+ } from "./chunk-3WGMHDZL.js";
15
15
 
16
16
  // src/index.ts
17
17
  import { Command } from "commander";
@@ -1080,7 +1080,8 @@ var AxisCollector = class extends BaseCollector {
1080
1080
  */
1081
1081
  getDocsUrl(slug, category) {
1082
1082
  const baseUrl = "https://axis.minu.best";
1083
- return `${baseUrl}/${category}/${slug}`;
1083
+ const route = category === "agentic" ? "agentic" : "components";
1084
+ return `${baseUrl}/${route}/${slug}`;
1084
1085
  }
1085
1086
  };
1086
1087
 
@@ -1654,7 +1655,7 @@ async function applyTemplate(slug, options) {
1654
1655
  async function initTemplate(slug, options) {
1655
1656
  let selectedSlug = slug;
1656
1657
  if (!selectedSlug) {
1657
- const { fetchTemplateIndex: fetchTemplateIndex2 } = await import("./registry-client-TPWFZ4HE.js");
1658
+ const { fetchTemplateIndex: fetchTemplateIndex2 } = await import("./registry-client-RMES4CL5.js");
1658
1659
  const index = await fetchTemplateIndex2();
1659
1660
  const { choice } = await prompts({
1660
1661
  type: "select",
@@ -1705,7 +1706,7 @@ async function initTemplate(slug, options) {
1705
1706
  const configPath = path6.join(resolved, "axis.config.json");
1706
1707
  if (!await fs6.pathExists(configPath)) {
1707
1708
  const axisConfig = {
1708
- $schema: "https://ds.minu.best/schema.json",
1709
+ $schema: "https://axis.minu.best/schema.json",
1709
1710
  template: selectedSlug,
1710
1711
  componentsDir: "./components/ui",
1711
1712
  tailwindConfig: "./tailwind.config.ts",
@@ -2102,7 +2103,7 @@ function registerCheckCommand(program2) {
2102
2103
  }
2103
2104
 
2104
2105
  // src/index.ts
2105
- var REGISTRY_URL = process.env.AXIS_REGISTRY_URL || "https://ds.minu.best/r";
2106
+ var REGISTRY_URL = process.env.AXIS_REGISTRY_URL || "https://axis.minu.best/r";
2106
2107
  var program = new Command();
2107
2108
  program.name("axis").description("AXIS Design System CLI - shadcn \uD638\uD658 \uCEF4\uD3EC\uB10C\uD2B8 \uC124\uCE58 \uB3C4\uAD6C").version("0.1.0");
2108
2109
  program.command("init").description("\uD504\uB85C\uC81D\uD2B8\uC5D0 AXIS Design System \uCD08\uAE30\uD654").option("-y, --yes", "\uAE30\uBCF8\uAC12\uC73C\uB85C \uC790\uB3D9 \uC124\uC815").action(async (options) => {
@@ -2132,7 +2133,7 @@ program.command("init").description("\uD504\uB85C\uC81D\uD2B8\uC5D0 AXIS Design
2132
2133
  }
2133
2134
  ]);
2134
2135
  const axisConfig = {
2135
- $schema: "https://ds.minu.best/schema.json",
2136
+ $schema: "https://axis.minu.best/schema.json",
2136
2137
  componentsDir: config.componentsDir,
2137
2138
  tailwindConfig: config.tailwindConfig,
2138
2139
  globalCss: config.globalCss,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  fetchTemplateDetail,
3
3
  fetchTemplateIndex
4
- } from "./chunk-CGFESSCW.js";
4
+ } from "./chunk-3WGMHDZL.js";
5
5
  export {
6
6
  fetchTemplateDetail,
7
7
  fetchTemplateIndex
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axis-ds/cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "type": "module",
5
5
  "description": "AXIS Design System - CLI for adding components (shadcn compatible)",
6
6
  "publishConfig": {
@@ -26,6 +26,7 @@
26
26
  "@types/fs-extra": "^11.0.0",
27
27
  "@types/node": "^22.0.0",
28
28
  "@types/prompts": "^2.4.0",
29
+ "@vitest/coverage-v8": "^2.1.0",
29
30
  "tsup": "^8.0.0",
30
31
  "typescript": "^5.7.0",
31
32
  "vitest": "^2.1.0"
@@ -49,6 +50,7 @@
49
50
  "dev": "tsup src/index.ts src/monet.ts src/v0.ts src/sync/index.ts --format esm --watch",
50
51
  "clean": "rm -rf dist",
51
52
  "test": "vitest run",
53
+ "test:coverage": "vitest run --coverage",
52
54
  "test:watch": "vitest"
53
55
  }
54
56
  }
@@ -1,873 +0,0 @@
1
- // src/sync/index.ts
2
- import chalk from "chalk";
3
- import ora from "ora";
4
- import path2 from "path";
5
- import fs3 from "fs-extra";
6
-
7
- // src/sync/parser.ts
8
- import fs from "fs-extra";
9
- var IN_PROGRESS_ROW_REGEX = /\|\s*(\d+)\s*\|\s*([^|]+)\|\s*\[?(WI-\d{4})\]?(?:\([^)]*\))?\s*\|\s*([^|]*)\|\s*([^|]*)\|\s*(✅|🔄|⏳)\s*\|/;
10
- var COMPLETED_ROW_REGEX = /\|\s*(\d+)\s*\|\s*([^|]+)\|\s*\[?(WI-\d{4})\]?(?:\([^)]*\))?\s*\|\s*([^|]*)\|\s*([^|]*)\|/;
11
- var SECTION_HEADER_REGEX = /^###\s+(진행 중|완료)/;
12
- async function parseProjectTodo(filePath) {
13
- const content = await fs.readFile(filePath, "utf-8");
14
- return parseContent(content);
15
- }
16
- function parseContent(content) {
17
- const lines = content.split("\n");
18
- const inProgress = [];
19
- const completed = [];
20
- const errors = [];
21
- let currentSection = "none";
22
- let inTable = false;
23
- for (let i = 0; i < lines.length; i++) {
24
- const line = lines[i];
25
- const lineNumber = i + 1;
26
- const sectionMatch = line.match(SECTION_HEADER_REGEX);
27
- if (sectionMatch) {
28
- currentSection = sectionMatch[1] === "\uC9C4\uD589 \uC911" ? "inProgress" : "completed";
29
- inTable = false;
30
- continue;
31
- }
32
- if (line.trim().startsWith("| #") || line.trim().startsWith("|---")) {
33
- inTable = true;
34
- continue;
35
- }
36
- if (line.trim() === "" || !line.trim().startsWith("|")) {
37
- inTable = false;
38
- continue;
39
- }
40
- if (inTable && currentSection !== "none") {
41
- try {
42
- const workItem = parseTableRow(line, currentSection);
43
- if (workItem) {
44
- if (currentSection === "inProgress") {
45
- inProgress.push(workItem);
46
- } else {
47
- completed.push(workItem);
48
- }
49
- }
50
- } catch (error) {
51
- if (line.includes("WI-")) {
52
- errors.push({
53
- line: lineNumber,
54
- content: line,
55
- message: error.message
56
- });
57
- }
58
- }
59
- }
60
- }
61
- return { inProgress, completed, errors };
62
- }
63
- function parseTableRow(line, section) {
64
- if (section === "inProgress") {
65
- const match = line.match(IN_PROGRESS_ROW_REGEX);
66
- if (!match) {
67
- if (!line.includes("WI-")) {
68
- return null;
69
- }
70
- throw new Error("\uC9C4\uD589 \uC911 \uD14C\uC774\uBE14 \uD589 \uD30C\uC2F1 \uC2E4\uD328");
71
- }
72
- const [, index, title, wiId, phase, priority, status] = match;
73
- const linkMatch = line.match(/\[WI-\d{4}\]\(([^)]+)\)/);
74
- return {
75
- index: parseInt(index, 10),
76
- title: title.trim(),
77
- wiId: wiId.trim(),
78
- phase: phase.trim(),
79
- priority: priority.trim(),
80
- status: status.trim(),
81
- link: linkMatch ? linkMatch[1] : void 0
82
- };
83
- } else {
84
- const match = line.match(COMPLETED_ROW_REGEX);
85
- if (!match) {
86
- if (!line.includes("WI-")) {
87
- return null;
88
- }
89
- throw new Error("\uC644\uB8CC \uD14C\uC774\uBE14 \uD589 \uD30C\uC2F1 \uC2E4\uD328");
90
- }
91
- const [, index, title, wiId, phase] = match;
92
- const linkMatch = line.match(/\[WI-\d{4}\]\(([^)]+)\)/);
93
- return {
94
- index: parseInt(index, 10),
95
- title: title.trim(),
96
- wiId: wiId.trim(),
97
- phase: phase.trim(),
98
- priority: "",
99
- status: "\u2705",
100
- link: linkMatch ? linkMatch[1] : void 0
101
- };
102
- }
103
- }
104
- function extractAllWorkItems(result) {
105
- const allItems = [...result.inProgress, ...result.completed];
106
- const seen = /* @__PURE__ */ new Set();
107
- return allItems.filter((item) => {
108
- if (seen.has(item.wiId)) {
109
- return false;
110
- }
111
- seen.add(item.wiId);
112
- return true;
113
- });
114
- }
115
- function filterByWiIds(items, wiIds) {
116
- const targetSet = new Set(wiIds.map((id) => id.toUpperCase()));
117
- return items.filter((item) => targetSet.has(item.wiId.toUpperCase()));
118
- }
119
-
120
- // src/sync/github-client.ts
121
- import { execSync } from "child_process";
122
- var GITHUB_GRAPHQL_URL = "https://api.github.com/graphql";
123
- var GitHubClient = class {
124
- token;
125
- owner;
126
- repo;
127
- constructor(owner, repo, token) {
128
- this.owner = owner;
129
- this.repo = repo;
130
- this.token = token || this.getTokenFromGhCli();
131
- }
132
- /**
133
- * gh CLI에서 토큰 가져오기
134
- */
135
- getTokenFromGhCli() {
136
- try {
137
- const token = execSync("gh auth token", {
138
- encoding: "utf-8",
139
- stdio: ["pipe", "pipe", "ignore"]
140
- }).trim();
141
- if (!token) {
142
- throw new Error("\uD1A0\uD070\uC774 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4.");
143
- }
144
- return token;
145
- } catch {
146
- throw new Error(
147
- "GitHub \uD1A0\uD070\uC744 \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. gh CLI \uC124\uCE58 \uBC0F \uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.\n \uC124\uCE58: https://cli.github.com\n \uC778\uC99D: gh auth login"
148
- );
149
- }
150
- }
151
- /**
152
- * GraphQL 요청 실행
153
- */
154
- async graphql(query, variables) {
155
- const response = await fetch(GITHUB_GRAPHQL_URL, {
156
- method: "POST",
157
- headers: {
158
- "Authorization": `Bearer ${this.token}`,
159
- "Content-Type": "application/json",
160
- "Accept": "application/json"
161
- },
162
- body: JSON.stringify({ query, variables })
163
- });
164
- if (!response.ok) {
165
- throw new Error(`GitHub API \uC624\uB958: ${response.status} ${response.statusText}`);
166
- }
167
- const result = await response.json();
168
- if (result.errors && result.errors.length > 0) {
169
- throw new Error(`GraphQL \uC624\uB958: ${result.errors.map((e) => e.message).join(", ")}`);
170
- }
171
- return result.data;
172
- }
173
- /**
174
- * Project V2 정보 조회
175
- */
176
- async getProjectInfo(projectNumber) {
177
- const query = `
178
- query GetProject($owner: String!, $repo: String!, $number: Int!) {
179
- repository(owner: $owner, name: $repo) {
180
- projectV2(number: $number) {
181
- id
182
- title
183
- url
184
- field(name: "Status") {
185
- ... on ProjectV2SingleSelectField {
186
- id
187
- options {
188
- id
189
- name
190
- }
191
- }
192
- }
193
- }
194
- }
195
- }
196
- `;
197
- const data = await this.graphql(query, {
198
- owner: this.owner,
199
- repo: this.repo,
200
- number: projectNumber
201
- });
202
- const project = data.repository.projectV2;
203
- const statusField = project.field;
204
- const options = {
205
- todo: "",
206
- inProgress: "",
207
- done: ""
208
- };
209
- for (const opt of statusField.options) {
210
- const nameLower = opt.name.toLowerCase();
211
- if (nameLower === "todo" || nameLower === "to do") {
212
- options.todo = opt.id;
213
- } else if (nameLower === "in progress" || nameLower === "in-progress") {
214
- options.inProgress = opt.id;
215
- } else if (nameLower === "done" || nameLower === "completed") {
216
- options.done = opt.id;
217
- }
218
- }
219
- return {
220
- id: project.id,
221
- title: project.title,
222
- url: project.url,
223
- statusField: {
224
- id: statusField.id,
225
- options
226
- }
227
- };
228
- }
229
- /**
230
- * 프로젝트의 모든 아이템 조회
231
- */
232
- async getProjectItems(projectId) {
233
- const query = `
234
- query GetProjectItems($projectId: ID!, $cursor: String) {
235
- node(id: $projectId) {
236
- ... on ProjectV2 {
237
- items(first: 100, after: $cursor) {
238
- pageInfo {
239
- hasNextPage
240
- endCursor
241
- }
242
- nodes {
243
- id
244
- content {
245
- ... on Issue {
246
- id
247
- number
248
- title
249
- url
250
- }
251
- }
252
- fieldValueByName(name: "Status") {
253
- ... on ProjectV2ItemFieldSingleSelectValue {
254
- name
255
- }
256
- }
257
- }
258
- }
259
- }
260
- }
261
- }
262
- `;
263
- const items = [];
264
- let cursor = null;
265
- let hasNextPage = true;
266
- while (hasNextPage) {
267
- const data = await this.graphql(query, {
268
- projectId,
269
- cursor
270
- });
271
- const itemsData = data.node.items;
272
- for (const node of itemsData.nodes) {
273
- items.push({
274
- id: node.id,
275
- content: node.content,
276
- fieldValues: {
277
- status: node.fieldValueByName?.name
278
- }
279
- });
280
- }
281
- hasNextPage = itemsData.pageInfo.hasNextPage;
282
- cursor = itemsData.pageInfo.endCursor;
283
- }
284
- return items;
285
- }
286
- /**
287
- * Issue 생성
288
- */
289
- async createIssue(title, body, labels) {
290
- const query = `
291
- mutation CreateIssue($repositoryId: ID!, $title: String!, $body: String, $labelIds: [ID!]) {
292
- createIssue(input: {
293
- repositoryId: $repositoryId
294
- title: $title
295
- body: $body
296
- labelIds: $labelIds
297
- }) {
298
- issue {
299
- id
300
- number
301
- }
302
- }
303
- }
304
- `;
305
- const repoQuery = `
306
- query GetRepoId($owner: String!, $repo: String!) {
307
- repository(owner: $owner, name: $repo) {
308
- id
309
- }
310
- }
311
- `;
312
- const repoData = await this.graphql(repoQuery, {
313
- owner: this.owner,
314
- repo: this.repo
315
- });
316
- const data = await this.graphql(query, {
317
- repositoryId: repoData.repository.id,
318
- title,
319
- body,
320
- labelIds: labels
321
- });
322
- return data.createIssue.issue;
323
- }
324
- /**
325
- * Issue를 Project에 추가
326
- */
327
- async addIssueToProject(projectId, issueId) {
328
- const query = `
329
- mutation AddToProject($projectId: ID!, $contentId: ID!) {
330
- addProjectV2ItemById(input: {
331
- projectId: $projectId
332
- contentId: $contentId
333
- }) {
334
- item {
335
- id
336
- }
337
- }
338
- }
339
- `;
340
- const data = await this.graphql(query, {
341
- projectId,
342
- contentId: issueId
343
- });
344
- return data.addProjectV2ItemById.item.id;
345
- }
346
- /**
347
- * Project Item의 Status 업데이트
348
- */
349
- async updateItemStatus(projectId, itemId, fieldId, optionId) {
350
- const query = `
351
- mutation UpdateStatus($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
352
- updateProjectV2ItemFieldValue(input: {
353
- projectId: $projectId
354
- itemId: $itemId
355
- fieldId: $fieldId
356
- value: { singleSelectOptionId: $optionId }
357
- }) {
358
- projectV2Item {
359
- id
360
- }
361
- }
362
- }
363
- `;
364
- await this.graphql(query, {
365
- projectId,
366
- itemId,
367
- fieldId,
368
- optionId
369
- });
370
- }
371
- /**
372
- * Issue 번호로 Issue 정보 조회
373
- */
374
- async getIssue(issueNumber) {
375
- const query = `
376
- query GetIssue($owner: String!, $repo: String!, $number: Int!) {
377
- repository(owner: $owner, name: $repo) {
378
- issue(number: $number) {
379
- id
380
- title
381
- state
382
- }
383
- }
384
- }
385
- `;
386
- try {
387
- const data = await this.graphql(query, {
388
- owner: this.owner,
389
- repo: this.repo,
390
- number: issueNumber
391
- });
392
- return data.repository.issue;
393
- } catch {
394
- return null;
395
- }
396
- }
397
- };
398
-
399
- // src/sync/mapper.ts
400
- import fs2 from "fs-extra";
401
- import path from "path";
402
- var DEFAULT_MAPPING = {
403
- version: "1.0.0",
404
- repository: {
405
- owner: "thoughtandaction",
406
- repo: "axis"
407
- },
408
- project: {
409
- number: 4
410
- },
411
- mappings: [],
412
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
413
- };
414
- var WiMapper = class {
415
- mappingPath;
416
- mapping;
417
- constructor(mappingPath) {
418
- this.mappingPath = mappingPath;
419
- this.mapping = { ...DEFAULT_MAPPING };
420
- }
421
- /**
422
- * 매핑 파일 로드
423
- */
424
- async load() {
425
- if (await fs2.pathExists(this.mappingPath)) {
426
- try {
427
- this.mapping = await fs2.readJSON(this.mappingPath);
428
- } catch {
429
- console.warn("\uB9E4\uD551 \uD30C\uC77C \uB85C\uB4DC \uC2E4\uD328, \uAE30\uBCF8\uAC12 \uC0AC\uC6A9");
430
- this.mapping = { ...DEFAULT_MAPPING };
431
- }
432
- }
433
- }
434
- /**
435
- * 매핑 파일 저장
436
- */
437
- async save() {
438
- this.mapping.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
439
- await fs2.ensureDir(path.dirname(this.mappingPath));
440
- await fs2.writeJSON(this.mappingPath, this.mapping, { spaces: 2 });
441
- }
442
- /**
443
- * 저장소 정보 반환
444
- */
445
- getRepository() {
446
- return this.mapping.repository;
447
- }
448
- /**
449
- * 프로젝트 번호 반환
450
- */
451
- getProjectNumber() {
452
- return this.mapping.project.number;
453
- }
454
- /**
455
- * 프로젝트 ID 설정
456
- */
457
- setProjectId(id) {
458
- this.mapping.project.id = id;
459
- }
460
- /**
461
- * WI ID로 매핑 찾기
462
- */
463
- findByWiId(wiId) {
464
- return this.mapping.mappings.find(
465
- (m) => m.wiId.toUpperCase() === wiId.toUpperCase()
466
- );
467
- }
468
- /**
469
- * Issue 번호로 매핑 찾기
470
- */
471
- findByIssueNumber(issueNumber) {
472
- return this.mapping.mappings.find((m) => m.issueNumber === issueNumber);
473
- }
474
- /**
475
- * 매핑 추가 또는 업데이트
476
- */
477
- upsertMapping(wiId, issueNumber, projectItemId, status) {
478
- const existing = this.findByWiId(wiId);
479
- if (existing) {
480
- existing.issueNumber = issueNumber;
481
- if (projectItemId) {
482
- existing.projectItemId = projectItemId;
483
- }
484
- existing.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
485
- if (status) {
486
- existing.lastStatus = status;
487
- }
488
- } else {
489
- this.mapping.mappings.push({
490
- wiId: wiId.toUpperCase(),
491
- issueNumber,
492
- projectItemId,
493
- lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
494
- lastStatus: status
495
- });
496
- }
497
- }
498
- /**
499
- * 모든 매핑 반환
500
- */
501
- getAllMappings() {
502
- return [...this.mapping.mappings];
503
- }
504
- /**
505
- * GitHub 프로젝트 아이템에서 WI ID 추출
506
- */
507
- extractWiIdFromItem(item) {
508
- const title = item.content?.title || "";
509
- const match = title.match(/\[?(WI-\d{4})\]?/i);
510
- return match ? match[1].toUpperCase() : null;
511
- }
512
- /**
513
- * 프로젝트 아이템으로 매핑 동기화
514
- */
515
- syncFromProjectItems(items) {
516
- for (const item of items) {
517
- if (!item.content) continue;
518
- const wiId = this.extractWiIdFromItem(item);
519
- if (wiId) {
520
- const existing = this.findByWiId(wiId);
521
- if (existing) {
522
- existing.projectItemId = item.id;
523
- } else {
524
- this.mapping.mappings.push({
525
- wiId,
526
- issueNumber: item.content.number,
527
- projectItemId: item.id,
528
- lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString()
529
- });
530
- }
531
- }
532
- }
533
- }
534
- };
535
- function generateIssueTitle(workItem) {
536
- return `[${workItem.wiId}] ${workItem.title}`;
537
- }
538
- function generateIssueBody(workItem) {
539
- const lines = [
540
- `## Work Item: ${workItem.wiId}`,
541
- "",
542
- `**\uC81C\uBAA9**: ${workItem.title}`,
543
- `**Phase**: ${workItem.phase}`,
544
- `**\uC6B0\uC120\uC21C\uC704**: ${workItem.priority}`,
545
- ""
546
- ];
547
- if (workItem.link) {
548
- lines.push(`**WI \uD3F4\uB354**: [\uBC14\uB85C\uAC00\uAE30](${workItem.link})`);
549
- lines.push("");
550
- }
551
- lines.push("---");
552
- lines.push("");
553
- lines.push("> \uC774 Issue\uB294 `project-todo.md`\uC640 \uC790\uB3D9 \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4.");
554
- return lines.join("\n");
555
- }
556
-
557
- // src/sync/types.ts
558
- var STATUS_MAP = {
559
- "\u2705": "Done",
560
- "\u{1F504}": "In Progress",
561
- "\u23F3": "Todo"
562
- };
563
-
564
- // src/sync/index.ts
565
- var statusMap = {
566
- "\u2705": "Done",
567
- "\u{1F504}": "In Progress",
568
- "\u23F3": "Todo"
569
- };
570
- var DEFAULT_TODO_PATH = "project-todo.md";
571
- var DEFAULT_MAPPING_PATH = ".github/wi-mapping.json";
572
- function registerSyncCommand(program) {
573
- const syncCmd = program.command("sync").description("GitHub Project\uC640 project-todo.md \uB3D9\uAE30\uD654");
574
- syncCmd.option("--dry-run", "\uBBF8\uB9AC\uBCF4\uAE30 \uBAA8\uB4DC (\uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C)").option("--wi <ids...>", "\uD2B9\uC815 WI\uB9CC \uB3D9\uAE30\uD654 (\uC608: WI-0001 WI-0002)").option("-v, --verbose", "\uC0C1\uC138 \uCD9C\uB825").option("--token <token>", "GitHub \uD1A0\uD070 (\uAE30\uBCF8: gh auth token \uC0AC\uC6A9)").action(async (options) => {
575
- await runSync({
576
- dryRun: options.dryRun,
577
- wiIds: options.wi,
578
- verbose: options.verbose,
579
- token: options.token
580
- });
581
- });
582
- syncCmd.command("status").description("\uD604\uC7AC \uB3D9\uAE30\uD654 \uC0C1\uD0DC \uD655\uC778").action(async () => {
583
- await showSyncStatus();
584
- });
585
- syncCmd.command("init").description("\uB3D9\uAE30\uD654 \uC124\uC815 \uCD08\uAE30\uD654").option("--owner <owner>", "GitHub \uC800\uC7A5\uC18C \uC18C\uC720\uC790", "thoughtandaction").option("--repo <repo>", "GitHub \uC800\uC7A5\uC18C \uC774\uB984", "axis").option("--project <number>", "GitHub Project \uBC88\uD638", "4").action(async (options) => {
586
- await initSync(options.owner, options.repo, parseInt(options.project, 10));
587
- });
588
- }
589
- async function runSync(options) {
590
- const spinner = ora();
591
- const result = {
592
- success: true,
593
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
594
- summary: { total: 0, created: 0, updated: 0, skipped: 0, failed: 0 },
595
- items: [],
596
- errors: []
597
- };
598
- try {
599
- spinner.start("project-todo.md \uD30C\uC2F1 \uC911...");
600
- const todoPath = path2.resolve(DEFAULT_TODO_PATH);
601
- if (!await fs3.pathExists(todoPath)) {
602
- throw new Error(`project-todo.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${todoPath}`);
603
- }
604
- const parseResult = await parseProjectTodo(todoPath);
605
- let workItems = extractAllWorkItems(parseResult);
606
- if (options.wiIds && options.wiIds.length > 0) {
607
- workItems = filterByWiIds(workItems, options.wiIds);
608
- }
609
- spinner.succeed(`${workItems.length}\uAC1C Work Item \uD30C\uC2F1 \uC644\uB8CC`);
610
- if (workItems.length === 0) {
611
- console.log(chalk.yellow("\n\uB3D9\uAE30\uD654\uD560 Work Item\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
612
- return;
613
- }
614
- spinner.start("\uB9E4\uD551 \uD30C\uC77C \uB85C\uB4DC \uC911...");
615
- const mapper = new WiMapper(path2.resolve(DEFAULT_MAPPING_PATH));
616
- await mapper.load();
617
- spinner.succeed("\uB9E4\uD551 \uD30C\uC77C \uB85C\uB4DC \uC644\uB8CC");
618
- spinner.start("GitHub \uC5F0\uACB0 \uC911...");
619
- const { owner, repo } = mapper.getRepository();
620
- const client = new GitHubClient(owner, repo, options.token);
621
- spinner.succeed("GitHub \uC5F0\uACB0 \uC644\uB8CC");
622
- spinner.start("GitHub Project \uC815\uBCF4 \uC870\uD68C \uC911...");
623
- const projectNumber = mapper.getProjectNumber();
624
- const projectInfo = await client.getProjectInfo(projectNumber);
625
- mapper.setProjectId(projectInfo.id);
626
- spinner.succeed(`Project: ${projectInfo.title}`);
627
- spinner.start("\uD504\uB85C\uC81D\uD2B8 \uC544\uC774\uD15C \uC870\uD68C \uC911...");
628
- const projectItems = await client.getProjectItems(projectInfo.id);
629
- mapper.syncFromProjectItems(projectItems);
630
- spinner.succeed(`${projectItems.length}\uAC1C \uD504\uB85C\uC81D\uD2B8 \uC544\uC774\uD15C \uC870\uD68C \uC644\uB8CC`);
631
- console.log(chalk.blue("\n\u{1F4E6} \uB3D9\uAE30\uD654 \uC2DC\uC791\n"));
632
- if (options.dryRun) {
633
- console.log(chalk.yellow("\u{1F50D} Dry Run \uBAA8\uB4DC - \uC2E4\uC81C \uBCC0\uACBD \uC5C6\uC74C\n"));
634
- }
635
- result.summary.total = workItems.length;
636
- for (const workItem of workItems) {
637
- const itemResult = await syncWorkItem(
638
- workItem,
639
- mapper,
640
- client,
641
- projectInfo,
642
- options
643
- );
644
- result.items.push(itemResult);
645
- switch (itemResult.action) {
646
- case "create":
647
- result.summary.created++;
648
- break;
649
- case "update":
650
- result.summary.updated++;
651
- break;
652
- case "skip":
653
- result.summary.skipped++;
654
- break;
655
- case "error":
656
- result.summary.failed++;
657
- result.errors.push(itemResult.error || "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958");
658
- break;
659
- }
660
- printItemResult(itemResult, options.verbose);
661
- }
662
- if (!options.dryRun) {
663
- spinner.start("\uB9E4\uD551 \uD30C\uC77C \uC800\uC7A5 \uC911...");
664
- await mapper.save();
665
- spinner.succeed("\uB9E4\uD551 \uD30C\uC77C \uC800\uC7A5 \uC644\uB8CC");
666
- }
667
- printSummary(result);
668
- } catch (error) {
669
- spinner.fail(`\uC624\uB958 \uBC1C\uC0DD: ${error.message}`);
670
- result.success = false;
671
- result.errors.push(error.message);
672
- }
673
- }
674
- async function syncWorkItem(workItem, mapper, client, projectInfo, options) {
675
- const mapping = mapper.findByWiId(workItem.wiId);
676
- const targetStatus = statusMap[workItem.status];
677
- const statusOptionId = targetStatus === "Done" ? projectInfo.statusField.options.done : targetStatus === "In Progress" ? projectInfo.statusField.options.inProgress : projectInfo.statusField.options.todo;
678
- try {
679
- if (!mapping) {
680
- if (options.dryRun) {
681
- return {
682
- wiId: workItem.wiId,
683
- action: "create",
684
- newStatus: workItem.status
685
- };
686
- }
687
- const issue = await client.createIssue(
688
- generateIssueTitle(workItem),
689
- generateIssueBody(workItem)
690
- );
691
- const itemId = await client.addIssueToProject(projectInfo.id, issue.id);
692
- await client.updateItemStatus(
693
- projectInfo.id,
694
- itemId,
695
- projectInfo.statusField.id,
696
- statusOptionId
697
- );
698
- mapper.upsertMapping(workItem.wiId, issue.number, itemId, workItem.status);
699
- return {
700
- wiId: workItem.wiId,
701
- action: "create",
702
- newStatus: workItem.status,
703
- issueNumber: issue.number,
704
- projectItemId: itemId
705
- };
706
- } else {
707
- const previousStatus = mapping.lastStatus;
708
- if (previousStatus === workItem.status) {
709
- return {
710
- wiId: workItem.wiId,
711
- action: "skip",
712
- previousStatus,
713
- newStatus: workItem.status,
714
- issueNumber: mapping.issueNumber,
715
- projectItemId: mapping.projectItemId
716
- };
717
- }
718
- if (options.dryRun) {
719
- return {
720
- wiId: workItem.wiId,
721
- action: "update",
722
- previousStatus,
723
- newStatus: workItem.status,
724
- issueNumber: mapping.issueNumber
725
- };
726
- }
727
- let itemId = mapping.projectItemId;
728
- if (!itemId) {
729
- const issue = await client.getIssue(mapping.issueNumber);
730
- if (issue) {
731
- itemId = await client.addIssueToProject(projectInfo.id, issue.id);
732
- }
733
- }
734
- if (itemId) {
735
- await client.updateItemStatus(
736
- projectInfo.id,
737
- itemId,
738
- projectInfo.statusField.id,
739
- statusOptionId
740
- );
741
- mapper.upsertMapping(workItem.wiId, mapping.issueNumber, itemId, workItem.status);
742
- }
743
- return {
744
- wiId: workItem.wiId,
745
- action: "update",
746
- previousStatus,
747
- newStatus: workItem.status,
748
- issueNumber: mapping.issueNumber,
749
- projectItemId: itemId
750
- };
751
- }
752
- } catch (error) {
753
- return {
754
- wiId: workItem.wiId,
755
- action: "error",
756
- newStatus: workItem.status,
757
- error: error.message
758
- };
759
- }
760
- }
761
- function printItemResult(result, verbose) {
762
- const statusIcons = {
763
- create: chalk.green("+"),
764
- update: chalk.yellow("~"),
765
- skip: chalk.gray("-"),
766
- error: chalk.red("\u2717")
767
- };
768
- const icon = statusIcons[result.action];
769
- const wiId = chalk.cyan(result.wiId);
770
- const status = result.newStatus;
771
- let message = `${icon} ${wiId} ${status}`;
772
- if (result.action === "update" && result.previousStatus) {
773
- message += chalk.gray(` (${result.previousStatus} \u2192 ${result.newStatus})`);
774
- }
775
- if (result.action === "create" && result.issueNumber) {
776
- message += chalk.gray(` \u2192 Issue #${result.issueNumber}`);
777
- }
778
- if (result.action === "error") {
779
- message += chalk.red(` - ${result.error}`);
780
- }
781
- console.log(` ${message}`);
782
- }
783
- function printSummary(result) {
784
- console.log(chalk.blue("\n\u{1F4CA} \uB3D9\uAE30\uD654 \uACB0\uACFC\n"));
785
- console.log(` \uC804\uCCB4: ${result.summary.total}`);
786
- console.log(chalk.green(` \uC0DD\uC131: ${result.summary.created}`));
787
- console.log(chalk.yellow(` \uC5C5\uB370\uC774\uD2B8: ${result.summary.updated}`));
788
- console.log(chalk.gray(` \uC2A4\uD0B5: ${result.summary.skipped}`));
789
- if (result.summary.failed > 0) {
790
- console.log(chalk.red(` \uC2E4\uD328: ${result.summary.failed}`));
791
- }
792
- console.log();
793
- }
794
- async function showSyncStatus() {
795
- const spinner = ora();
796
- try {
797
- spinner.start("\uC0C1\uD0DC \uD655\uC778 \uC911...");
798
- const mapper = new WiMapper(path2.resolve(DEFAULT_MAPPING_PATH));
799
- await mapper.load();
800
- const todoPath = path2.resolve(DEFAULT_TODO_PATH);
801
- if (!await fs3.pathExists(todoPath)) {
802
- throw new Error(`project-todo.md\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${todoPath}`);
803
- }
804
- const parseResult = await parseProjectTodo(todoPath);
805
- const workItems = extractAllWorkItems(parseResult);
806
- spinner.succeed("\uC0C1\uD0DC \uD655\uC778 \uC644\uB8CC");
807
- console.log(chalk.blue("\n\u{1F4CB} \uB3D9\uAE30\uD654 \uC0C1\uD0DC\n"));
808
- const { owner, repo } = mapper.getRepository();
809
- const projectNumber = mapper.getProjectNumber();
810
- console.log(chalk.gray(`\uC800\uC7A5\uC18C: ${owner}/${repo}`));
811
- console.log(chalk.gray(`\uD504\uB85C\uC81D\uD2B8: #${projectNumber}`));
812
- console.log();
813
- console.log(chalk.bold("Work Items:"));
814
- let synced = 0;
815
- let unsynced = 0;
816
- for (const wi of workItems) {
817
- const mapping = mapper.findByWiId(wi.wiId);
818
- const icon = mapping ? chalk.green("\u2713") : chalk.yellow("\u25CB");
819
- const issueInfo = mapping ? chalk.gray(` \u2192 #${mapping.issueNumber}`) : chalk.yellow(" (\uBBF8\uB3D9\uAE30\uD654)");
820
- console.log(` ${icon} ${chalk.cyan(wi.wiId)} ${wi.status} ${wi.title}${issueInfo}`);
821
- if (mapping) {
822
- synced++;
823
- } else {
824
- unsynced++;
825
- }
826
- }
827
- console.log();
828
- console.log(chalk.gray(`\uB3D9\uAE30\uD654\uB428: ${synced}, \uBBF8\uB3D9\uAE30\uD654: ${unsynced}`));
829
- console.log();
830
- } catch (error) {
831
- spinner.fail(`\uC624\uB958: ${error.message}`);
832
- }
833
- }
834
- async function initSync(owner, repo, projectNumber) {
835
- const spinner = ora();
836
- try {
837
- spinner.start("\uC124\uC815 \uCD08\uAE30\uD654 \uC911...");
838
- const mappingPath = path2.resolve(DEFAULT_MAPPING_PATH);
839
- const mapping = {
840
- version: "1.0.0",
841
- repository: { owner, repo },
842
- project: { number: projectNumber },
843
- mappings: [],
844
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
845
- };
846
- await fs3.ensureDir(path2.dirname(mappingPath));
847
- await fs3.writeJSON(mappingPath, mapping, { spaces: 2 });
848
- spinner.succeed("\uC124\uC815 \uCD08\uAE30\uD654 \uC644\uB8CC");
849
- console.log(chalk.blue("\n\u2713 \uB3D9\uAE30\uD654 \uC124\uC815 \uC644\uB8CC\n"));
850
- console.log(chalk.gray(` \uB9E4\uD551 \uD30C\uC77C: ${DEFAULT_MAPPING_PATH}`));
851
- console.log(chalk.gray(` \uC800\uC7A5\uC18C: ${owner}/${repo}`));
852
- console.log(chalk.gray(` \uD504\uB85C\uC81D\uD2B8: #${projectNumber}`));
853
- console.log();
854
- console.log(chalk.cyan("\uB2E4\uC74C \uBA85\uB839\uC5B4\uB85C \uB3D9\uAE30\uD654\uB97C \uC2DC\uC791\uD558\uC138\uC694:"));
855
- console.log(chalk.cyan(" npx axis-cli sync"));
856
- console.log();
857
- } catch (error) {
858
- spinner.fail(`\uC624\uB958: ${error.message}`);
859
- }
860
- }
861
-
862
- export {
863
- parseProjectTodo,
864
- parseContent,
865
- extractAllWorkItems,
866
- filterByWiIds,
867
- GitHubClient,
868
- WiMapper,
869
- generateIssueTitle,
870
- generateIssueBody,
871
- STATUS_MAP,
872
- registerSyncCommand
873
- };