github-issue-tower-defence-management 1.94.2 → 1.94.4
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/CHANGELOG.md +14 -0
- package/bin/adapter/entry-points/cli/index.js +1 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleImageProxy.js +79 -0
- package/bin/adapter/entry-points/console/consoleImageProxy.js.map +1 -0
- package/bin/adapter/entry-points/console/consoleServer.js +29 -1
- package/bin/adapter/entry-points/console/consoleServer.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-fKvnL036.js +101 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.ts +1 -0
- package/src/adapter/entry-points/console/consoleImageProxy.test.ts +140 -0
- package/src/adapter/entry-points/console/consoleImageProxy.ts +111 -0
- package/src/adapter/entry-points/console/consoleServer.test.ts +182 -0
- package/src/adapter/entry-points/console/consoleServer.ts +45 -0
- package/src/adapter/entry-points/console/consoleUiDistSync.test.ts +66 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.stories.tsx +10 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/content/ConsoleMarkdownContent.tsx +23 -3
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleCommentList.tsx +7 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +9 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsolePullRequestDetail.tsx +7 -1
- package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.test.ts +78 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/imageProxy.ts +41 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +9 -0
- package/src/adapter/entry-points/console/ui/src/features/console/testing/fixtures.ts +11 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-fKvnL036.js +101 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +1 -1
- package/types/adapter/entry-points/console/consoleImageProxy.d.ts +19 -0
- package/types/adapter/entry-points/console/consoleImageProxy.d.ts.map +1 -0
- package/types/adapter/entry-points/console/consoleServer.d.ts +4 -0
- package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-eSyo3r3C.js +0 -101
- package/src/adapter/entry-points/console/ui-dist/assets/index-eSyo3r3C.js +0 -101
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
+
import type { ImageProxyUrlBuilder } from '../../lib/imageProxy';
|
|
2
3
|
import { formatRelativeTime } from '../../logic/relativeTime';
|
|
3
4
|
import type { ConsoleComment } from '../../logic/types';
|
|
4
5
|
import { ConsoleMarkdownContent } from '../content/ConsoleMarkdownContent';
|
|
@@ -8,6 +9,7 @@ export type ConsoleCommentListProps = {
|
|
|
8
9
|
isLoading: boolean;
|
|
9
10
|
error: string | null;
|
|
10
11
|
now: number;
|
|
12
|
+
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
export const ConsoleCommentList = ({
|
|
@@ -15,6 +17,7 @@ export const ConsoleCommentList = ({
|
|
|
15
17
|
isLoading,
|
|
16
18
|
error,
|
|
17
19
|
now,
|
|
20
|
+
buildImageProxyUrl,
|
|
18
21
|
}: ConsoleCommentListProps) => {
|
|
19
22
|
const [showAll, setShowAll] = useState<boolean>(false);
|
|
20
23
|
|
|
@@ -58,7 +61,10 @@ export const ConsoleCommentList = ({
|
|
|
58
61
|
{formatRelativeTime(comment.createdAt, now)}
|
|
59
62
|
</span>
|
|
60
63
|
</header>
|
|
61
|
-
<ConsoleMarkdownContent
|
|
64
|
+
<ConsoleMarkdownContent
|
|
65
|
+
body={comment.body}
|
|
66
|
+
buildImageProxyUrl={buildImageProxyUrl}
|
|
67
|
+
/>
|
|
62
68
|
</article>
|
|
63
69
|
))}
|
|
64
70
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
import type { ImageProxyUrlBuilder } from '../../lib/imageProxy';
|
|
2
3
|
import { colorFromEnum } from '../../logic/colors';
|
|
3
4
|
import {
|
|
4
5
|
formatFullTimestamp,
|
|
@@ -54,6 +55,7 @@ export type ConsoleItemDetailProps = {
|
|
|
54
55
|
now: number;
|
|
55
56
|
commentComposer: ReactNode;
|
|
56
57
|
operationBar: ReactNode;
|
|
58
|
+
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
57
59
|
};
|
|
58
60
|
|
|
59
61
|
export const ConsoleItemDetail = ({
|
|
@@ -78,6 +80,7 @@ export const ConsoleItemDetail = ({
|
|
|
78
80
|
now,
|
|
79
81
|
commentComposer,
|
|
80
82
|
operationBar,
|
|
83
|
+
buildImageProxyUrl,
|
|
81
84
|
}: ConsoleItemDetailProps) => {
|
|
82
85
|
const resolvedState = state?.state ?? 'open';
|
|
83
86
|
const merged = state?.merged ?? false;
|
|
@@ -192,7 +195,10 @@ export const ConsoleItemDetail = ({
|
|
|
192
195
|
) : bodyIsLoading ? (
|
|
193
196
|
<p className="console-detail-body-loading">Loading description...</p>
|
|
194
197
|
) : (
|
|
195
|
-
<ConsoleMarkdownContent
|
|
198
|
+
<ConsoleMarkdownContent
|
|
199
|
+
body={body}
|
|
200
|
+
buildImageProxyUrl={buildImageProxyUrl}
|
|
201
|
+
/>
|
|
196
202
|
)}
|
|
197
203
|
</ConsolePanel>
|
|
198
204
|
|
|
@@ -216,6 +222,7 @@ export const ConsoleItemDetail = ({
|
|
|
216
222
|
isLoading={commentsAreLoading}
|
|
217
223
|
error={commentsError}
|
|
218
224
|
now={now}
|
|
225
|
+
buildImageProxyUrl={buildImageProxyUrl}
|
|
219
226
|
/>
|
|
220
227
|
</ConsolePanel>
|
|
221
228
|
|
|
@@ -244,6 +251,7 @@ export const ConsoleItemDetail = ({
|
|
|
244
251
|
commitsAreLoading={related.commitsAreLoading}
|
|
245
252
|
commitsError={related.commitsError}
|
|
246
253
|
now={now}
|
|
254
|
+
buildImageProxyUrl={buildImageProxyUrl}
|
|
247
255
|
/>
|
|
248
256
|
))}
|
|
249
257
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ImageProxyUrlBuilder } from '../../lib/imageProxy';
|
|
1
2
|
import type {
|
|
2
3
|
ConsoleChangedFile,
|
|
3
4
|
ConsoleCommit,
|
|
@@ -19,6 +20,7 @@ export type ConsolePullRequestSectionProps = {
|
|
|
19
20
|
commitsAreLoading: boolean;
|
|
20
21
|
commitsError: string | null;
|
|
21
22
|
now: number;
|
|
23
|
+
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export const ConsolePullRequestDetail = ({
|
|
@@ -32,6 +34,7 @@ export const ConsolePullRequestDetail = ({
|
|
|
32
34
|
commitsAreLoading,
|
|
33
35
|
commitsError,
|
|
34
36
|
now,
|
|
37
|
+
buildImageProxyUrl,
|
|
35
38
|
}: ConsolePullRequestSectionProps) => {
|
|
36
39
|
const summary = pullRequest.summary;
|
|
37
40
|
const filesCount =
|
|
@@ -71,7 +74,10 @@ export const ConsolePullRequestDetail = ({
|
|
|
71
74
|
{bodyIsLoading ? (
|
|
72
75
|
<p className="console-pr-body-loading">Loading description...</p>
|
|
73
76
|
) : (
|
|
74
|
-
<ConsoleMarkdownContent
|
|
77
|
+
<ConsoleMarkdownContent
|
|
78
|
+
body={summary?.body ?? body}
|
|
79
|
+
buildImageProxyUrl={buildImageProxyUrl}
|
|
80
|
+
/>
|
|
75
81
|
)}
|
|
76
82
|
</ConsolePanel>
|
|
77
83
|
<ConsolePanel title="Changed files" count={filesCount}>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildImageProxyUrl,
|
|
3
|
+
isProxyableImageUrl,
|
|
4
|
+
rewriteGitHubImageSources,
|
|
5
|
+
} from './imageProxy';
|
|
6
|
+
|
|
7
|
+
describe('isProxyableImageUrl', () => {
|
|
8
|
+
it('matches github user-attachments urls', () => {
|
|
9
|
+
expect(
|
|
10
|
+
isProxyableImageUrl(
|
|
11
|
+
'https://github.com/user-attachments/assets/0a1b2c3d',
|
|
12
|
+
),
|
|
13
|
+
).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('matches githubusercontent subdomain urls', () => {
|
|
17
|
+
expect(
|
|
18
|
+
isProxyableImageUrl(
|
|
19
|
+
'https://private-user-images.githubusercontent.com/1/2.png',
|
|
20
|
+
),
|
|
21
|
+
).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('does not match arbitrary external hosts', () => {
|
|
25
|
+
expect(isProxyableImageUrl('https://example.com/avatar.png')).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('buildImageProxyUrl', () => {
|
|
30
|
+
it('encodes the source url and appends the token', () => {
|
|
31
|
+
const src = 'https://github.com/user-attachments/assets/abc?x=1';
|
|
32
|
+
expect(buildImageProxyUrl(src, 'tk')).toBe(
|
|
33
|
+
`/api/img?url=${encodeURIComponent(src)}&k=tk`,
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('omits the token when it is null', () => {
|
|
38
|
+
const src = 'https://github.com/user-attachments/assets/abc';
|
|
39
|
+
expect(buildImageProxyUrl(src, null)).toBe(
|
|
40
|
+
`/api/img?url=${encodeURIComponent(src)}`,
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('rewriteGitHubImageSources', () => {
|
|
46
|
+
const buildProxyUrl = (src: string): string =>
|
|
47
|
+
buildImageProxyUrl(src, 'console-token');
|
|
48
|
+
|
|
49
|
+
it('rewrites an allow-listed github image src to the proxy url', () => {
|
|
50
|
+
const githubSrc = 'https://github.com/user-attachments/assets/abc';
|
|
51
|
+
const html = `<p><img src="${githubSrc}" alt="shot" /></p>`;
|
|
52
|
+
const result = rewriteGitHubImageSources(html, buildProxyUrl);
|
|
53
|
+
expect(result).toContain(`/api/img?url=${encodeURIComponent(githubSrc)}`);
|
|
54
|
+
expect(result).toContain('k=console-token');
|
|
55
|
+
expect(result).not.toContain(`src="${githubSrc}"`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('rewrites a githubusercontent image src to the proxy url', () => {
|
|
59
|
+
const githubSrc =
|
|
60
|
+
'https://private-user-images.githubusercontent.com/1/2.png';
|
|
61
|
+
const html = `<img src="${githubSrc}" />`;
|
|
62
|
+
const result = rewriteGitHubImageSources(html, buildProxyUrl);
|
|
63
|
+
expect(result).toContain(`/api/img?url=${encodeURIComponent(githubSrc)}`);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('leaves a non-matching image src unchanged', () => {
|
|
67
|
+
const externalSrc = 'https://example.com/avatar.png';
|
|
68
|
+
const html = `<img src="${externalSrc}" />`;
|
|
69
|
+
const result = rewriteGitHubImageSources(html, buildProxyUrl);
|
|
70
|
+
expect(result).toContain(`src="${externalSrc}"`);
|
|
71
|
+
expect(result).not.toContain('/api/img');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('returns the original html when there is no image to rewrite', () => {
|
|
75
|
+
const html = '<p>no images here</p>';
|
|
76
|
+
expect(rewriteGitHubImageSources(html, buildProxyUrl)).toBe(html);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const ALLOWED_IMAGE_URL =
|
|
2
|
+
/^https:\/\/github\.com\/user-attachments\/|^https:\/\/[a-z0-9][a-z0-9.-]*\.githubusercontent\.com\//;
|
|
3
|
+
|
|
4
|
+
export const isProxyableImageUrl = (src: string): boolean =>
|
|
5
|
+
ALLOWED_IMAGE_URL.test(src);
|
|
6
|
+
|
|
7
|
+
export type ImageProxyUrlBuilder = (src: string) => string;
|
|
8
|
+
|
|
9
|
+
export const buildImageProxyUrl = (
|
|
10
|
+
src: string,
|
|
11
|
+
token: string | null,
|
|
12
|
+
): string => {
|
|
13
|
+
if (token === null) {
|
|
14
|
+
return `/api/img?url=${encodeURIComponent(src)}`;
|
|
15
|
+
}
|
|
16
|
+
return `/api/img?url=${encodeURIComponent(src)}&k=${encodeURIComponent(token)}`;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const rewriteGitHubImageSources = (
|
|
20
|
+
html: string,
|
|
21
|
+
buildProxyUrl: ImageProxyUrlBuilder,
|
|
22
|
+
): string => {
|
|
23
|
+
if (typeof document === 'undefined') {
|
|
24
|
+
return html;
|
|
25
|
+
}
|
|
26
|
+
const template = document.createElement('template');
|
|
27
|
+
template.innerHTML = html;
|
|
28
|
+
const images = template.content.querySelectorAll('img[src]');
|
|
29
|
+
let rewrote = false;
|
|
30
|
+
images.forEach((image) => {
|
|
31
|
+
const src = image.getAttribute('src') ?? '';
|
|
32
|
+
if (isProxyableImageUrl(src)) {
|
|
33
|
+
image.setAttribute('src', buildProxyUrl(src));
|
|
34
|
+
rewrote = true;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
if (!rewrote) {
|
|
38
|
+
return html;
|
|
39
|
+
}
|
|
40
|
+
return template.innerHTML;
|
|
41
|
+
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
1
2
|
import { ConsoleCommentComposer } from '../components/detail/ConsoleCommentComposer';
|
|
2
3
|
import { ConsoleItemDetail } from '../components/detail/ConsoleItemDetail';
|
|
3
4
|
import { ConsoleOperationMenu } from '../components/operations/ConsoleOperationMenu';
|
|
4
5
|
import type { ConsoleCaches } from '../hooks/useConsoleCaches';
|
|
5
6
|
import { useConsoleItemDetailData } from '../hooks/useConsoleItemDetailData';
|
|
6
7
|
import type { ConsoleOperationsApi } from '../hooks/useConsoleOperations';
|
|
8
|
+
import { useConsoleToken } from '../hooks/useConsoleToken';
|
|
9
|
+
import { buildImageProxyUrl } from '../lib/imageProxy';
|
|
7
10
|
import type { ConsoleActionKind } from '../logic/actionToast';
|
|
8
11
|
import { resolveStoryColorEnum } from '../logic/grouping';
|
|
9
12
|
import type { ConsoleOperationHandlers } from '../logic/operations';
|
|
@@ -50,6 +53,11 @@ export const ConsoleItemDetailContainer = ({
|
|
|
50
53
|
onQueueAction,
|
|
51
54
|
}: ConsoleItemDetailContainerProps) => {
|
|
52
55
|
const detail = useConsoleItemDetailData(caches, item);
|
|
56
|
+
const { token } = useConsoleToken();
|
|
57
|
+
const resolveImageProxyUrl = useCallback(
|
|
58
|
+
(src: string): string => buildImageProxyUrl(src, token),
|
|
59
|
+
[token],
|
|
60
|
+
);
|
|
53
61
|
const hasPullRequest = item.isPr || detail.relatedPullRequests.length > 0;
|
|
54
62
|
|
|
55
63
|
const handlers: ConsoleOperationHandlers = {
|
|
@@ -140,6 +148,7 @@ export const ConsoleItemDetailContainer = ({
|
|
|
140
148
|
commitsError={detail.commitsError}
|
|
141
149
|
relatedPullRequests={detail.relatedPullRequests}
|
|
142
150
|
now={now}
|
|
151
|
+
buildImageProxyUrl={resolveImageProxyUrl}
|
|
143
152
|
commentComposer={
|
|
144
153
|
<ConsoleCommentComposer
|
|
145
154
|
isPr={item.isPr}
|
|
@@ -133,6 +133,17 @@ console bundle and the token-protected \`/api\` endpoints.
|
|
|
133
133
|
- [x] Token validation via \`?k=\` and the \`x-pv-token\` header
|
|
134
134
|
`;
|
|
135
135
|
|
|
136
|
+
export const consoleMarkdownImageBodyFixture = `## Screenshot
|
|
137
|
+
|
|
138
|
+
The failing screen is attached below.
|
|
139
|
+
|
|
140
|
+

|
|
141
|
+
|
|
142
|
+
For reference, the avatar comes from an external host:
|
|
143
|
+
|
|
144
|
+

|
|
145
|
+
`;
|
|
146
|
+
|
|
136
147
|
export const consoleMermaidBodyFixture = `Here is the request flow:
|
|
137
148
|
|
|
138
149
|
\`\`\`mermaid
|