backlog-readonly-mcp-blog 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 (64) hide show
  1. package/.github/copilot-instructions.md +57 -0
  2. package/.github/settings.yml +49 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.kiro/hooks/agent-completion-sound.kiro.hook +15 -0
  5. package/.kiro/hooks/markdown-lint-save.kiro.hook +19 -0
  6. package/.kiro/settings/locale.json +3 -0
  7. package/.kiro/settings/mcp.json +32 -0
  8. package/.kiro/specs/backlog-readonly-mcp/design.md +285 -0
  9. package/.kiro/specs/backlog-readonly-mcp/requirements.md +158 -0
  10. package/.kiro/specs/backlog-readonly-mcp/tasks.md +224 -0
  11. package/.kiro/steering/blog-evaluation.md +103 -0
  12. package/.kiro/steering/pr-review-response.md +29 -0
  13. package/.markdownlint.json +5 -0
  14. package/.textlintrc.json +32 -0
  15. package/.vscode/settings.json +47 -0
  16. package/README.md +170 -0
  17. package/biome.json +36 -0
  18. package/blog_content/blog.md +57 -0
  19. package/package.json +49 -0
  20. package/packages/backlog-readonly-mcp/.backlog-mcp.env.example +49 -0
  21. package/packages/backlog-readonly-mcp/MCP_CLIENT_SETUP.md +303 -0
  22. package/packages/backlog-readonly-mcp/README.md +259 -0
  23. package/packages/backlog-readonly-mcp/package.json +58 -0
  24. package/packages/backlog-readonly-mcp/src/client/backlog-api-client.ts +348 -0
  25. package/packages/backlog-readonly-mcp/src/config/config-manager.ts +280 -0
  26. package/packages/backlog-readonly-mcp/src/index.ts +247 -0
  27. package/packages/backlog-readonly-mcp/src/tools/issue-tools.ts +449 -0
  28. package/packages/backlog-readonly-mcp/src/tools/master-data-tools.ts +209 -0
  29. package/packages/backlog-readonly-mcp/src/tools/project-tools.ts +219 -0
  30. package/packages/backlog-readonly-mcp/src/tools/tool-registry.ts +223 -0
  31. package/packages/backlog-readonly-mcp/src/tools/user-tools.ts +111 -0
  32. package/packages/backlog-readonly-mcp/src/tools/wiki-tools.ts +149 -0
  33. package/packages/backlog-readonly-mcp/src/types/index.ts +297 -0
  34. package/packages/backlog-readonly-mcp/src/utils/logger.ts +123 -0
  35. package/packages/backlog-readonly-mcp/test/backlog-api-client.test.ts +307 -0
  36. package/packages/backlog-readonly-mcp/test/config-manager.test.ts +303 -0
  37. package/packages/backlog-readonly-mcp/test/issue-tools.test.ts +345 -0
  38. package/packages/backlog-readonly-mcp/test/mcp-server-integration.test.ts +254 -0
  39. package/packages/backlog-readonly-mcp/test/mcp-server.test.ts +91 -0
  40. package/packages/backlog-readonly-mcp/test/project-tools.test.ts +194 -0
  41. package/packages/backlog-readonly-mcp/test/workspace-config.test.ts +320 -0
  42. package/packages/backlog-readonly-mcp/tsconfig.json +21 -0
  43. package/packages/backlog-readonly-mcp/tsconfig.prod.json +11 -0
  44. package/packages/backlog-readonly-mcp/vitest.config.ts +12 -0
  45. package/packages/cdk/README.md +14 -0
  46. package/packages/cdk/cdk.json +98 -0
  47. package/packages/cdk/jest.config.js +9 -0
  48. package/packages/cdk/node_modules/.bin/browserslist +21 -0
  49. package/packages/cdk/node_modules/.bin/cdk +21 -0
  50. package/packages/cdk/node_modules/.bin/jest +21 -0
  51. package/packages/cdk/node_modules/.bin/ts-jest +21 -0
  52. package/packages/cdk/node_modules/.bin/ts-node +21 -0
  53. package/packages/cdk/node_modules/.bin/ts-node-cwd +21 -0
  54. package/packages/cdk/node_modules/.bin/ts-node-esm +21 -0
  55. package/packages/cdk/node_modules/.bin/ts-node-script +21 -0
  56. package/packages/cdk/node_modules/.bin/ts-node-transpile-only +21 -0
  57. package/packages/cdk/node_modules/.bin/ts-script +21 -0
  58. package/packages/cdk/node_modules/.bin/tsc +21 -0
  59. package/packages/cdk/node_modules/.bin/tsserver +21 -0
  60. package/packages/cdk/package.json +31 -0
  61. package/packages/cdk/test/__snapshots__/cdk.test.ts.snap +40 -0
  62. package/packages/cdk/tsconfig.json +25 -0
  63. package/pnpm-workspace.yaml +2 -0
  64. package/scripts/setup-blog.js +148 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * プロジェクトツールのテスト
3
+ */
4
+
5
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
6
+ import { BacklogApiClient } from '../src/client/backlog-api-client.js';
7
+ import { ConfigManager } from '../src/config/config-manager.js';
8
+ import { registerProjectTools } from '../src/tools/project-tools.js';
9
+ import { ToolRegistry } from '../src/tools/tool-registry.js';
10
+ import type { BacklogProject, BacklogUser } from '../src/types/index.js';
11
+
12
+ // モックデータ
13
+ const mockProject: BacklogProject = {
14
+ id: 1,
15
+ projectKey: 'TEST',
16
+ name: 'テストプロジェクト',
17
+ chartEnabled: true,
18
+ subtaskingEnabled: true,
19
+ projectLeaderCanEditProjectLeader: false,
20
+ useWikiTreeView: true,
21
+ textFormattingRule: 'markdown',
22
+ archived: false,
23
+ displayOrder: 1,
24
+ useDevAttributes: false,
25
+ };
26
+
27
+ const mockProjects: BacklogProject[] = [mockProject];
28
+
29
+ const mockUsers: BacklogUser[] = [
30
+ {
31
+ id: 1,
32
+ userId: 'test-user',
33
+ name: 'テストユーザー',
34
+ roleType: 1,
35
+ lang: 'ja',
36
+ mailAddress: 'test@example.com',
37
+ },
38
+ ];
39
+
40
+ describe('プロジェクトツール', () => {
41
+ let toolRegistry: ToolRegistry;
42
+ let apiClient: BacklogApiClient;
43
+ let configManager: ConfigManager;
44
+
45
+ beforeEach(() => {
46
+ // ConfigManagerのリセット
47
+ configManager = ConfigManager.getInstance();
48
+ configManager.reset();
49
+
50
+ // 環境変数の設定
51
+ process.env.BACKLOG_DOMAIN = 'test.backlog.com';
52
+ process.env.BACKLOG_API_KEY = 'test-api-key';
53
+ process.env.BACKLOG_DEFAULT_PROJECT = 'TEST';
54
+
55
+ // 設定の読み込み
56
+ configManager.loadConfig();
57
+
58
+ // APIクライアントのモック
59
+ apiClient = new BacklogApiClient(configManager);
60
+ vi.spyOn(apiClient, 'get').mockImplementation(async (endpoint: string) => {
61
+ if (endpoint === '/projects') {
62
+ return mockProjects;
63
+ }
64
+ if (endpoint === '/projects/TEST') {
65
+ return mockProject;
66
+ }
67
+ if (endpoint === '/projects/TEST/users') {
68
+ return mockUsers;
69
+ }
70
+ throw new Error(`Unknown endpoint: ${endpoint}`);
71
+ });
72
+
73
+ // ツールレジストリの初期化
74
+ toolRegistry = new ToolRegistry();
75
+ registerProjectTools(toolRegistry, apiClient);
76
+ });
77
+
78
+ describe('get_projects', () => {
79
+ it('プロジェクト一覧を取得できる', async () => {
80
+ const result = await toolRegistry.executeTool('get_projects', {});
81
+
82
+ expect(result).toEqual({
83
+ success: true,
84
+ data: mockProjects,
85
+ count: 1,
86
+ message: '1件のプロジェクトを取得しました',
87
+ });
88
+ });
89
+
90
+ it('アーカイブされたプロジェクトも含めて取得できる', async () => {
91
+ const result = await toolRegistry.executeTool('get_projects', {
92
+ archived: true,
93
+ });
94
+
95
+ expect(result).toEqual({
96
+ success: true,
97
+ data: mockProjects,
98
+ count: 1,
99
+ message: '1件のプロジェクトを取得しました',
100
+ });
101
+ });
102
+ });
103
+
104
+ describe('get_project', () => {
105
+ it('プロジェクト詳細を取得できる', async () => {
106
+ const result = await toolRegistry.executeTool('get_project', {
107
+ projectIdOrKey: 'TEST',
108
+ });
109
+
110
+ expect(result).toMatchObject({
111
+ success: true,
112
+ data: mockProject,
113
+ message: 'プロジェクト "テストプロジェクト" の詳細情報を取得しました',
114
+ isDefaultProject: false,
115
+ });
116
+ });
117
+
118
+ it('デフォルトプロジェクトを使用してプロジェクト詳細を取得できる', async () => {
119
+ const result = await toolRegistry.executeTool('get_project', {});
120
+
121
+ expect(result).toMatchObject({
122
+ success: true,
123
+ data: mockProject,
124
+ message:
125
+ 'プロジェクト "テストプロジェクト" の詳細情報を取得しました(デフォルトプロジェクト)',
126
+ isDefaultProject: true,
127
+ });
128
+ });
129
+ });
130
+
131
+ describe('get_project_users', () => {
132
+ it('プロジェクトメンバーを取得できる', async () => {
133
+ const result = await toolRegistry.executeTool('get_project_users', {
134
+ projectIdOrKey: 'TEST',
135
+ });
136
+
137
+ expect(result).toMatchObject({
138
+ success: true,
139
+ data: mockUsers,
140
+ count: 1,
141
+ message: 'プロジェクトのメンバー 1名を取得しました',
142
+ isDefaultProject: false,
143
+ });
144
+ });
145
+
146
+ it('デフォルトプロジェクトを使用してプロジェクトメンバーを取得できる', async () => {
147
+ const result = await toolRegistry.executeTool('get_project_users', {});
148
+
149
+ expect(result).toMatchObject({
150
+ success: true,
151
+ data: mockUsers,
152
+ count: 1,
153
+ message:
154
+ 'プロジェクトのメンバー 1名を取得しました(デフォルトプロジェクト)',
155
+ isDefaultProject: true,
156
+ });
157
+ });
158
+ });
159
+
160
+ describe('get_default_project', () => {
161
+ it('デフォルトプロジェクト情報を取得できる', async () => {
162
+ const result = await toolRegistry.executeTool('get_default_project', {});
163
+
164
+ expect(result).toMatchObject({
165
+ success: true,
166
+ data: mockProject,
167
+ message:
168
+ 'デフォルトプロジェクト "テストプロジェクト" の情報を取得しました',
169
+ defaultProjectKey: 'TEST',
170
+ });
171
+ });
172
+ });
173
+
174
+ describe('ツール登録', () => {
175
+ it('すべてのプロジェクトツールが登録されている', () => {
176
+ const tools = toolRegistry.getTools();
177
+ const toolNames = tools.map((tool) => tool.name);
178
+
179
+ expect(toolNames).toContain('get_projects');
180
+ expect(toolNames).toContain('get_project');
181
+ expect(toolNames).toContain('get_project_users');
182
+ expect(toolNames).toContain('get_default_project');
183
+ });
184
+
185
+ it('各ツールが適切な説明を持っている', () => {
186
+ const tools = toolRegistry.getTools();
187
+
188
+ for (const tool of tools) {
189
+ expect(tool.description).toBeTruthy();
190
+ expect(tool.description).toContain('読み取り専用');
191
+ }
192
+ });
193
+ });
194
+ });
@@ -0,0 +1,320 @@
1
+ /**
2
+ * ワークスペース固有設定の動作確認テスト
3
+ *
4
+ * 要件 10.1, 10.2, 10.3 の検証
5
+ */
6
+
7
+ import { existsSync, unlinkSync, writeFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
10
+ import { ConfigManager } from '../src/config/config-manager.js';
11
+
12
+ describe('Workspace Configuration Tests', () => {
13
+ const testConfigPath = join(process.cwd(), '.backlog-mcp.env.test');
14
+ const defaultConfigPath = join(process.cwd(), '.backlog-mcp.env');
15
+ let originalEnv: Record<string, string | undefined>;
16
+
17
+ beforeEach(() => {
18
+ // 環境変数を保存
19
+ originalEnv = {
20
+ BACKLOG_DOMAIN: process.env.BACKLOG_DOMAIN,
21
+ BACKLOG_API_KEY: process.env.BACKLOG_API_KEY,
22
+ BACKLOG_DEFAULT_PROJECT: process.env.BACKLOG_DEFAULT_PROJECT,
23
+ BACKLOG_MAX_RETRIES: process.env.BACKLOG_MAX_RETRIES,
24
+ BACKLOG_TIMEOUT: process.env.BACKLOG_TIMEOUT,
25
+ BACKLOG_CONFIG_PATH: process.env.BACKLOG_CONFIG_PATH,
26
+ };
27
+
28
+ // ConfigManagerをリセット
29
+ ConfigManager.getInstance().reset();
30
+
31
+ // テスト用設定ファイルが存在する場合は削除
32
+ [testConfigPath, defaultConfigPath].forEach((path) => {
33
+ if (existsSync(path)) {
34
+ unlinkSync(path);
35
+ }
36
+ });
37
+ });
38
+
39
+ afterEach(() => {
40
+ // 環境変数を復元
41
+ Object.keys(originalEnv).forEach((key) => {
42
+ if (originalEnv[key] === undefined) {
43
+ delete process.env[key];
44
+ } else {
45
+ process.env[key] = originalEnv[key];
46
+ }
47
+ });
48
+
49
+ // テスト用設定ファイルを削除
50
+ [testConfigPath, defaultConfigPath].forEach((path) => {
51
+ if (existsSync(path)) {
52
+ unlinkSync(path);
53
+ }
54
+ });
55
+
56
+ // ConfigManagerをリセット
57
+ ConfigManager.getInstance().reset();
58
+ });
59
+
60
+ /**
61
+ * 要件 10.1: BACKLOG_CONFIG_PATH環境変数で指定されたファイルから設定を読み込む
62
+ */
63
+ it('should load config from BACKLOG_CONFIG_PATH specified file', () => {
64
+ // システム環境変数を設定
65
+ process.env.BACKLOG_DOMAIN = 'system.backlog.com';
66
+ process.env.BACKLOG_API_KEY = 'system-api-key';
67
+ process.env.BACKLOG_DEFAULT_PROJECT = 'SYSTEM';
68
+
69
+ // カスタム設定ファイルを作成
70
+ const customConfig = [
71
+ 'BACKLOG_DOMAIN=custom.backlog.com',
72
+ 'BACKLOG_API_KEY=custom-api-key',
73
+ 'BACKLOG_DEFAULT_PROJECT=CUSTOM',
74
+ 'BACKLOG_MAX_RETRIES=5',
75
+ 'BACKLOG_TIMEOUT=45000',
76
+ ].join('\n');
77
+ writeFileSync(testConfigPath, customConfig);
78
+
79
+ // BACKLOG_CONFIG_PATH環境変数を設定
80
+ process.env.BACKLOG_CONFIG_PATH = testConfigPath;
81
+
82
+ const manager = ConfigManager.getInstance();
83
+ const config = manager.loadConfig();
84
+
85
+ // カスタム設定ファイルの値が使用されることを確認
86
+ // 認証情報は環境変数を優先(要件10.4)
87
+ expect(config.domain).toBe('system.backlog.com');
88
+ expect(config.apiKey).toBe('system-api-key');
89
+ // その他の設定はカスタム設定ファイルを優先
90
+ expect(config.defaultProject).toBe('CUSTOM');
91
+ expect(config.maxRetries).toBe(5);
92
+ expect(config.timeout).toBe(45000);
93
+ });
94
+
95
+ /**
96
+ * 要件 10.2: ワークスペース固有の設定ファイルが存在するとき、そのファイルからBACKLOG_DEFAULT_PROJECTを読み込む
97
+ */
98
+ it('should load BACKLOG_DEFAULT_PROJECT from workspace config file', () => {
99
+ // システム環境変数を設定(デフォルトプロジェクトなし)
100
+ process.env.BACKLOG_DOMAIN = 'test.backlog.com';
101
+ process.env.BACKLOG_API_KEY = 'test-api-key';
102
+
103
+ // ワークスペース設定ファイルを作成(デフォルトの .backlog-mcp.env)
104
+ const workspaceConfig = ['BACKLOG_DEFAULT_PROJECT=WORKSPACE_PROJ'].join(
105
+ '\n',
106
+ );
107
+ writeFileSync(defaultConfigPath, workspaceConfig);
108
+
109
+ const manager = ConfigManager.getInstance();
110
+ const config = manager.loadConfig();
111
+
112
+ // ワークスペース設定からデフォルトプロジェクトが読み込まれることを確認
113
+ expect(config.defaultProject).toBe('WORKSPACE_PROJ');
114
+ expect(manager.hasDefaultProject()).toBe(true);
115
+ expect(manager.getDefaultProject()).toBe('WORKSPACE_PROJ');
116
+ });
117
+
118
+ /**
119
+ * 要件 10.3: デフォルトプロジェクトが設定されているとき、プロジェクトIDを省略した課題取得でそのプロジェクトを使用する
120
+ */
121
+ it('should use default project when project ID is omitted', () => {
122
+ // システム環境変数を設定
123
+ process.env.BACKLOG_DOMAIN = 'test.backlog.com';
124
+ process.env.BACKLOG_API_KEY = 'test-api-key';
125
+ process.env.BACKLOG_DEFAULT_PROJECT = 'DEFAULT_PROJ';
126
+
127
+ // ワークスペース設定ファイルが存在しないことを確認
128
+ if (existsSync(defaultConfigPath)) {
129
+ unlinkSync(defaultConfigPath);
130
+ }
131
+
132
+ const manager = ConfigManager.getInstance();
133
+ manager.reset(); // 確実にリセット
134
+ manager.loadConfig();
135
+
136
+ // プロジェクトIDを省略した場合、デフォルトプロジェクトが使用されることを確認
137
+ const resolvedProject = manager.resolveProjectIdOrKey();
138
+ expect(resolvedProject).toBe('DEFAULT_PROJ');
139
+
140
+ // 明示的にプロジェクトIDを指定した場合、そちらが優先されることを確認
141
+ const explicitProject = manager.resolveProjectIdOrKey('EXPLICIT_PROJ');
142
+ expect(explicitProject).toBe('EXPLICIT_PROJ');
143
+ });
144
+
145
+ /**
146
+ * 要件 10.4: 認証情報(BACKLOG_DOMAIN、BACKLOG_API_KEY)は環境変数から取得する
147
+ */
148
+ it('should get authentication info from environment variables', () => {
149
+ // 環境変数のみで認証情報を設定
150
+ process.env.BACKLOG_DOMAIN = 'env.backlog.com';
151
+ process.env.BACKLOG_API_KEY = 'env-api-key';
152
+
153
+ // ワークスペース設定ファイルには認証情報以外を設定
154
+ const workspaceConfig = [
155
+ 'BACKLOG_DEFAULT_PROJECT=WS_PROJ',
156
+ 'BACKLOG_MAX_RETRIES=7',
157
+ ].join('\n');
158
+ writeFileSync(defaultConfigPath, workspaceConfig);
159
+
160
+ // ConfigManagerをリセットして新しい設定を読み込み
161
+ ConfigManager.getInstance().reset();
162
+ const manager = ConfigManager.getInstance();
163
+ const config = manager.loadConfig();
164
+
165
+ // 認証情報は環境変数から取得されることを確認
166
+ expect(config.domain).toBe('env.backlog.com');
167
+ expect(config.apiKey).toBe('env-api-key');
168
+ // その他の設定はワークスペース設定から取得されることを確認
169
+ expect(config.defaultProject).toBe('WS_PROJ');
170
+ expect(config.maxRetries).toBe(7);
171
+ });
172
+
173
+ /**
174
+ * 要件 10.5: 設定ファイルが存在しないとき、環境変数のみを使用して動作する
175
+ */
176
+ it('should work with environment variables only when config file does not exist', () => {
177
+ // 環境変数のみを設定
178
+ process.env.BACKLOG_DOMAIN = 'env-only.backlog.com';
179
+ process.env.BACKLOG_API_KEY = 'env-only-api-key';
180
+ process.env.BACKLOG_DEFAULT_PROJECT = 'ENV_PROJ';
181
+ process.env.BACKLOG_MAX_RETRIES = '2';
182
+ process.env.BACKLOG_TIMEOUT = '20000';
183
+
184
+ // 設定ファイルが存在しないことを確認
185
+ expect(existsSync(defaultConfigPath)).toBe(false);
186
+
187
+ const manager = ConfigManager.getInstance();
188
+ const config = manager.loadConfig();
189
+
190
+ // 環境変数の値が使用されることを確認
191
+ expect(config.domain).toBe('env-only.backlog.com');
192
+ expect(config.apiKey).toBe('env-only-api-key');
193
+ expect(config.defaultProject).toBe('ENV_PROJ');
194
+ expect(config.maxRetries).toBe(2);
195
+ expect(config.timeout).toBe(20000);
196
+
197
+ // 設定サマリーでワークスペース設定がないことを確認
198
+ const summary = manager.getConfigSummary();
199
+ expect(summary.hasWorkspaceConfig).toBe(false);
200
+ });
201
+
202
+ /**
203
+ * 要件 10.6: 設定ファイルの形式として.envファイル形式をサポートする
204
+ */
205
+ it('should support .env file format', () => {
206
+ // 環境変数を完全にクリア
207
+ delete process.env.BACKLOG_DEFAULT_PROJECT;
208
+ delete process.env.BACKLOG_MAX_RETRIES;
209
+ delete process.env.BACKLOG_TIMEOUT;
210
+
211
+ // システム環境変数を設定(認証情報のみ)
212
+ process.env.BACKLOG_DOMAIN = 'test.backlog.com';
213
+ process.env.BACKLOG_API_KEY = 'test-api-key';
214
+
215
+ // .env形式の設定ファイルを作成(コメント、空行、クォート付き値を含む)
216
+ const envConfig = [
217
+ '# Backlog設定ファイル',
218
+ '',
219
+ 'BACKLOG_DEFAULT_PROJECT=ENV_FORMAT_PROJ',
220
+ 'BACKLOG_MAX_RETRIES="8"',
221
+ "BACKLOG_TIMEOUT='60000'",
222
+ '# コメント行',
223
+ 'BACKLOG_CUSTOM_SETTING=value_with_equals=sign',
224
+ '',
225
+ ].join('\n');
226
+ writeFileSync(defaultConfigPath, envConfig);
227
+
228
+ // ConfigManagerを完全にリセット
229
+ ConfigManager.getInstance().reset();
230
+ const manager = ConfigManager.getInstance();
231
+ const config = manager.loadConfig();
232
+
233
+ // .env形式が正しく解析されることを確認
234
+ expect(config.defaultProject).toBe('ENV_FORMAT_PROJ');
235
+ expect(config.maxRetries).toBe(8);
236
+ expect(config.timeout).toBe(60000);
237
+ });
238
+
239
+ /**
240
+ * 要件 10.7: 無効な設定ファイルが指定されたとき、適切なエラーメッセージを返す
241
+ */
242
+ it('should handle invalid config file gracefully', () => {
243
+ // システム環境変数を設定
244
+ process.env.BACKLOG_DOMAIN = 'test.backlog.com';
245
+ process.env.BACKLOG_API_KEY = 'test-api-key';
246
+
247
+ // 存在しないファイルを指定
248
+ process.env.BACKLOG_CONFIG_PATH = '/non/existent/path/.backlog-mcp.env';
249
+
250
+ const manager = ConfigManager.getInstance();
251
+
252
+ // 存在しないファイルでもエラーにならず、環境変数で動作することを確認
253
+ expect(() => manager.loadConfig()).not.toThrow();
254
+
255
+ const config = manager.loadConfig();
256
+ expect(config.domain).toBe('test.backlog.com');
257
+ expect(config.apiKey).toBe('test-api-key');
258
+ });
259
+
260
+ /**
261
+ * デフォルトプロジェクトが設定されていない場合のエラーハンドリング
262
+ */
263
+ it('should throw error when no project ID and no default project', () => {
264
+ // デフォルトプロジェクトなしで設定
265
+ process.env.BACKLOG_DOMAIN = 'test.backlog.com';
266
+ process.env.BACKLOG_API_KEY = 'test-api-key';
267
+
268
+ const manager = ConfigManager.getInstance();
269
+ manager.loadConfig();
270
+
271
+ // プロジェクトIDを省略し、デフォルトプロジェクトもない場合はエラー
272
+ expect(() => manager.resolveProjectIdOrKey()).toThrow();
273
+
274
+ // エラーメッセージの内容を確認
275
+ try {
276
+ manager.resolveProjectIdOrKey();
277
+ } catch (error) {
278
+ expect(error).toBeInstanceOf(Error);
279
+ expect((error as Error).message).toContain(
280
+ 'プロジェクトIDまたはキーが指定されておらず、デフォルトプロジェクトも設定されていません',
281
+ );
282
+ }
283
+ });
284
+
285
+ /**
286
+ * 設定サマリー機能のテスト
287
+ */
288
+ it('should provide configuration summary', () => {
289
+ // ConfigManagerをリセット
290
+ ConfigManager.getInstance().reset();
291
+
292
+ // 環境変数をクリア
293
+ delete process.env.BACKLOG_DOMAIN;
294
+ delete process.env.BACKLOG_API_KEY;
295
+ delete process.env.BACKLOG_DEFAULT_PROJECT;
296
+ delete process.env.BACKLOG_MAX_RETRIES;
297
+ delete process.env.BACKLOG_TIMEOUT;
298
+
299
+ // 環境変数を設定
300
+ process.env.BACKLOG_DOMAIN = 'summary.backlog.com';
301
+ process.env.BACKLOG_API_KEY = 'summary-api-key-12345678';
302
+ process.env.BACKLOG_DEFAULT_PROJECT = 'SUMMARY_PROJ';
303
+
304
+ // ワークスペース設定ファイルを作成
305
+ const workspaceConfig = 'BACKLOG_MAX_RETRIES=9';
306
+ writeFileSync(defaultConfigPath, workspaceConfig);
307
+
308
+ const manager = ConfigManager.getInstance();
309
+ manager.loadConfig();
310
+
311
+ const summary = manager.getConfigSummary();
312
+
313
+ expect(summary.domain).toBe('summary.backlog.com');
314
+ expect(summary.maskedApiKey).toMatch(/^summ\*+5678$/);
315
+ expect(summary.defaultProject).toBe('SUMMARY_PROJ');
316
+ expect(summary.maxRetries).toBe(9);
317
+ expect(summary.timeout).toBe(30000); // デフォルト値
318
+ expect(summary.hasWorkspaceConfig).toBe(true);
319
+ });
320
+ });
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "nodenext",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "resolveJsonModule": true,
17
+ "allowSyntheticDefaultImports": true
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "sourceMap": false,
5
+ "declaration": false,
6
+ "declarationMap": false,
7
+ "removeComments": true,
8
+ "noEmitOnError": true
9
+ },
10
+ "exclude": ["node_modules", "dist", "**/*.test.ts", "test/**/*"]
11
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ globals: true,
7
+ coverage: {
8
+ reporter: ['text', 'json', 'html'],
9
+ exclude: ['node_modules/', 'dist/', '**/*.test.ts', '**/*.config.ts'],
10
+ },
11
+ },
12
+ });
@@ -0,0 +1,14 @@
1
+ # Welcome to your CDK TypeScript project
2
+
3
+ This is a blank project for CDK development with TypeScript.
4
+
5
+ The `cdk.json` file tells the CDK Toolkit how to execute your app.
6
+
7
+ ## Useful commands
8
+
9
+ * `npm run build` compile typescript to js
10
+ * `npm run watch` watch for changes and compile
11
+ * `npm run test` perform the jest unit tests
12
+ * `npx cdk deploy` deploy this stack to your default AWS account/region
13
+ * `npx cdk diff` compare deployed stack with current state
14
+ * `npx cdk synth` emits the synthesized CloudFormation template
@@ -0,0 +1,98 @@
1
+ {
2
+ "app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
3
+ "watch": {
4
+ "include": ["**"],
5
+ "exclude": [
6
+ "README.md",
7
+ "cdk*.json",
8
+ "**/*.d.ts",
9
+ "**/*.js",
10
+ "tsconfig.json",
11
+ "package*.json",
12
+ "yarn.lock",
13
+ "node_modules",
14
+ "test"
15
+ ]
16
+ },
17
+ "context": {
18
+ "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": true,
19
+ "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": true,
20
+ "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21
+ "@aws-cdk/core:checkSecretUsage": true,
22
+ "@aws-cdk/core:target-partitions": ["aws", "aws-cn"],
23
+ "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
24
+ "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
25
+ "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
26
+ "@aws-cdk/aws-iam:minimizePolicies": true,
27
+ "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
28
+ "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
29
+ "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
30
+ "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
31
+ "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
32
+ "@aws-cdk/core:enablePartitionLiterals": true,
33
+ "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
34
+ "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
35
+ "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
36
+ "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
37
+ "@aws-cdk/aws-route53-patters:useCertificate": true,
38
+ "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
39
+ "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
40
+ "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
41
+ "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
42
+ "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
43
+ "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
44
+ "@aws-cdk/aws-redshift:columnId": true,
45
+ "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
46
+ "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
47
+ "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
48
+ "@aws-cdk/aws-kms:aliasNameRef": true,
49
+ "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": true,
50
+ "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
51
+ "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
52
+ "@aws-cdk/aws-efs:denyAnonymousAccess": true,
53
+ "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
54
+ "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
55
+ "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
56
+ "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
57
+ "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
58
+ "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
59
+ "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
60
+ "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
61
+ "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
62
+ "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
63
+ "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
64
+ "@aws-cdk/aws-eks:nodegroupNameAttribute": true,
65
+ "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
66
+ "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
67
+ "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
68
+ "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
69
+ "@aws-cdk/core:explicitStackTags": true,
70
+ "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
71
+ "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
72
+ "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
73
+ "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
74
+ "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
75
+ "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
76
+ "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
77
+ "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
78
+ "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
79
+ "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
80
+ "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
81
+ "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
82
+ "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
83
+ "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
84
+ "@aws-cdk/core:enableAdditionalMetadataCollection": true,
85
+ "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false,
86
+ "@aws-cdk/aws-s3:setUniqueReplicationRoleName": true,
87
+ "@aws-cdk/aws-events:requireEventBusPolicySid": true,
88
+ "@aws-cdk/core:aspectPrioritiesMutating": true,
89
+ "@aws-cdk/aws-dynamodb:retainTableReplica": true,
90
+ "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
91
+ "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
92
+ "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true,
93
+ "@aws-cdk/aws-s3:publicAccessBlockedByDefault": true,
94
+ "@aws-cdk/aws-lambda:useCdkManagedLogGroup": true,
95
+ "@aws-cdk/aws-elasticloadbalancingv2:networkLoadBalancerWithSecurityGroupByDefault": true,
96
+ "@aws-cdk/aws-ecs-patterns:uniqueTargetGroupId": true
97
+ }
98
+ }
@@ -0,0 +1,9 @@
1
+ module.exports = {
2
+ testEnvironment: 'node',
3
+ roots: ['<rootDir>/test'],
4
+ testMatch: ['**/*.test.ts'],
5
+ transform: {
6
+ '^.+\\.tsx?$': 'ts-jest',
7
+ },
8
+ setupFilesAfterEnv: ['aws-cdk-lib/testhelpers/jest-autoclean'],
9
+ };