github-issue-tower-defence-management 1.146.3 → 1.146.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/codeql.yml +2 -2
- package/CHANGELOG.md +14 -0
- package/bin/adapter/entry-points/cli/index.js +13 -20
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleGithubTokenResolver.js +16 -1
- package/bin/adapter/entry-points/console/consoleGithubTokenResolver.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleProjectResolver.js +22 -1
- package/bin/adapter/entry-points/console/consoleProjectResolver.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-CPY6wV8C.js +82 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js +26 -100
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +49 -576
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +31 -7
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/package.json +6 -8
- package/src/adapter/entry-points/cli/index.ts +29 -24
- package/src/adapter/entry-points/console/consoleGithubTokenResolver.test.ts +68 -20
- package/src/adapter/entry-points/console/consoleGithubTokenResolver.ts +24 -0
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +2 -2
- package/src/adapter/entry-points/console/consoleProjectResolver.test.ts +91 -0
- package/src/adapter/entry-points/console/consoleProjectResolver.ts +32 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-CPY6wV8C.js +82 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/src/adapter/entry-points/console/webServer.test.ts +2 -2
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +31 -3
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +68 -3
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +35 -4
- package/src/domain/usecases/ProjectRequiredFieldCreateUseCase.test.ts +3 -3
- package/tsconfig.json +0 -5
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleGithubTokenResolver.d.ts +2 -0
- package/types/adapter/entry-points/console/consoleGithubTokenResolver.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleProjectResolver.d.ts +5 -0
- package/types/adapter/entry-points/console/consoleProjectResolver.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-Bmc_nF1d.js +0 -82
- package/src/adapter/entry-points/console/ui-dist/assets/index-Bmc_nF1d.js +0 -82
|
@@ -1,9 +1,55 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createConsoleGithubTokenResolver,
|
|
3
3
|
createConsoleIssueRepositoryResolver,
|
|
4
|
+
createConsoleProjectRepositoryResolver,
|
|
5
|
+
extractProjectOwner,
|
|
4
6
|
extractRepositoryOwner,
|
|
5
7
|
} from './consoleGithubTokenResolver';
|
|
6
8
|
|
|
9
|
+
describe('extractProjectOwner', () => {
|
|
10
|
+
it('should return the organization login of an organization project url', () => {
|
|
11
|
+
expect(
|
|
12
|
+
extractProjectOwner('https://github.com/orgs/acme/projects/18'),
|
|
13
|
+
).toBe('acme');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return the user login of a user project url', () => {
|
|
17
|
+
expect(
|
|
18
|
+
extractProjectOwner('https://github.com/users/acme-owner/projects/48'),
|
|
19
|
+
).toBe('acme-owner');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return null for a url that is not a project url', () => {
|
|
23
|
+
expect(
|
|
24
|
+
extractProjectOwner('https://github.com/acme/acme-repository/issues/178'),
|
|
25
|
+
).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('createConsoleProjectRepositoryResolver', () => {
|
|
30
|
+
it('builds the project repository from the token of the owner in the project url', () => {
|
|
31
|
+
const resolve = createConsoleProjectRepositoryResolver<string>(
|
|
32
|
+
(repositoryOwner) => `token-of-${repositoryOwner}`,
|
|
33
|
+
(githubToken) => `repository-with-${githubToken}`,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(resolve('https://github.com/orgs/acme/projects/18')).toBe(
|
|
37
|
+
'repository-with-token-of-acme',
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('throws when the project owner cannot be read from the project url', () => {
|
|
42
|
+
const resolve = createConsoleProjectRepositoryResolver<string>(
|
|
43
|
+
(repositoryOwner) => `token-of-${repositoryOwner}`,
|
|
44
|
+
(githubToken) => `repository-with-${githubToken}`,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
expect(() => resolve('https://github.com/orgs/acme')).toThrow(
|
|
48
|
+
'The project owner cannot be read from the project url: https://github.com/orgs/acme',
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
7
53
|
describe('createConsoleIssueRepositoryResolver', () => {
|
|
8
54
|
it('builds the issue repository from the token of the owner in the url', () => {
|
|
9
55
|
const resolve = createConsoleIssueRepositoryResolver<string>(
|
|
@@ -12,8 +58,8 @@ describe('createConsoleIssueRepositoryResolver', () => {
|
|
|
12
58
|
);
|
|
13
59
|
|
|
14
60
|
expect(
|
|
15
|
-
resolve('https://github.com/
|
|
16
|
-
).toBe('repository-with-token-of-
|
|
61
|
+
resolve('https://github.com/acme-labs/acme-portal-mock/issues/178'),
|
|
62
|
+
).toBe('repository-with-token-of-acme-labs');
|
|
17
63
|
});
|
|
18
64
|
|
|
19
65
|
it('throws when the repository owner cannot be read from the url', () => {
|
|
@@ -22,8 +68,10 @@ describe('createConsoleIssueRepositoryResolver', () => {
|
|
|
22
68
|
(githubToken) => `repository-with-${githubToken}`,
|
|
23
69
|
);
|
|
24
70
|
|
|
25
|
-
expect(() =>
|
|
26
|
-
'
|
|
71
|
+
expect(() =>
|
|
72
|
+
resolve('https://github.com/acme-labs/acme-portal-mock'),
|
|
73
|
+
).toThrow(
|
|
74
|
+
'The repository owner cannot be read from the operated url: https://github.com/acme-labs/acme-portal-mock',
|
|
27
75
|
);
|
|
28
76
|
});
|
|
29
77
|
});
|
|
@@ -32,9 +80,9 @@ describe('extractRepositoryOwner', () => {
|
|
|
32
80
|
it('should return the owner of an issue url', () => {
|
|
33
81
|
expect(
|
|
34
82
|
extractRepositoryOwner(
|
|
35
|
-
'https://github.com/
|
|
83
|
+
'https://github.com/acme-labs/acme-portal-mock/issues/178',
|
|
36
84
|
),
|
|
37
|
-
).toBe('
|
|
85
|
+
).toBe('acme-labs');
|
|
38
86
|
});
|
|
39
87
|
|
|
40
88
|
it('should return the owner of a pull request url', () => {
|
|
@@ -47,7 +95,7 @@ describe('extractRepositoryOwner', () => {
|
|
|
47
95
|
|
|
48
96
|
it('should return null for a url that is not an issue or a pull request', () => {
|
|
49
97
|
expect(
|
|
50
|
-
extractRepositoryOwner('https://github.com/
|
|
98
|
+
extractRepositoryOwner('https://github.com/acme-labs/acme-portal-mock'),
|
|
51
99
|
).toBeNull();
|
|
52
100
|
});
|
|
53
101
|
});
|
|
@@ -68,41 +116,41 @@ describe('createConsoleGithubTokenResolver', () => {
|
|
|
68
116
|
it('should return the token read from the file configured for that owner', () => {
|
|
69
117
|
const resolve = createConsoleGithubTokenResolver(
|
|
70
118
|
'default-token',
|
|
71
|
-
{ '
|
|
119
|
+
{ 'acme-labs': '/creds/acme-labs-token.txt' },
|
|
72
120
|
(filePath) =>
|
|
73
|
-
filePath === '/creds/
|
|
121
|
+
filePath === '/creds/acme-labs-token.txt'
|
|
74
122
|
? 'fine-grained-token\n'
|
|
75
123
|
: (() => {
|
|
76
124
|
throw new Error(`unexpected file path: ${filePath}`);
|
|
77
125
|
})(),
|
|
78
126
|
);
|
|
79
127
|
|
|
80
|
-
expect(resolve('
|
|
128
|
+
expect(resolve('acme-labs')).toBe('fine-grained-token');
|
|
81
129
|
});
|
|
82
130
|
|
|
83
131
|
it('should keep using the default token for the other owners when one owner has a token file', () => {
|
|
84
132
|
const resolve = createConsoleGithubTokenResolver(
|
|
85
133
|
'default-token',
|
|
86
|
-
{ '
|
|
134
|
+
{ 'acme-labs': '/creds/acme-labs-token.txt' },
|
|
87
135
|
() => 'fine-grained-token',
|
|
88
136
|
);
|
|
89
137
|
|
|
90
|
-
expect(resolve('
|
|
138
|
+
expect(resolve('globex-inc')).toBe('default-token');
|
|
91
139
|
});
|
|
92
140
|
|
|
93
141
|
it('should read the token file only once per owner', () => {
|
|
94
142
|
let readCount = 0;
|
|
95
143
|
const resolve = createConsoleGithubTokenResolver(
|
|
96
144
|
'default-token',
|
|
97
|
-
{ '
|
|
145
|
+
{ 'acme-labs': '/creds/acme-labs-token.txt' },
|
|
98
146
|
() => {
|
|
99
147
|
readCount += 1;
|
|
100
148
|
return 'fine-grained-token';
|
|
101
149
|
},
|
|
102
150
|
);
|
|
103
151
|
|
|
104
|
-
resolve('
|
|
105
|
-
resolve('
|
|
152
|
+
resolve('acme-labs');
|
|
153
|
+
resolve('acme-labs');
|
|
106
154
|
|
|
107
155
|
expect(readCount).toBe(1);
|
|
108
156
|
});
|
|
@@ -110,25 +158,25 @@ describe('createConsoleGithubTokenResolver', () => {
|
|
|
110
158
|
it('should throw when the configured token file contains no token', () => {
|
|
111
159
|
const resolve = createConsoleGithubTokenResolver(
|
|
112
160
|
'default-token',
|
|
113
|
-
{ '
|
|
161
|
+
{ 'acme-labs': '/creds/acme-labs-token.txt' },
|
|
114
162
|
() => ' \n',
|
|
115
163
|
);
|
|
116
164
|
|
|
117
|
-
expect(() => resolve('
|
|
118
|
-
'The GitHub token file configured for repository owner "
|
|
165
|
+
expect(() => resolve('acme-labs')).toThrow(
|
|
166
|
+
'The GitHub token file configured for repository owner "acme-labs" contains no token: /creds/acme-labs-token.txt',
|
|
119
167
|
);
|
|
120
168
|
});
|
|
121
169
|
|
|
122
170
|
it('should surface the read failure when the configured token file cannot be read', () => {
|
|
123
171
|
const resolve = createConsoleGithubTokenResolver(
|
|
124
172
|
'default-token',
|
|
125
|
-
{ '
|
|
173
|
+
{ 'acme-labs': '/creds/acme-labs-token.txt' },
|
|
126
174
|
() => {
|
|
127
175
|
throw new Error('ENOENT: no such file or directory');
|
|
128
176
|
},
|
|
129
177
|
);
|
|
130
178
|
|
|
131
|
-
expect(() => resolve('
|
|
179
|
+
expect(() => resolve('acme-labs')).toThrow(
|
|
132
180
|
'ENOENT: no such file or directory',
|
|
133
181
|
);
|
|
134
182
|
});
|
|
@@ -26,6 +26,30 @@ export const createConsoleIssueRepositoryResolver = <IssueRepositoryType>(
|
|
|
26
26
|
};
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
export const extractProjectOwner = (projectUrl: string): string | null => {
|
|
30
|
+
const match = projectUrl.match(
|
|
31
|
+
/^https:\/\/github\.com\/(?:orgs|users)\/([A-Za-z0-9._-]+)\/projects\/\d+/,
|
|
32
|
+
);
|
|
33
|
+
return match ? match[1] : null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const createConsoleProjectRepositoryResolver = <ProjectRepositoryType>(
|
|
37
|
+
resolveGithubToken: ConsoleGithubTokenResolver,
|
|
38
|
+
buildProjectRepositoryForToken: (
|
|
39
|
+
githubToken: string,
|
|
40
|
+
) => ProjectRepositoryType,
|
|
41
|
+
): ((projectUrl: string) => ProjectRepositoryType) => {
|
|
42
|
+
return (projectUrl: string): ProjectRepositoryType => {
|
|
43
|
+
const projectOwner = extractProjectOwner(projectUrl);
|
|
44
|
+
if (projectOwner === null) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`The project owner cannot be read from the project url: ${projectUrl}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return buildProjectRepositoryForToken(resolveGithubToken(projectOwner));
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
29
53
|
export const createConsoleGithubTokenResolver = (
|
|
30
54
|
defaultToken: string,
|
|
31
55
|
githubTokenFilePathByRepositoryOwner: Record<string, string> | null,
|
|
@@ -141,12 +141,12 @@ describe('consoleOperationApi', () => {
|
|
|
141
141
|
await handleReview(contextRecordingResolvedUrls, {
|
|
142
142
|
pjcode: 'acme',
|
|
143
143
|
action: 'approve',
|
|
144
|
-
prUrl: 'https://github.com/
|
|
144
|
+
prUrl: 'https://github.com/acme-labs/acme-portal-mock/pull/178',
|
|
145
145
|
projectItemId: 'PVTI_resolver',
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
expect(resolvedUrls).toContain(
|
|
149
|
-
'https://github.com/
|
|
149
|
+
'https://github.com/acme-labs/acme-portal-mock/pull/178',
|
|
150
150
|
);
|
|
151
151
|
});
|
|
152
152
|
|
|
@@ -2,6 +2,7 @@ import { mock } from 'jest-mock-extended';
|
|
|
2
2
|
import { Project } from '../../../domain/entities/Project';
|
|
3
3
|
import {
|
|
4
4
|
buildPjcodeToProjectUrl,
|
|
5
|
+
createConsoleProjectLoader,
|
|
5
6
|
createConsoleProjectResolver,
|
|
6
7
|
createPjcodeConfigChecker,
|
|
7
8
|
} from './consoleProjectResolver';
|
|
@@ -107,3 +108,93 @@ describe('createConsoleProjectResolver', () => {
|
|
|
107
108
|
expect(loadProject).toHaveBeenCalledTimes(1);
|
|
108
109
|
});
|
|
109
110
|
});
|
|
111
|
+
|
|
112
|
+
describe('createConsoleProjectLoader', () => {
|
|
113
|
+
const globexProjectOfLoader: Project = {
|
|
114
|
+
...mock<Project>(),
|
|
115
|
+
id: 'PVT_globex',
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const buildProjectRepositoryResolver = (
|
|
119
|
+
findProjectIdByUrl: (projectUrl: string) => Promise<string | null>,
|
|
120
|
+
getProject: (projectId: string) => Promise<Project | null>,
|
|
121
|
+
) =>
|
|
122
|
+
jest.fn((projectUrl: string) => {
|
|
123
|
+
if (projectUrl !== 'https://github.com/orgs/globex/projects/18') {
|
|
124
|
+
throw new Error(`unexpected project url: ${projectUrl}`);
|
|
125
|
+
}
|
|
126
|
+
return { findProjectIdByUrl, getProject };
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('resolves the project id through the repository chosen for the project url', async () => {
|
|
130
|
+
const resolveProjectRepository = buildProjectRepositoryResolver(
|
|
131
|
+
async () => 'PVT_globex',
|
|
132
|
+
async () => globexProjectOfLoader,
|
|
133
|
+
);
|
|
134
|
+
const loadProject = createConsoleProjectLoader(
|
|
135
|
+
resolveProjectRepository,
|
|
136
|
+
async () => null,
|
|
137
|
+
() => undefined,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
await expect(
|
|
141
|
+
loadProject('https://github.com/orgs/globex/projects/18'),
|
|
142
|
+
).resolves.toBe(globexProjectOfLoader);
|
|
143
|
+
expect(resolveProjectRepository).toHaveBeenCalledWith(
|
|
144
|
+
'https://github.com/orgs/globex/projects/18',
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('prefers the locally cached project over a GraphQL project load', async () => {
|
|
149
|
+
const getProject = jest.fn(async () => globexProjectOfLoader);
|
|
150
|
+
const loadProject = createConsoleProjectLoader(
|
|
151
|
+
buildProjectRepositoryResolver(async () => 'PVT_globex', getProject),
|
|
152
|
+
async (projectId: string) =>
|
|
153
|
+
projectId === 'PVT_globex' ? globexProjectOfLoader : null,
|
|
154
|
+
() => undefined,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
await expect(
|
|
158
|
+
loadProject('https://github.com/orgs/globex/projects/18'),
|
|
159
|
+
).resolves.toBe(globexProjectOfLoader);
|
|
160
|
+
expect(getProject).not.toHaveBeenCalled();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('reports and returns null when the project id cannot be resolved', async () => {
|
|
164
|
+
const reportedMessages: string[] = [];
|
|
165
|
+
const loadProject = createConsoleProjectLoader(
|
|
166
|
+
buildProjectRepositoryResolver(
|
|
167
|
+
async () => null,
|
|
168
|
+
async () => globexProjectOfLoader,
|
|
169
|
+
),
|
|
170
|
+
async () => null,
|
|
171
|
+
(message: string) => reportedMessages.push(message),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
await expect(
|
|
175
|
+
loadProject('https://github.com/orgs/globex/projects/18'),
|
|
176
|
+
).resolves.toBeNull();
|
|
177
|
+
expect(reportedMessages).toEqual([
|
|
178
|
+
'No project found for projectUrl https://github.com/orgs/globex/projects/18',
|
|
179
|
+
]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('reports and returns null when the project cannot be loaded', async () => {
|
|
183
|
+
const reportedMessages: string[] = [];
|
|
184
|
+
const loadProject = createConsoleProjectLoader(
|
|
185
|
+
buildProjectRepositoryResolver(
|
|
186
|
+
async () => 'PVT_globex',
|
|
187
|
+
async () => null,
|
|
188
|
+
),
|
|
189
|
+
async () => null,
|
|
190
|
+
(message: string) => reportedMessages.push(message),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
await expect(
|
|
194
|
+
loadProject('https://github.com/orgs/globex/projects/18'),
|
|
195
|
+
).resolves.toBeNull();
|
|
196
|
+
expect(reportedMessages).toEqual([
|
|
197
|
+
'Failed to load project for projectUrl https://github.com/orgs/globex/projects/18',
|
|
198
|
+
]);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -9,6 +9,38 @@ export type ConsoleProjectLoader = (
|
|
|
9
9
|
projectUrl: string,
|
|
10
10
|
) => Promise<Project | null>;
|
|
11
11
|
|
|
12
|
+
export type ConsoleProjectIdAndProjectReader = {
|
|
13
|
+
findProjectIdByUrl: (projectUrl: string) => Promise<string | null>;
|
|
14
|
+
getProject: (projectId: string) => Promise<Project | null>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const createConsoleProjectLoader = (
|
|
18
|
+
resolveProjectRepository: (
|
|
19
|
+
projectUrl: string,
|
|
20
|
+
) => ConsoleProjectIdAndProjectReader,
|
|
21
|
+
getCachedProject: (projectId: string) => Promise<Project | null>,
|
|
22
|
+
reportLoadFailure: (message: string) => void,
|
|
23
|
+
): ConsoleProjectLoader => {
|
|
24
|
+
return async (projectUrl: string): Promise<Project | null> => {
|
|
25
|
+
const projectRepository = resolveProjectRepository(projectUrl);
|
|
26
|
+
const projectId = await projectRepository.findProjectIdByUrl(projectUrl);
|
|
27
|
+
if (!projectId) {
|
|
28
|
+
reportLoadFailure(`No project found for projectUrl ${projectUrl}`);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const cachedProject = await getCachedProject(projectId);
|
|
32
|
+
if (cachedProject) {
|
|
33
|
+
return cachedProject;
|
|
34
|
+
}
|
|
35
|
+
const loadedProject = await projectRepository.getProject(projectId);
|
|
36
|
+
if (!loadedProject) {
|
|
37
|
+
reportLoadFailure(`Failed to load project for projectUrl ${projectUrl}`);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return loadedProject;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
12
44
|
export const buildPjcodeToProjectUrl = (
|
|
13
45
|
defaultPjcode: string,
|
|
14
46
|
defaultProjectUrl: string,
|