npm-cli-gh-issue-preparator 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 (91) hide show
  1. package/.env.example +0 -0
  2. package/.eslintrc.cjs +65 -0
  3. package/.github/CODEOWNERS +2 -0
  4. package/.github/workflows/commit-lint.yml +52 -0
  5. package/.github/workflows/configs/commitlint.config.js +27 -0
  6. package/.github/workflows/create-pr.yml +66 -0
  7. package/.github/workflows/empty-format-test-job.yml +28 -0
  8. package/.github/workflows/format.yml +25 -0
  9. package/.github/workflows/publish.yml +47 -0
  10. package/.github/workflows/test.yml +38 -0
  11. package/.github/workflows/umino-project.yml +191 -0
  12. package/.prettierignore +22 -0
  13. package/.prettierrc +5 -0
  14. package/CHANGELOG.md +27 -0
  15. package/CONTRIBUTING.md +107 -0
  16. package/README.md +49 -0
  17. package/bin/adapter/entry-points/cli/index.js +72 -0
  18. package/bin/adapter/entry-points/cli/index.js.map +1 -0
  19. package/bin/adapter/repositories/GitHubIssueRepository.js +340 -0
  20. package/bin/adapter/repositories/GitHubIssueRepository.js.map +1 -0
  21. package/bin/adapter/repositories/GitHubProjectRepository.js +123 -0
  22. package/bin/adapter/repositories/GitHubProjectRepository.js.map +1 -0
  23. package/bin/adapter/repositories/NodeLocalCommandRunner.js +34 -0
  24. package/bin/adapter/repositories/NodeLocalCommandRunner.js.map +1 -0
  25. package/bin/domain/entities/Issue.js +3 -0
  26. package/bin/domain/entities/Issue.js.map +1 -0
  27. package/bin/domain/entities/Project.js +3 -0
  28. package/bin/domain/entities/Project.js.map +1 -0
  29. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +37 -0
  30. package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -0
  31. package/bin/domain/usecases/StartPreparationUseCase.js +31 -0
  32. package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -0
  33. package/bin/domain/usecases/adapter-interfaces/IssueRepository.js +3 -0
  34. package/bin/domain/usecases/adapter-interfaces/IssueRepository.js.map +1 -0
  35. package/bin/domain/usecases/adapter-interfaces/LocalCommandRunner.js +3 -0
  36. package/bin/domain/usecases/adapter-interfaces/LocalCommandRunner.js.map +1 -0
  37. package/bin/domain/usecases/adapter-interfaces/ProjectRepository.js +3 -0
  38. package/bin/domain/usecases/adapter-interfaces/ProjectRepository.js.map +1 -0
  39. package/bin/index.js +6 -0
  40. package/bin/index.js.map +1 -0
  41. package/commitlint.config.js +6 -0
  42. package/jest.config.js +33 -0
  43. package/package.json +75 -0
  44. package/renovate.json +37 -0
  45. package/src/adapter/entry-points/cli/index.integration.test.ts +143 -0
  46. package/src/adapter/entry-points/cli/index.test.ts +165 -0
  47. package/src/adapter/entry-points/cli/index.ts +110 -0
  48. package/src/adapter/repositories/GitHubIssueRepository.integration.test.ts +50 -0
  49. package/src/adapter/repositories/GitHubIssueRepository.test.ts +996 -0
  50. package/src/adapter/repositories/GitHubIssueRepository.ts +470 -0
  51. package/src/adapter/repositories/GitHubProjectRepository.test.ts +252 -0
  52. package/src/adapter/repositories/GitHubProjectRepository.ts +162 -0
  53. package/src/adapter/repositories/NodeLocalCommandRunner.test.ts +80 -0
  54. package/src/adapter/repositories/NodeLocalCommandRunner.ts +37 -0
  55. package/src/domain/entities/Issue.ts +7 -0
  56. package/src/domain/entities/Project.ts +7 -0
  57. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +109 -0
  58. package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +48 -0
  59. package/src/domain/usecases/StartPreparationUseCase.test.ts +150 -0
  60. package/src/domain/usecases/StartPreparationUseCase.ts +48 -0
  61. package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +8 -0
  62. package/src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts +7 -0
  63. package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +5 -0
  64. package/src/index.test.ts +7 -0
  65. package/src/index.ts +3 -0
  66. package/tsconfig.build.json +11 -0
  67. package/tsconfig.json +16 -0
  68. package/types/adapter/entry-points/cli/index.d.ts +5 -0
  69. package/types/adapter/entry-points/cli/index.d.ts.map +1 -0
  70. package/types/adapter/repositories/GitHubIssueRepository.d.ts +14 -0
  71. package/types/adapter/repositories/GitHubIssueRepository.d.ts.map +1 -0
  72. package/types/adapter/repositories/GitHubProjectRepository.d.ts +9 -0
  73. package/types/adapter/repositories/GitHubProjectRepository.d.ts.map +1 -0
  74. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts +9 -0
  75. package/types/adapter/repositories/NodeLocalCommandRunner.d.ts.map +1 -0
  76. package/types/domain/entities/Issue.d.ts +8 -0
  77. package/types/domain/entities/Issue.d.ts.map +1 -0
  78. package/types/domain/entities/Project.d.ts +8 -0
  79. package/types/domain/entities/Project.d.ts.map +1 -0
  80. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts +20 -0
  81. package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -0
  82. package/types/domain/usecases/StartPreparationUseCase.d.ts +17 -0
  83. package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -0
  84. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +8 -0
  85. package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -0
  86. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts +8 -0
  87. package/types/domain/usecases/adapter-interfaces/LocalCommandRunner.d.ts.map +1 -0
  88. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +5 -0
  89. package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -0
  90. package/types/index.d.ts +3 -0
  91. package/types/index.d.ts.map +1 -0
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GitHubProjectRepository = void 0;
4
+ function isGitHubApiResponse(value) {
5
+ if (typeof value !== 'object' || value === null)
6
+ return false;
7
+ return true;
8
+ }
9
+ class GitHubProjectRepository {
10
+ constructor(token) {
11
+ this.token = token;
12
+ }
13
+ parseGitHubProjectUrl(url) {
14
+ const orgMatch = url.match(/github\.com\/orgs\/([^/]+)\/projects\/(\d+)/);
15
+ if (orgMatch) {
16
+ return {
17
+ owner: orgMatch[1],
18
+ projectNumber: orgMatch[2],
19
+ };
20
+ }
21
+ const userMatch = url.match(/github\.com\/users\/([^/]+)\/projects\/(\d+)/);
22
+ if (userMatch) {
23
+ return {
24
+ owner: userMatch[1],
25
+ projectNumber: userMatch[2],
26
+ };
27
+ }
28
+ const repoMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/projects\/(\d+)/);
29
+ if (repoMatch) {
30
+ return {
31
+ owner: repoMatch[1],
32
+ projectNumber: repoMatch[3],
33
+ };
34
+ }
35
+ throw new Error(`Invalid GitHub project URL: ${url}`);
36
+ }
37
+ async getByUrl(url) {
38
+ const { owner, projectNumber } = this.parseGitHubProjectUrl(url);
39
+ const projectQuery = `
40
+ query($owner: String!, $number: Int!) {
41
+ organization(login: $owner) {
42
+ projectV2(number: $number) {
43
+ id
44
+ title
45
+ url
46
+ fields(first: 100) {
47
+ nodes {
48
+ ... on ProjectV2SingleSelectField {
49
+ name
50
+ options {
51
+ name
52
+ }
53
+ }
54
+ ... on ProjectV2Field {
55
+ name
56
+ }
57
+ }
58
+ }
59
+ }
60
+ }
61
+ user(login: $owner) {
62
+ projectV2(number: $number) {
63
+ id
64
+ title
65
+ url
66
+ fields(first: 100) {
67
+ nodes {
68
+ ... on ProjectV2SingleSelectField {
69
+ name
70
+ options {
71
+ name
72
+ }
73
+ }
74
+ ... on ProjectV2Field {
75
+ name
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ `;
83
+ const response = await fetch('https://api.github.com/graphql', {
84
+ method: 'POST',
85
+ headers: {
86
+ Authorization: `Bearer ${this.token}`,
87
+ 'Content-Type': 'application/json',
88
+ },
89
+ body: JSON.stringify({
90
+ query: projectQuery,
91
+ variables: {
92
+ owner,
93
+ number: parseInt(projectNumber, 10),
94
+ },
95
+ }),
96
+ });
97
+ if (!response.ok) {
98
+ const errorText = await response.text();
99
+ throw new Error(`GitHub API error: ${errorText}`);
100
+ }
101
+ const responseData = await response.json();
102
+ if (!isGitHubApiResponse(responseData)) {
103
+ throw new Error('Invalid API response format');
104
+ }
105
+ const result = responseData;
106
+ const project = result.data?.organization?.projectV2 || result.data?.user?.projectV2;
107
+ if (!project) {
108
+ throw new Error(`Project not found: ${url}`);
109
+ }
110
+ const fields = project.fields.nodes;
111
+ const statusField = fields.find((f) => f.name === 'Status');
112
+ const statuses = statusField?.options?.map((o) => o.name) || [];
113
+ return {
114
+ id: project.id,
115
+ url: project.url,
116
+ name: project.title,
117
+ statuses,
118
+ customFieldNames: fields.map((f) => f.name),
119
+ };
120
+ }
121
+ }
122
+ exports.GitHubProjectRepository = GitHubProjectRepository;
123
+ //# sourceMappingURL=GitHubProjectRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GitHubProjectRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubProjectRepository.ts"],"names":[],"mappings":";;;AA4BA,SAAS,mBAAmB,CAAC,KAAc;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAa,uBAAuB;IAClC,YAA6B,KAAa;QAAb,UAAK,GAAL,KAAK,CAAQ;IAAG,CAAC;IAEtC,qBAAqB,CAAC,GAAW;QAIvC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;QAC1E,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO;gBACL,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;gBAClB,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC;aAC3B,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAC5E,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;gBACnB,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;aAC5B,CAAC;QACJ,CAAC;QAED,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CACzB,gDAAgD,CACjD,CAAC;QACF,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC;gBACnB,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC;aAC5B,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,+BAA+B,GAAG,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,GAAW;QACxB,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAEjE,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2CpB,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,gCAAgC,EAAE;YAC7D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;gBACrC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK,EAAE,YAAY;gBACnB,SAAS,EAAE;oBACT,KAAK;oBACL,MAAM,EAAE,QAAQ,CAAC,aAAa,EAAE,EAAE,CAAC;iBACpC;aACF,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CAAC,qBAAqB,SAAS,EAAE,CAAC,CAAC;QACpD,CAAC;QAED,MAAM,YAAY,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpD,IAAI,CAAC,mBAAmB,CAAC,YAAY,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,MAAM,GAAsB,YAAY,CAAC;QAC/C,MAAM,OAAO,GACX,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,IAAI,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC;QACvE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;QACpC,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAa,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAE1E,OAAO;YACL,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,IAAI,EAAE,OAAO,CAAC,KAAK;YACnB,QAAQ;YACR,gBAAgB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SAC5C,CAAC;IACJ,CAAC;CACF;AAhID,0DAgIC"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NodeLocalCommandRunner = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const util_1 = require("util");
6
+ const execAsync = (0, util_1.promisify)(child_process_1.exec);
7
+ class NodeLocalCommandRunner {
8
+ async runCommand(command) {
9
+ try {
10
+ const { stdout, stderr } = await execAsync(command);
11
+ return {
12
+ stdout,
13
+ stderr,
14
+ exitCode: 0,
15
+ };
16
+ }
17
+ catch (error) {
18
+ if (error &&
19
+ typeof error === 'object' &&
20
+ 'stdout' in error &&
21
+ 'stderr' in error &&
22
+ 'code' in error) {
23
+ return {
24
+ stdout: String(error.stdout),
25
+ stderr: String(error.stderr),
26
+ exitCode: typeof error.code === 'number' ? error.code : 1,
27
+ };
28
+ }
29
+ throw error;
30
+ }
31
+ }
32
+ }
33
+ exports.NodeLocalCommandRunner = NodeLocalCommandRunner;
34
+ //# sourceMappingURL=NodeLocalCommandRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodeLocalCommandRunner.js","sourceRoot":"","sources":["../../../src/adapter/repositories/NodeLocalCommandRunner.ts"],"names":[],"mappings":";;;AACA,iDAAqC;AACrC,+BAAiC;AAEjC,MAAM,SAAS,GAAG,IAAA,gBAAS,EAAC,oBAAI,CAAC,CAAC;AAElC,MAAa,sBAAsB;IACjC,KAAK,CAAC,UAAU,CAAC,OAAe;QAK9B,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;YACpD,OAAO;gBACL,MAAM;gBACN,MAAM;gBACN,QAAQ,EAAE,CAAC;aACZ,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,KAAK;gBACL,OAAO,KAAK,KAAK,QAAQ;gBACzB,QAAQ,IAAI,KAAK;gBACjB,QAAQ,IAAI,KAAK;gBACjB,MAAM,IAAI,KAAK,EACf,CAAC;gBACD,OAAO;oBACL,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;oBAC5B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;oBAC5B,QAAQ,EAAE,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;iBAC1D,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF;AA9BD,wDA8BC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=Issue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Issue.js","sourceRoot":"","sources":["../../../src/domain/entities/Issue.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=Project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Project.js","sourceRoot":"","sources":["../../../src/domain/entities/Project.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NotifyFinishedIssuePreparationUseCase = exports.IllegalIssueStatusError = exports.IssueNotFoundError = void 0;
4
+ class IssueNotFoundError extends Error {
5
+ constructor(issueUrl) {
6
+ super(`Issue not found: ${issueUrl}`);
7
+ this.name = 'IssueNotFoundError';
8
+ }
9
+ }
10
+ exports.IssueNotFoundError = IssueNotFoundError;
11
+ class IllegalIssueStatusError extends Error {
12
+ constructor(issueUrl, currentStatus, expectedStatus) {
13
+ super(`Illegal issue status for ${issueUrl}: expected ${expectedStatus}, but got ${currentStatus}`);
14
+ this.name = 'IllegalIssueStatusError';
15
+ }
16
+ }
17
+ exports.IllegalIssueStatusError = IllegalIssueStatusError;
18
+ class NotifyFinishedIssuePreparationUseCase {
19
+ constructor(projectRepository, issueRepository) {
20
+ this.projectRepository = projectRepository;
21
+ this.issueRepository = issueRepository;
22
+ this.run = async (params) => {
23
+ const project = await this.projectRepository.getByUrl(params.projectUrl);
24
+ const issue = await this.issueRepository.get(params.issueUrl, project);
25
+ if (!issue) {
26
+ throw new IssueNotFoundError(params.issueUrl);
27
+ }
28
+ else if (issue.status !== params.preparationStatus) {
29
+ throw new IllegalIssueStatusError(params.issueUrl, issue.status, params.preparationStatus);
30
+ }
31
+ issue.status = params.awaitingQualityCheckStatus;
32
+ await this.issueRepository.update(issue, project);
33
+ };
34
+ }
35
+ }
36
+ exports.NotifyFinishedIssuePreparationUseCase = NotifyFinishedIssuePreparationUseCase;
37
+ //# sourceMappingURL=NotifyFinishedIssuePreparationUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotifyFinishedIssuePreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts"],"names":[],"mappings":";;;AAGA,MAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,QAAgB;QAC1B,KAAK,CAAC,oBAAoB,QAAQ,EAAE,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AALD,gDAKC;AACD,MAAa,uBAAwB,SAAQ,KAAK;IAChD,YAAY,QAAgB,EAAE,aAAqB,EAAE,cAAsB;QACzE,KAAK,CACH,4BAA4B,QAAQ,cAAc,cAAc,aAAa,aAAa,EAAE,CAC7F,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAPD,0DAOC;AAED,MAAa,qCAAqC;IAChD,YACmB,iBAAoC,EACpC,eAAgC;QADhC,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,oBAAe,GAAf,eAAe,CAAiB;QAGnD,QAAG,GAAG,KAAK,EAAE,MAKZ,EAAiB,EAAE;YAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAEzE,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;YAEvE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,IAAI,kBAAkB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChD,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,iBAAiB,EAAE,CAAC;gBACrD,MAAM,IAAI,uBAAuB,CAC/B,MAAM,CAAC,QAAQ,EACf,KAAK,CAAC,MAAM,EACZ,MAAM,CAAC,iBAAiB,CACzB,CAAC;YACJ,CAAC;YAED,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,0BAA0B,CAAC;YACjD,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACpD,CAAC,CAAC;IAxBC,CAAC;CAyBL;AA7BD,sFA6BC"}
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StartPreparationUseCase = void 0;
4
+ class StartPreparationUseCase {
5
+ constructor(projectRepository, issueRepository, localCommandRunner) {
6
+ this.projectRepository = projectRepository;
7
+ this.issueRepository = issueRepository;
8
+ this.localCommandRunner = localCommandRunner;
9
+ this.maximumPreparingIssuesCount = 6;
10
+ this.run = async (params) => {
11
+ const project = await this.projectRepository.getByUrl(params.projectUrl);
12
+ const allIssues = await this.issueRepository.getAllOpened(project);
13
+ const awaitingWorkspaceIssues = allIssues.filter((issue) => issue.status === params.awaitingWorkspaceStatus);
14
+ if (allIssues.filter((issue) => issue.status === params.preparationStatus)
15
+ .length >= this.maximumPreparingIssuesCount) {
16
+ return;
17
+ }
18
+ for (const issue of awaitingWorkspaceIssues) {
19
+ const agent = issue.labels
20
+ .find((label) => label.startsWith('category:'))
21
+ ?.replace('category:', '')
22
+ .trim() || params.defaultAgentName;
23
+ issue.status = params.preparationStatus;
24
+ await this.issueRepository.update(issue, project);
25
+ await this.localCommandRunner.runCommand(`aw ${project.url} ${issue.url} ${agent}`);
26
+ }
27
+ };
28
+ }
29
+ }
30
+ exports.StartPreparationUseCase = StartPreparationUseCase;
31
+ //# sourceMappingURL=StartPreparationUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,uBAAuB;IAElC,YACmB,iBAAoC,EACpC,eAAgC,EAChC,kBAAsC;QAFtC,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,oBAAe,GAAf,eAAe,CAAiB;QAChC,uBAAkB,GAAlB,kBAAkB,CAAoB;QAJzD,gCAA2B,GAAG,CAAC,CAAC;QAOhC,QAAG,GAAG,KAAK,EAAE,MAKZ,EAAiB,EAAE;YAClB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAEzE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YAEnE,MAAM,uBAAuB,GAAG,SAAS,CAAC,MAAM,CAC9C,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,uBAAuB,CAC3D,CAAC;YAEF,IACE,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,iBAAiB,CAAC;iBACnE,MAAM,IAAI,IAAI,CAAC,2BAA2B,EAC7C,CAAC;gBACD,OAAO;YACT,CAAC;YAED,KAAK,MAAM,KAAK,IAAI,uBAAuB,EAAE,CAAC;gBAC5C,MAAM,KAAK,GACT,KAAK,CAAC,MAAM;qBACT,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;oBAC/C,EAAE,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;qBACzB,IAAI,EAAE,IAAI,MAAM,CAAC,gBAAgB,CAAC;gBACvC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAC;gBACxC,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAElD,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CACtC,MAAM,OAAO,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,EAAE,CAC1C,CAAC;YACJ,CAAC;QACH,CAAC,CAAC;IApCC,CAAC;CAqCL;AA3CD,0DA2CC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=IssueRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IssueRepository.js","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/IssueRepository.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=LocalCommandRunner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalCommandRunner.js","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/LocalCommandRunner.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=ProjectRepository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProjectRepository.js","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/ProjectRepository.ts"],"names":[],"mappings":""}
package/bin/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.program = void 0;
4
+ const index_1 = require("./adapter/entry-points/cli/index");
5
+ Object.defineProperty(exports, "program", { enumerable: true, get: function () { return index_1.program; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,4DAA2D;AAElD,wFAFA,eAAO,OAEA"}
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ parserPreset: 'conventional-changelog-conventionalcommits',
3
+ rules: {
4
+ 'body-max-line-length': [2, 'always', 'Infinity'],
5
+ },
6
+ };
package/jest.config.js ADDED
@@ -0,0 +1,33 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ transform: {
5
+ '^.+\\.ts?$': 'ts-jest',
6
+ },
7
+ transformIgnorePatterns: ['<rootDir>/node_modules/'],
8
+ collectCoverage: true,
9
+ collectCoverageFrom: [
10
+ 'src/**/*.ts',
11
+ '!src/**/*.test.ts',
12
+ '!src/**/*.spec.ts',
13
+ '!src/**/adapter-interfaces/**',
14
+ ],
15
+ coverageDirectory: 'reports/coverage',
16
+ coverageThreshold: {
17
+ global: {
18
+ branches: 100,
19
+ functions: 100,
20
+ lines: 100,
21
+ statements: 100,
22
+ },
23
+ },
24
+ reporters: [
25
+ 'default',
26
+ ['jest-junit', { outputDirectory: 'reports/jest-junit' }],
27
+ [
28
+ './node_modules/jest-html-reporter',
29
+ { outputPath: 'reports/jest-html-reporter/index.html' },
30
+ ],
31
+ ],
32
+ testPathIgnorePatterns: ['/node_modules/', '/bin/', '/dist/'],
33
+ };
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "npm-cli-gh-issue-preparator",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "bin/index.js",
6
+ "scripts": {
7
+ "prebuild": "npm run doctor",
8
+ "doctor:node-version": "check-node-version --node 25 --npx 11 --npm 11",
9
+ "doctor": "npm run doctor:node-version",
10
+ "prettier": "prettier --write '**/*'",
11
+ "lint:eslint": "eslint src",
12
+ "lint:bypasscheck:test-skip": "! grep -r '\\.skip\\|describe\\.skip\\|it\\.skip\\|test\\.skip' src --include='*.test.ts' --include='*.spec.ts'",
13
+ "lint:bypasscheck:disable-checks": "! grep -r 'eslint-disable\\|@ts-ignore\\|@ts-nocheck\\|prettier-ignore' src --include='*.ts' --include='*.js' --exclude-dir=node_modules",
14
+ "lint:bypasscheck": "npm run lint:bypasscheck:test-skip && npm run lint:bypasscheck:disable-checks",
15
+ "lint": "npm run lint:eslint && npm run lint:bypasscheck",
16
+ "lint-fix": "eslint src --fix",
17
+ "fmt": "npm run prettier && npm run lint-fix",
18
+ "build": "tsc -p ./tsconfig.build.json",
19
+ "test": "jest"
20
+ },
21
+ "bin": {
22
+ "npm-cli-gh-issue-preparator": "./bin/adapter/entry-points/cli/index.js"
23
+ },
24
+ "types": "types/index.d.ts",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/HiromiShikata/npm-cli-gh-issue-preparator.git"
28
+ },
29
+ "keywords": [],
30
+ "author": "",
31
+ "license": "ISC",
32
+ "bugs": {
33
+ "url": "https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/issues"
34
+ },
35
+ "release": {
36
+ "plugins": [
37
+ "@semantic-release/commit-analyzer",
38
+ "@semantic-release/release-notes-generator",
39
+ "@semantic-release/changelog",
40
+ "@semantic-release/npm"
41
+ ],
42
+ "branches": [
43
+ "main"
44
+ ]
45
+ },
46
+ "homepage": "https://github.com/HiromiShikata/npm-cli-gh-issue-preparator#readme",
47
+ "devDependencies": {
48
+ "@semantic-release/changelog": "^6.0.3",
49
+ "@semantic-release/commit-analyzer": "^13.0.0",
50
+ "@types/jest": "^30.0.0",
51
+ "@typescript-eslint/eslint-plugin": "^7.2.0",
52
+ "@typescript-eslint/parser": "^7.2.0",
53
+ "check-node-version": "^4.2.1",
54
+ "commitlint": "^20.0.0",
55
+ "conventional-changelog-conventionalcommits": "^9.0.0",
56
+ "eslint": "^8.57.0",
57
+ "eslint-plugin-import": "^2.29.1",
58
+ "eslint-plugin-jest": "^28.14.0",
59
+ "eslint-plugin-no-type-assertion": "^1.3.0",
60
+ "eslint-plugin-unused-imports": "^3.1.0",
61
+ "jest": "^30.0.0",
62
+ "jest-html-reporter": "^4.0.0",
63
+ "jest-junit": "^16.0.0",
64
+ "jest-mock-extended": "^4.0.0",
65
+ "prettier": "^3.2.5",
66
+ "semantic-release": "^25.0.0",
67
+ "ts-jest": "^29.1.2",
68
+ "typescript": "^5.4.2"
69
+ },
70
+ "dependencies": {
71
+ "@semantic-release/npm": "^13.0.0",
72
+ "commander": "^14.0.0",
73
+ "dotenv": "^16.4.5"
74
+ }
75
+ }
package/renovate.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": ["config:base"],
4
+ "automerge": true,
5
+ "platformAutomerge": true,
6
+ "rebaseWhen": "auto",
7
+ "prConcurrentLimit": 2,
8
+ "schedule": ["every weekend"],
9
+ "ignorePresets": [],
10
+ "dependencyDashboard": false,
11
+ "branchConcurrentLimit": 2,
12
+ "ignorePaths": ["**/generated/*", "**/_gen/*"],
13
+ "packageRules": [
14
+ {
15
+ "matchPackagePatterns": ["*"],
16
+ "matchUpdateTypes": ["minor", "patch"],
17
+ "groupName": "all non-major dependencies",
18
+ "groupSlug": "all-minor-patch"
19
+ },
20
+ {
21
+ "matchPackageNames": ["eslint"],
22
+ "allowedVersions": "<9.0.0"
23
+ },
24
+ {
25
+ "matchPackageNames": ["@typescript-eslint/eslint-plugin"],
26
+ "allowedVersions": "<6.0.0"
27
+ },
28
+ {
29
+ "matchPackageNames": ["@typescript-eslint/parser"],
30
+ "allowedVersions": "<6.0.0"
31
+ },
32
+ {
33
+ "matchPackageNames": ["eslint-plugin-unused-imports"],
34
+ "allowedVersions": "<4.0.0"
35
+ }
36
+ ]
37
+ }
@@ -0,0 +1,143 @@
1
+ import { execSync } from 'child_process';
2
+ import dotenv from 'dotenv';
3
+ import { GitHubIssueRepository } from '../../repositories/GitHubIssueRepository';
4
+ import { GitHubProjectRepository } from '../../repositories/GitHubProjectRepository';
5
+
6
+ dotenv.config();
7
+
8
+ describe('index', () => {
9
+ const token = process.env.GH_TOKEN;
10
+ const projectUrl = 'https://github.com/users/HiromiShikata/projects/49';
11
+ const startDaemonIssueUrl =
12
+ 'https://github.com/HiromiShikata/test-repository/issues/1552';
13
+ const notifyFinishedIssueUrl =
14
+ 'https://github.com/HiromiShikata/test-repository/issues/1557';
15
+
16
+ describe('startDaemon', () => {
17
+ beforeAll(async () => {
18
+ if (!token) {
19
+ throw new Error('GH_TOKEN environment variable is required');
20
+ }
21
+
22
+ const issueRepository = new GitHubIssueRepository(token);
23
+ const projectRepository = new GitHubProjectRepository(token);
24
+ const project = await projectRepository.getByUrl(projectUrl);
25
+
26
+ const issue = await issueRepository.get(startDaemonIssueUrl, project);
27
+ if (!issue) {
28
+ throw new Error('Failed to get issue');
29
+ }
30
+ issue.status = 'Awaiting workspace';
31
+ await issueRepository.update(issue, project);
32
+ }, 60000);
33
+
34
+ afterAll(async () => {
35
+ if (!token) {
36
+ return;
37
+ }
38
+
39
+ const issueRepository = new GitHubIssueRepository(token);
40
+ const projectRepository = new GitHubProjectRepository(token);
41
+ const project = await projectRepository.getByUrl(projectUrl);
42
+
43
+ const issue = await issueRepository.get(startDaemonIssueUrl, project);
44
+ if (!issue) {
45
+ return;
46
+ }
47
+ issue.status = 'Awaiting workspace';
48
+ await issueRepository.update(issue, project);
49
+ });
50
+
51
+ it('success', async () => {
52
+ if (!token) {
53
+ throw new Error('GH_TOKEN environment variable is required');
54
+ }
55
+
56
+ const issueRepository = new GitHubIssueRepository(token);
57
+ const projectRepository = new GitHubProjectRepository(token);
58
+ const project = await projectRepository.getByUrl(projectUrl);
59
+
60
+ const beforeIssue = await issueRepository.get(
61
+ startDaemonIssueUrl,
62
+ project,
63
+ );
64
+ expect(beforeIssue?.status).toBe('Awaiting workspace');
65
+
66
+ const result = execSync(
67
+ 'npx ts-node ./src/adapter/entry-points/cli/index.ts startDaemon --projectUrl https://github.com/users/HiromiShikata/projects/49 --awaitingWorkspaceStatus "Awaiting workspace" --preparationStatus "Preparation" --defaultAgentName "impl"',
68
+ { encoding: 'utf-8', timeout: 600000 },
69
+ );
70
+ expect(result).toBeDefined();
71
+
72
+ const afterIssue = await issueRepository.get(
73
+ startDaemonIssueUrl,
74
+ project,
75
+ );
76
+ expect(afterIssue?.status).toBe('Preparation');
77
+ }, 600000);
78
+ });
79
+
80
+ describe('notifyFinishedIssuePreparation', () => {
81
+ beforeAll(async () => {
82
+ if (!token) {
83
+ throw new Error('GH_TOKEN environment variable is required');
84
+ }
85
+
86
+ const issueRepository = new GitHubIssueRepository(token);
87
+ const projectRepository = new GitHubProjectRepository(token);
88
+ const project = await projectRepository.getByUrl(projectUrl);
89
+
90
+ const issue = await issueRepository.get(notifyFinishedIssueUrl, project);
91
+ if (!issue) {
92
+ throw new Error('Failed to get issue');
93
+ }
94
+ issue.status = 'Preparation';
95
+ await issueRepository.update(issue, project);
96
+ }, 60000);
97
+
98
+ afterAll(async () => {
99
+ if (!token) {
100
+ return;
101
+ }
102
+
103
+ const issueRepository = new GitHubIssueRepository(token);
104
+ const projectRepository = new GitHubProjectRepository(token);
105
+ const project = await projectRepository.getByUrl(projectUrl);
106
+
107
+ const issue = await issueRepository.get(notifyFinishedIssueUrl, project);
108
+ if (!issue) {
109
+ return;
110
+ }
111
+ issue.status = 'Preparation';
112
+ await issueRepository.update(issue, project);
113
+ });
114
+
115
+ it('success', async () => {
116
+ if (!token) {
117
+ throw new Error('GH_TOKEN environment variable is required');
118
+ }
119
+
120
+ const issueRepository = new GitHubIssueRepository(token);
121
+ const projectRepository = new GitHubProjectRepository(token);
122
+ const project = await projectRepository.getByUrl(projectUrl);
123
+
124
+ const beforeIssue = await issueRepository.get(
125
+ notifyFinishedIssueUrl,
126
+ project,
127
+ );
128
+ expect(beforeIssue?.status).toBe('Preparation');
129
+
130
+ const result = execSync(
131
+ 'npx ts-node ./src/adapter/entry-points/cli/index.ts notifyFinishedIssuePreparation --projectUrl https://github.com/users/HiromiShikata/projects/49 --issueUrl https://github.com/HiromiShikata/test-repository/issues/1557 --preparationStatus "Preparation" --awaitingQualityCheckStatus "Awaiting quality check"',
132
+ { encoding: 'utf-8', timeout: 600000 },
133
+ );
134
+ expect(result).toBeDefined();
135
+
136
+ const afterIssue = await issueRepository.get(
137
+ notifyFinishedIssueUrl,
138
+ project,
139
+ );
140
+ expect(afterIssue?.status).toBe('Awaiting quality check');
141
+ }, 600000);
142
+ });
143
+ });