From 4a3bef0e3e9b01d74916d2bb6b2d76a50c3f15f9 Mon Sep 17 00:00:00 2001 From: Vladislav Tupikin Date: Sun, 5 Jan 2025 01:10:59 +0300 Subject: [PATCH] Release v4.0.4 (#342) * #327: Package `form-data` replaced to `formdata-node` for add esm support (#341) * #327: Package `form-data` replaced to `formdata-node` for add esm support * #327: Add `Buffer` type support using `formdata-node` package - Implemented `Buffer` to `File` conversion for attachment uploads. - Added `attachment.mimeType` an optional property. - Added automatic MIME type detection based on file extensions. * #327: Fixed the issue with the absence of the `File` class in Node.js v18.x.x by using `File` from `formdata-node`. - Enhanced documentation with TSDoc and examples for better clarity. * #320: Fix tree shaking mechanism and remove circular dependencies (#343) - Improved the tree shaking process to ensure unused code is properly eliminated across the entire library. - Refactored the codebase to resolve circular dependencies, improving tree shaking and maintainability. --- CHANGELOG.md | 42 +++++ package-lock.json | 178 ++++++++++++------ package.json | 8 +- src/agile/client/agileClient.ts | 30 ++- src/serviceDesk/client/serviceDeskClient.ts | 2 +- .../parameters/attachTemporaryFile.ts | 92 ++++++++- src/serviceDesk/serviceDesk.ts | 13 +- src/version2/client/version2Client.ts | 174 +++++++++-------- src/version2/issueAttachments.ts | 14 +- src/version2/parameters/addAttachment.ts | 92 ++++++++- src/version2/parameters/updateProject.ts | 4 +- src/version3/client/version3Client.ts | 176 +++++++++-------- src/version3/issueAttachments.ts | 14 +- src/version3/models/index.ts | 1 - src/version3/parameters/addAttachment.ts | 92 ++++++++- src/version3/parameters/updateProject.ts | 2 +- .../version2/issueAttachments.test.ts | 19 +- .../version3/issueAttachments.test.ts | 19 +- 18 files changed, 695 insertions(+), 277 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cb66ec3c6..e4f75103cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # Jira.js changelog +### 4.0.4 + +- **#320:** Resolved a tree-shaking issue where importing a single client would still include all clients in the output bundle when using bundlers. Now, only the required client code is included. Thanks to [Nao Yonashiro](https://github.com/orisano) for [reporting the issue](https://github.com/MrRefactoring/jira.js/issues/320) and proposing a fix. +- **#327:** Replaced the `form-data` library with `formdata-node` to enable compatibility with `ESM` projects when adding attachments via the `issueAttachment.addAttachment` method. Thanks to [Paweł Król](https://github.com/xpawk) for [reporting the issue](https://github.com/MrRefactoring/jira.js/issues/327) and [Matyáš Kroupa](https://github.com/krouma) for implementing the fix. +- **Improvement:** The type of the `projectIdOrKey` property was updated from `string` to `number | string` for project update operations. This enhancement improves type safety and flexibility when handling project identifiers. +- **Enhancement:** Added a `mimeType` property to the `version2.issueAttachments.addAttachment`, `version3.issueAttachments.addAttachment`, and `serviceDesk.serviceDesk.attachTemporaryFile` methods. This allows specifying the file type. If `mimeType` is not provided, a default type is inferred from the filename. + + #### Examples: + + **👎 Before:** + + ```typescript + const client = new Version2Client() || new Version3Client() || new ServiceDeskClient(); + + const attachment = await client.issueAttachments.addAttachment({ + issueIdOrKey: issue.key, + attachment: { + filename: 'issueAttachments.test.ts', + file: fs.readFileSync('./tests/integration/version2/issueAttachments.test.ts'), + }, + }); + + console.log(attachment[0].mimeType); // Will be 'video/mp2t' + ``` + + **👍 Now:** + + ```typescript + const client = new Version2Client() || new Version3Client() || new ServiceDeskClient(); + + const attachment = await client.issueAttachments.addAttachment({ + issueIdOrKey: issue.key, + attachment: { + filename: 'issueAttachments.test.ts', + file: fs.readFileSync('./tests/integration/version2/issueAttachments.test.ts'), + mimeType: 'application/typescript', + }, + }); + + console.log(attachment[0].mimeType); // Will be 'application/typescript' + ``` + ### 4.0.3 - **Bug Fix:** Fixed an issue with the `Users.createUser` method by adding the required `products` property. Thanks to [Appelberg-s](https://github.com/Appelberg-s) for the [fix](https://github.com/MrRefactoring/jira.js/commit/362918093c20036049db334743e2a0f5f41cbcd4#diff-6960050bc2a3d9ffad9eb5e307145969dc4a38eb5434eebf39da545fd18e01b7R12). diff --git a/package-lock.json b/package-lock.json index 35e16de8a2..a033708eb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,21 @@ { "name": "jira.js", - "version": "4.0.3", + "version": "4.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jira.js", - "version": "4.0.3", + "version": "4.0.4", "license": "MIT", "dependencies": { "axios": "^1.7.9", - "form-data": "^4.0.1", + "formdata-node": "^6.0.3", + "mime-types": "^2.1.35", "tslib": "^2.8.1" }, "devDependencies": { + "@types/mime-types": "^2.1.4", "@types/node": "^18.19.69", "@types/sinon": "^17.0.3", "@typescript-eslint/eslint-plugin": "^8.19.0", @@ -24,7 +26,7 @@ "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-import": "^2.31.0", "prettier": "^3.4.2", - "prettier-plugin-jsdoc": "^1.3.0", + "prettier-plugin-jsdoc": "^1.3.2", "sinon": "^18.0.1", "typedoc": "^0.27.6", "typescript": "^5.7.2", @@ -511,15 +513,15 @@ } }, "node_modules/@gerrit0/mini-shiki": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.24.4.tgz", - "integrity": "sha512-YEHW1QeAg6UmxEmswiQbOVEg1CW22b1XUD/lNTliOsu0LD0wqoyleFMnmbTp697QE0pcadQiR5cVtbbAPncvpw==", + "version": "1.26.1", + "resolved": "https://registry.npmjs.org/@gerrit0/mini-shiki/-/mini-shiki-1.26.1.tgz", + "integrity": "sha512-gHFUvv9f1fU2Piou/5Y7Sx5moYxcERbC7CXc6rkDLQTUBg5Dgg9L4u29/nHqfoQ3Y9R0h0BcOhd14uOEZIBP7Q==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/engine-oniguruma": "^1.24.2", - "@shikijs/types": "^1.24.2", - "@shikijs/vscode-textmate": "^9.3.1" + "@shikijs/engine-oniguruma": "^1.26.1", + "@shikijs/types": "^1.26.1", + "@shikijs/vscode-textmate": "^10.0.1" } }, "node_modules/@humanwhocodes/config-array": { @@ -913,31 +915,31 @@ "license": "MIT" }, "node_modules/@shikijs/engine-oniguruma": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.24.4.tgz", - "integrity": "sha512-Do2ry6flp2HWdvpj2XOwwa0ljZBRy15HKZITzPcNIBOGSeprnA8gOooA/bLsSPuy8aJBa+Q/r34dMmC3KNL/zw==", + "version": "1.26.1", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.26.1.tgz", + "integrity": "sha512-F5XuxN1HljLuvfXv7d+mlTkV7XukC1cawdtOo+7pKgPD83CAB1Sf8uHqP3PK0u7njFH0ZhoXE1r+0JzEgAQ+kg==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/types": "1.24.4", - "@shikijs/vscode-textmate": "^9.3.1" + "@shikijs/types": "1.26.1", + "@shikijs/vscode-textmate": "^10.0.1" } }, "node_modules/@shikijs/types": { - "version": "1.24.4", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.24.4.tgz", - "integrity": "sha512-0r0XU7Eaow0PuDxuWC1bVqmWCgm3XqizIaT7SM42K03vc69LGooT0U8ccSR44xP/hGlNx4FKhtYpV+BU6aaKAA==", + "version": "1.26.1", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.26.1.tgz", + "integrity": "sha512-d4B00TKKAMaHuFYgRf3L0gwtvqpW4hVdVwKcZYbBfAAQXspgkbWqnFfuFl3MDH6gLbsubOcr+prcnsqah3ny7Q==", "dev": true, "license": "MIT", "dependencies": { - "@shikijs/vscode-textmate": "^9.3.1", + "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "node_modules/@shikijs/vscode-textmate": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-9.3.1.tgz", - "integrity": "sha512-79QfK1393x9Ho60QFyLti+QfdJzRQCVLFb97kOIV7Eo9vQU/roINgk7m24uv0a7AUvN//RDH36FLjjK48v0s9g==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz", + "integrity": "sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==", "dev": true, "license": "MIT" }, @@ -1034,6 +1036,13 @@ "@types/unist": "*" } }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "0.7.34", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", @@ -2107,9 +2116,9 @@ } }, "node_modules/es-abstract": { - "version": "1.23.8", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.8.tgz", - "integrity": "sha512-lfab8IzDn6EpI1ibZakcgS6WsfEBiB+43cuJo+wgylx1xKXf+Sp+YR3vFuQwC/u3sxYwV8Cxe3B0DpVUu/WiJQ==", + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", "dev": true, "license": "MIT", "dependencies": { @@ -2124,10 +2133,11 @@ "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", + "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.2.6", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", @@ -2148,11 +2158,12 @@ "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", - "own-keys": "^1.0.0", + "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", @@ -2211,15 +2222,16 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -2877,6 +2889,15 @@ "node": ">= 6" } }, + "node_modules/formdata-node": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz", + "integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2941,22 +2962,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", + "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2965,6 +2986,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -3306,13 +3341,16 @@ } }, "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.0.tgz", + "integrity": "sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3455,13 +3493,16 @@ } }, "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4852,9 +4893,9 @@ } }, "node_modules/prettier-plugin-jsdoc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-jsdoc/-/prettier-plugin-jsdoc-1.3.0.tgz", - "integrity": "sha512-cQm8xIa0fN9ieJFMXACQd6JPycl+8ouOijAqUqu44EF/s4fXL3Wi9sKXuEaodsEWgCN42Xby/bNhqgM1iWx4uw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prettier-plugin-jsdoc/-/prettier-plugin-jsdoc-1.3.2.tgz", + "integrity": "sha512-LNi9eq0TjyZn/PUNf/SYQxxUvGg5FLK4alEbi3i/S+2JbMyTu790c/puFueXzx09KP44oWCJ+TaHRyM/a0rKJQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4917,19 +4958,19 @@ "license": "MIT" }, "node_modules/reflect.getprototypeof": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", - "integrity": "sha512-r0Ay04Snci87djAsI4U+WNRcSw5S4pOH7qFjd/veA5gC7TbqESR3tcj28ia95L/fYUDw11JKP7uqUKUAfVvV5Q==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "dunder-proto": "^1.0.1", - "es-abstract": "^1.23.6", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" }, "engines": { @@ -4940,15 +4981,17 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz", - "integrity": "sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", "set-function-name": "^2.0.2" }, "engines": { @@ -5192,6 +5235,21 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index cce81d7b5f..e61086e1a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jira.js", - "version": "4.0.3", + "version": "4.0.4", "description": "A comprehensive JavaScript/TypeScript library designed for both Node.JS and browsers, facilitating seamless interaction with the Atlassian Jira API.", "main": "out/index.js", "types": "out/index.d.ts", @@ -53,6 +53,7 @@ "code:formatting": "npm run replace:all && npm run prettier && npm run lint:fix" }, "devDependencies": { + "@types/mime-types": "^2.1.4", "@types/node": "^18.19.69", "@types/sinon": "^17.0.3", "@typescript-eslint/eslint-plugin": "^8.19.0", @@ -63,7 +64,7 @@ "eslint-import-resolver-typescript": "^3.7.0", "eslint-plugin-import": "^2.31.0", "prettier": "^3.4.2", - "prettier-plugin-jsdoc": "^1.3.0", + "prettier-plugin-jsdoc": "^1.3.2", "sinon": "^18.0.1", "typedoc": "^0.27.6", "typescript": "^5.7.2", @@ -72,7 +73,8 @@ }, "dependencies": { "axios": "^1.7.9", - "form-data": "^4.0.1", + "formdata-node": "^6.0.3", + "mime-types": "^2.1.35", "tslib": "^2.8.1" } } diff --git a/src/agile/client/agileClient.ts b/src/agile/client/agileClient.ts index 7f98f186ac..7710296e3c 100644 --- a/src/agile/client/agileClient.ts +++ b/src/agile/client/agileClient.ts @@ -1,19 +1,17 @@ -import { BaseClient } from '../../clients'; -import { - Backlog, - Board, - Builds, - Deployments, - DevelopmentInformation, - DevopsComponents, - Epic, - FeatureFlags, - Issue, - Operations, - RemoteLinks, - SecurityInformation, - Sprint, -} from '..'; +import { BaseClient } from '../../clients/baseClient'; +import { Backlog } from '../backlog'; +import { Board } from '../board'; +import { Builds } from '../builds'; +import { Deployments } from '../deployments'; +import { DevelopmentInformation } from '../developmentInformation'; +import { DevopsComponents } from '../devopsComponents'; +import { Epic } from '../epic'; +import { FeatureFlags } from '../featureFlags'; +import { Issue } from '../issue'; +import { Operations } from '../operations'; +import { RemoteLinks } from '../remoteLinks'; +import { SecurityInformation } from '../securityInformation'; +import { Sprint } from '../sprint'; export class AgileClient extends BaseClient { backlog = new Backlog(this); diff --git a/src/serviceDesk/client/serviceDeskClient.ts b/src/serviceDesk/client/serviceDeskClient.ts index c131004659..c3abefccbb 100644 --- a/src/serviceDesk/client/serviceDeskClient.ts +++ b/src/serviceDesk/client/serviceDeskClient.ts @@ -1,4 +1,4 @@ -import { BaseClient } from '../../clients'; +import { BaseClient } from '../../clients/baseClient'; import { Customer } from '../customer'; import { Info } from '../info'; import { Insight } from '../insight'; diff --git a/src/serviceDesk/parameters/attachTemporaryFile.ts b/src/serviceDesk/parameters/attachTemporaryFile.ts index f1ea8ab2bb..e9d0d7a7cc 100644 --- a/src/serviceDesk/parameters/attachTemporaryFile.ts +++ b/src/serviceDesk/parameters/attachTemporaryFile.ts @@ -1,13 +1,103 @@ +/** + * Represents an attachment to be temporarily attached to a Service Desk. + * + * @example + * ```typescript + * const attachment: Attachment = { + * filename: 'example.txt', + * file: Buffer.from('Temporary file content'), + * mimeType: 'text/plain', + * }; + * ``` + */ export interface Attachment { + /** + * The name of the attachment file. + * + * @example + * ```typescript + * const filename = 'example.png'; + * ``` + */ filename: string; + + /** + * The content of the attachment. Can be one of the following: + * + * - `Buffer`: For binary data. + * - `ReadableStream`: For streaming large files. + * - `string`: For text-based content. + * - `Blob`: For browser-like blob objects. + * - `File`: For file objects with metadata (e.g., in web environments). + * + * @example + * ```typescript + * const fileContent = Buffer.from('Example content here'); + * ``` + */ file: Buffer | ReadableStream | string | Blob | File; + + /** + * Optional MIME type of the attachment. Example values include: + * + * - 'application/pdf' + * - 'image/jpeg' If not provided, the MIME type will be automatically detected based on the filename. + * + * @example + * ```typescript + * const mimeType = 'image/jpeg'; + * ``` + */ + mimeType?: string; } +/** + * Parameters for attaching temporary files to a Service Desk. + * + * @example + * ```typescript + * const attachTemporaryFileParams: AttachTemporaryFile = { + * serviceDeskId: '5', + * attachment: [ + * { + * filename: 'example.txt', + * file: Buffer.from('Temporary file content'), + * mimeType: 'text/plain', + * }, + * ], + * }; + * ``` + */ export interface AttachTemporaryFile { /** * The ID of the Service Desk to which the file will be attached. This can alternatively be a [project - * identifier.](#project-identifiers) + * identifier](#project-identifiers). + * + * @example + * ```typescript + * const serviceDeskId = '5'; + * ``` */ serviceDeskId: string; + + /** + * The attachment(s) to be added. Can be a single `Attachment` object or an array of `Attachment` objects. + * + * @example + * ```typescript + * const attachments = [ + * { + * filename: 'file1.txt', + * file: Buffer.from('Temporary content 1'), + * mimeType: 'text/plain', + * }, + * { + * filename: 'file2.jpeg', + * file: Buffer.from('Temporary content 2'), + * mimeType: 'image/jpeg', + * }, + * ]; + * ``` + */ attachment: Attachment | Attachment[]; } diff --git a/src/serviceDesk/serviceDesk.ts b/src/serviceDesk/serviceDesk.ts index 9437d6b2b3..259ba29381 100644 --- a/src/serviceDesk/serviceDesk.ts +++ b/src/serviceDesk/serviceDesk.ts @@ -1,4 +1,5 @@ -import * as FormData from 'form-data'; +import { FormData, File } from 'formdata-node'; +import * as mime from 'mime-types'; import * as Models from './models'; import * as Parameters from './parameters'; import { Callback } from '../callback'; @@ -116,7 +117,14 @@ export class ServiceDesk { const formData = new FormData(); const attachments = Array.isArray(parameters.attachment) ? parameters.attachment : [parameters.attachment]; - attachments.forEach(attachment => formData.append('file', attachment.file, attachment.filename)); + attachments.forEach(attachment => { + const mimeType = attachment.mimeType ?? (mime.lookup(attachment.filename) || undefined); + const file = Buffer.isBuffer(attachment.file) + ? new File([attachment.file], attachment.filename, { type: mimeType }) + : attachment.file; + + formData.append('file', file, attachment.filename); + }); const config: RequestConfig = { url: `/rest/servicedeskapi/servicedesk/${parameters.serviceDeskId}/attachTemporaryFile`, @@ -124,7 +132,6 @@ export class ServiceDesk { headers: { 'X-Atlassian-Token': 'no-check', 'Content-Type': 'multipart/form-data', - ...formData.getHeaders?.(), }, data: formData, }; diff --git a/src/version2/client/version2Client.ts b/src/version2/client/version2Client.ts index f2fe264178..a7413e6940 100644 --- a/src/version2/client/version2Client.ts +++ b/src/version2/client/version2Client.ts @@ -1,91 +1,89 @@ -import { BaseClient } from '../../clients'; -import { - AnnouncementBanner, - ApplicationRoles, - AppMigration, - AppProperties, - AuditRecords, - Avatars, - Dashboards, - DynamicModules, - Filters, - FilterSharing, - GroupAndUserPicker, - Groups, - IssueAttachments, - IssueCommentProperties, - IssueComments, - IssueCustomFieldConfigurationApps, - IssueCustomFieldContexts, - IssueCustomFieldOptions, - IssueCustomFieldOptionsApps, - IssueCustomFieldValuesApps, - IssueFieldConfigurations, - IssueFields, - IssueLinks, - IssueLinkTypes, - IssueNavigatorSettings, - IssueNotificationSchemes, - IssuePriorities, - IssueProperties, - IssueRemoteLinks, - IssueResolutions, - Issues, - IssueSearch, - IssueSecurityLevel, - IssueSecuritySchemes, - IssueTypeProperties, - IssueTypes, - IssueTypeSchemes, - IssueTypeScreenSchemes, - IssueVotes, - IssueWatchers, - IssueWorklogProperties, - IssueWorklogs, - JiraExpressions, - JiraSettings, - JQL, - JqlFunctionsApps, - Labels, - LicenseMetrics, - Myself, - Permissions, - PermissionSchemes, - ProjectAvatars, - ProjectCategories, - ProjectComponents, - ProjectEmail, - ProjectFeatures, - ProjectKeyAndNameValidation, - ProjectPermissionSchemes, - ProjectProperties, - ProjectRoleActors, - ProjectRoles, - Projects, - ProjectTypes, - ProjectVersions, - Screens, - ScreenSchemes, - ScreenTabFields, - ScreenTabs, - ServerInfo, - Status, - Tasks, - TimeTracking, - UIModificationsApps, - UserProperties, - Users, - UserSearch, - Webhooks, - Workflows, - WorkflowSchemeDrafts, - WorkflowSchemeProjectAssociations, - WorkflowSchemes, - WorkflowStatusCategories, - WorkflowStatuses, - WorkflowTransitionProperties, - WorkflowTransitionRules, -} from '..'; +import { BaseClient } from '../../clients/baseClient'; +import { AnnouncementBanner } from '../announcementBanner'; +import { ApplicationRoles } from '../applicationRoles'; +import { AppMigration } from '../appMigration'; +import { AppProperties } from '../appProperties'; +import { AuditRecords } from '../auditRecords'; +import { Avatars } from '../avatars'; +import { Dashboards } from '../dashboards'; +import { DynamicModules } from '../dynamicModules'; +import { Filters } from '../filters'; +import { FilterSharing } from '../filterSharing'; +import { GroupAndUserPicker } from '../groupAndUserPicker'; +import { Groups } from '../groups'; +import { IssueAttachments } from '../issueAttachments'; +import { IssueCommentProperties } from '../issueCommentProperties'; +import { IssueComments } from '../issueComments'; +import { IssueCustomFieldConfigurationApps } from '../issueCustomFieldConfigurationApps'; +import { IssueCustomFieldContexts } from '../issueCustomFieldContexts'; +import { IssueCustomFieldOptions } from '../issueCustomFieldOptions'; +import { IssueCustomFieldOptionsApps } from '../issueCustomFieldOptionsApps'; +import { IssueCustomFieldValuesApps } from '../issueCustomFieldValuesApps'; +import { IssueFieldConfigurations } from '../issueFieldConfigurations'; +import { IssueFields } from '../issueFields'; +import { IssueLinks } from '../issueLinks'; +import { IssueLinkTypes } from '../issueLinkTypes'; +import { IssueNavigatorSettings } from '../issueNavigatorSettings'; +import { IssueNotificationSchemes } from '../issueNotificationSchemes'; +import { IssuePriorities } from '../issuePriorities'; +import { IssueProperties } from '../issueProperties'; +import { IssueRemoteLinks } from '../issueRemoteLinks'; +import { IssueResolutions } from '../issueResolutions'; +import { Issues } from '../issues'; +import { IssueSearch } from '../issueSearch'; +import { IssueSecurityLevel } from '../issueSecurityLevel'; +import { IssueSecuritySchemes } from '../issueSecuritySchemes'; +import { IssueTypeProperties } from '../issueTypeProperties'; +import { IssueTypes } from '../issueTypes'; +import { IssueTypeSchemes } from '../issueTypeSchemes'; +import { IssueTypeScreenSchemes } from '../issueTypeScreenSchemes'; +import { IssueVotes } from '../issueVotes'; +import { IssueWatchers } from '../issueWatchers'; +import { IssueWorklogProperties } from '../issueWorklogProperties'; +import { IssueWorklogs } from '../issueWorklogs'; +import { JiraExpressions } from '../jiraExpressions'; +import { JiraSettings } from '../jiraSettings'; +import { JQL } from '../jQL'; +import { JqlFunctionsApps } from '../jqlFunctionsApps'; +import { Labels } from '../labels'; +import { LicenseMetrics } from '../licenseMetrics'; +import { Myself } from '../myself'; +import { Permissions } from '../permissions'; +import { PermissionSchemes } from '../permissionSchemes'; +import { ProjectAvatars } from '../projectAvatars'; +import { ProjectCategories } from '../projectCategories'; +import { ProjectComponents } from '../projectComponents'; +import { ProjectEmail } from '../projectEmail'; +import { ProjectFeatures } from '../projectFeatures'; +import { ProjectKeyAndNameValidation } from '../projectKeyAndNameValidation'; +import { ProjectPermissionSchemes } from '../projectPermissionSchemes'; +import { ProjectProperties } from '../projectProperties'; +import { ProjectRoleActors } from '../projectRoleActors'; +import { ProjectRoles } from '../projectRoles'; +import { Projects } from '../projects'; +import { ProjectTypes } from '../projectTypes'; +import { ProjectVersions } from '../projectVersions'; +import { Screens } from '../screens'; +import { ScreenSchemes } from '../screenSchemes'; +import { ScreenTabFields } from '../screenTabFields'; +import { ScreenTabs } from '../screenTabs'; +import { ServerInfo } from '../serverInfo'; +import { Status } from '../status'; +import { Tasks } from '../tasks'; +import { TimeTracking } from '../timeTracking'; +import { UIModificationsApps } from '../uIModificationsApps'; +import { UserProperties } from '../userProperties'; +import { Users } from '../users'; +import { UserSearch } from '../userSearch'; +import { Webhooks } from '../webhooks'; +import { Workflows } from '../workflows'; +import { WorkflowSchemeDrafts } from '../workflowSchemeDrafts'; +import { WorkflowSchemeProjectAssociations } from '../workflowSchemeProjectAssociations'; +import { WorkflowSchemes } from '../workflowSchemes'; +import { WorkflowStatusCategories } from '../workflowStatusCategories'; +import { WorkflowStatuses } from '../workflowStatuses'; +import { WorkflowTransitionProperties } from '../workflowTransitionProperties'; +import { WorkflowTransitionRules } from '../workflowTransitionRules'; export class Version2Client extends BaseClient { announcementBanner = new AnnouncementBanner(this); diff --git a/src/version2/issueAttachments.ts b/src/version2/issueAttachments.ts index c1961e3af4..fa0ce36b86 100644 --- a/src/version2/issueAttachments.ts +++ b/src/version2/issueAttachments.ts @@ -1,5 +1,5 @@ -// @ts-expect-error Wrong form data typings -import FormData from 'form-data'; +import { FormData, File } from 'formdata-node'; +import * as mime from 'mime-types'; import * as Models from './models'; import * as Parameters from './parameters'; import { Callback } from '../callback'; @@ -426,7 +426,14 @@ export class IssueAttachments { const formData = new FormData(); const attachments = Array.isArray(parameters.attachment) ? parameters.attachment : [parameters.attachment]; - attachments.forEach(attachment => formData.append('file', attachment.file, attachment.filename)); + attachments.forEach(attachment => { + const mimeType = attachment.mimeType ?? (mime.lookup(attachment.filename) || undefined); + const file = Buffer.isBuffer(attachment.file) + ? new File([attachment.file], attachment.filename, { type: mimeType }) + : attachment.file; + + formData.append('file', file, attachment.filename); + }); const config: RequestConfig = { url: `/rest/api/2/issue/${parameters.issueIdOrKey}/attachments`, @@ -434,7 +441,6 @@ export class IssueAttachments { headers: { 'X-Atlassian-Token': 'no-check', 'Content-Type': 'multipart/form-data', - ...formData.getHeaders?.(), }, data: formData, maxBodyLength: Infinity, diff --git a/src/version2/parameters/addAttachment.ts b/src/version2/parameters/addAttachment.ts index aff6535be8..5caab857b6 100644 --- a/src/version2/parameters/addAttachment.ts +++ b/src/version2/parameters/addAttachment.ts @@ -1,11 +1,101 @@ +/** + * Represents an attachment to be added to an issue. + * + * @example + * ```typescript + * const attachment: Attachment = { + * filename: 'example.txt', + * file: Buffer.from('Hello, world!'), + * mimeType: 'text/plain', + * }; + * ``` + */ export interface Attachment { + /** + * The name of the attachment file. + * + * @example + * ```typescript + * const filename = 'document.pdf'; + * ``` + */ filename: string; + + /** + * The content of the attachment. Can be one of the following: + * + * - `Buffer`: For binary data. + * - `ReadableStream`: For streaming large files. + * - `string`: For text-based content. + * - `Blob`: For browser-like blob objects. + * - `File`: For file objects with metadata (e.g., in web environments). + * + * @example + * ```typescript + * const fileContent = fs.readFileSync('./document.pdf'); + * ``` + */ file: Buffer | ReadableStream | string | Blob | File; + + /** + * Optional MIME type of the attachment. Example values include: + * + * - 'application/pdf' + * - 'image/png' + * + * If not provided, the MIME type will be automatically detected based on the filename. + * + * @example + * ```typescript + * const mimeType = 'application/pdf'; + * ``` + */ + mimeType?: string; } +/** + * Parameters for adding attachments to an issue. + * + * @example + * ```typescript + * const addAttachmentParams: AddAttachment = { + * issueIdOrKey: 'PROJECT-123', + * attachment: { + * filename: 'example.txt', + * file: 'Hello, world!', + * mimeType: 'text/plain', + * }, + * }; + * ``` + */ export interface AddAttachment { - /** The ID or key of the issue that attachments are added to. */ + /** + * The ID or key of the issue to which the attachments will be added. + * + * @example + * ```typescript + * const issueIdOrKey = 'PROJECT-123'; + * ``` + */ issueIdOrKey: string; + /** + * The attachment(s) to be added. Can be a single `Attachment` object or an array of `Attachment` objects. + * + * @example + * ```typescript + * const attachments = [ + * { + * filename: 'file1.txt', + * file: Buffer.from('File 1 content'), + * mimeType: 'text/plain', + * }, + * { + * filename: 'proof image.png', + * file: fs.readFileSync('./image.png'), // Reads the image file into a Buffer + * }, + * ]; + * ``` + */ attachment: Attachment | Attachment[]; } diff --git a/src/version2/parameters/updateProject.ts b/src/version2/parameters/updateProject.ts index 6f4369d9f2..8d8c4117bb 100644 --- a/src/version2/parameters/updateProject.ts +++ b/src/version2/parameters/updateProject.ts @@ -1,8 +1,8 @@ import { UpdateProjectDetails } from '../models'; export interface UpdateProject extends UpdateProjectDetails { - /** The project ID or project key (case sensitive). */ - projectIdOrKey: string; + /** The project ID or project key (case-sensitive). */ + projectIdOrKey: number | string; /** * The [project * type](https://confluence.atlassian.com/x/GwiiLQ#Jiraapplicationsoverview-Productfeaturesandprojecttypes), which diff --git a/src/version3/client/version3Client.ts b/src/version3/client/version3Client.ts index e991ff1cc6..25fb690877 100644 --- a/src/version3/client/version3Client.ts +++ b/src/version3/client/version3Client.ts @@ -1,92 +1,90 @@ -import { BaseClient } from '../../clients'; -import { - AnnouncementBanner, - ApplicationRoles, - AppMigration, - AppProperties, - AuditRecords, - Avatars, - Dashboards, - DynamicModules, - Filters, - FilterSharing, - GroupAndUserPicker, - Groups, - InstanceInformation, - IssueAttachments, - IssueCommentProperties, - IssueComments, - IssueCustomFieldConfigurationApps, - IssueCustomFieldContexts, - IssueCustomFieldOptions, - IssueCustomFieldOptionsApps, - IssueCustomFieldValuesApps, - IssueFieldConfigurations, - IssueFields, - IssueLinks, - IssueLinkTypes, - IssueNavigatorSettings, - IssueNotificationSchemes, - IssuePriorities, - IssueProperties, - IssueRemoteLinks, - IssueResolutions, - Issues, - IssueSearch, - IssueSecurityLevel, - IssueSecuritySchemes, - IssueTypeProperties, - IssueTypes, - IssueTypeSchemes, - IssueTypeScreenSchemes, - IssueVotes, - IssueWatchers, - IssueWorklogProperties, - IssueWorklogs, - JiraExpressions, - JiraSettings, - JQL, - JqlFunctionsApps, - Labels, - LicenseMetrics, - Myself, - Permissions, - PermissionSchemes, - ProjectAvatars, - ProjectCategories, - ProjectComponents, - ProjectEmail, - ProjectFeatures, - ProjectKeyAndNameValidation, - ProjectPermissionSchemes, - ProjectProperties, - ProjectRoleActors, - ProjectRoles, - Projects, - ProjectTypes, - ProjectVersions, - Screens, - ScreenSchemes, - ScreenTabFields, - ScreenTabs, - ServerInfo, - Status, - Tasks, - TimeTracking, - UIModificationsApps, - UserProperties, - Users, - UserSearch, - Webhooks, - Workflows, - WorkflowSchemeDrafts, - WorkflowSchemeProjectAssociations, - WorkflowSchemes, - WorkflowStatusCategories, - WorkflowStatuses, - WorkflowTransitionProperties, - WorkflowTransitionRules, -} from '..'; +import { BaseClient } from '../../clients/baseClient'; +import { AnnouncementBanner } from '../announcementBanner'; +import { AppMigration } from '../appMigration'; +import { AppProperties } from '../appProperties'; +import { ApplicationRoles } from '../applicationRoles'; +import { AuditRecords } from '../auditRecords'; +import { Avatars } from '../avatars'; +import { Dashboards } from '../dashboards'; +import { DynamicModules } from '../dynamicModules'; +import { FilterSharing } from '../filterSharing'; +import { Filters } from '../filters'; +import { GroupAndUserPicker } from '../groupAndUserPicker'; +import { Groups } from '../groups'; +import { InstanceInformation } from '../instanceInformation'; +import { IssueAttachments } from '../issueAttachments'; +import { IssueCommentProperties } from '../issueCommentProperties'; +import { IssueComments } from '../issueComments'; +import { IssueCustomFieldConfigurationApps } from '../issueCustomFieldConfigurationApps'; +import { IssueCustomFieldContexts } from '../issueCustomFieldContexts'; +import { IssueCustomFieldOptions } from '../issueCustomFieldOptions'; +import { IssueCustomFieldOptionsApps } from '../issueCustomFieldOptionsApps'; +import { IssueCustomFieldValuesApps } from '../issueCustomFieldValuesApps'; +import { IssueFieldConfigurations } from '../issueFieldConfigurations'; +import { IssueFields } from '../issueFields'; +import { IssueLinkTypes } from '../issueLinkTypes'; +import { IssueLinks } from '../issueLinks'; +import { IssueNavigatorSettings } from '../issueNavigatorSettings'; +import { IssueNotificationSchemes } from '../issueNotificationSchemes'; +import { IssuePriorities } from '../issuePriorities'; +import { IssueProperties } from '../issueProperties'; +import { IssueRemoteLinks } from '../issueRemoteLinks'; +import { IssueResolutions } from '../issueResolutions'; +import { IssueSearch } from '../issueSearch'; +import { IssueSecurityLevel } from '../issueSecurityLevel'; +import { IssueSecuritySchemes } from '../issueSecuritySchemes'; +import { IssueTypeProperties } from '../issueTypeProperties'; +import { IssueTypeSchemes } from '../issueTypeSchemes'; +import { IssueTypeScreenSchemes } from '../issueTypeScreenSchemes'; +import { IssueTypes } from '../issueTypes'; +import { IssueVotes } from '../issueVotes'; +import { IssueWatchers } from '../issueWatchers'; +import { IssueWorklogProperties } from '../issueWorklogProperties'; +import { IssueWorklogs } from '../issueWorklogs'; +import { Issues } from '../issues'; +import { JiraExpressions } from '../jiraExpressions'; +import { JiraSettings } from '../jiraSettings'; +import { JQL } from '../jQL'; +import { JqlFunctionsApps } from '../jqlFunctionsApps'; +import { Labels } from '../labels'; +import { LicenseMetrics } from '../licenseMetrics'; +import { Myself } from '../myself'; +import { PermissionSchemes } from '../permissionSchemes'; +import { Permissions } from '../permissions'; +import { ProjectAvatars } from '../projectAvatars'; +import { ProjectCategories } from '../projectCategories'; +import { ProjectComponents } from '../projectComponents'; +import { ProjectEmail } from '../projectEmail'; +import { ProjectFeatures } from '../projectFeatures'; +import { ProjectKeyAndNameValidation } from '../projectKeyAndNameValidation'; +import { ProjectPermissionSchemes } from '../projectPermissionSchemes'; +import { ProjectProperties } from '../projectProperties'; +import { ProjectRoleActors } from '../projectRoleActors'; +import { ProjectRoles } from '../projectRoles'; +import { ProjectTypes } from '../projectTypes'; +import { ProjectVersions } from '../projectVersions'; +import { Projects } from '../projects'; +import { ScreenSchemes } from '../screenSchemes'; +import { ScreenTabFields } from '../screenTabFields'; +import { ScreenTabs } from '../screenTabs'; +import { Screens } from '../screens'; +import { ServerInfo } from '../serverInfo'; +import { Status } from '../status'; +import { Tasks } from '../tasks'; +import { TimeTracking } from '../timeTracking'; +import { UIModificationsApps } from '../uIModificationsApps'; +import { UserProperties } from '../userProperties'; +import { UserSearch } from '../userSearch'; +import { Users } from '../users'; +import { Webhooks } from '../webhooks'; +import { Workflows } from '../workflows'; +import { WorkflowSchemeDrafts } from '../workflowSchemeDrafts'; +import { WorkflowSchemeProjectAssociations } from '../workflowSchemeProjectAssociations'; +import { WorkflowSchemes } from '../workflowSchemes'; +import { WorkflowStatusCategories } from '../workflowStatusCategories'; +import { WorkflowStatuses } from '../workflowStatuses'; +import { WorkflowTransitionProperties } from '../workflowTransitionProperties'; +import { WorkflowTransitionRules } from '../workflowTransitionRules'; export class Version3Client extends BaseClient { announcementBanner = new AnnouncementBanner(this); diff --git a/src/version3/issueAttachments.ts b/src/version3/issueAttachments.ts index cbae4311fa..24ed0b36ad 100644 --- a/src/version3/issueAttachments.ts +++ b/src/version3/issueAttachments.ts @@ -1,5 +1,5 @@ -// @ts-expect-error Wrong form data typings -import FormData from 'form-data'; +import { FormData, File } from 'formdata-node'; +import * as mime from 'mime-types'; import * as Models from './models'; import * as Parameters from './parameters'; import { Callback } from '../callback'; @@ -426,7 +426,14 @@ export class IssueAttachments { const formData = new FormData(); const attachments = Array.isArray(parameters.attachment) ? parameters.attachment : [parameters.attachment]; - attachments.forEach(attachment => formData.append('file', attachment.file, attachment.filename)); + attachments.forEach(attachment => { + const mimeType = attachment.mimeType ?? (mime.lookup(attachment.filename) || undefined); + const file = Buffer.isBuffer(attachment.file) + ? new File([attachment.file], attachment.filename, { type: mimeType }) + : attachment.file; + + formData.append('file', file, attachment.filename); + }); const config: RequestConfig = { url: `/rest/api/3/issue/${parameters.issueIdOrKey}/attachments`, @@ -434,7 +441,6 @@ export class IssueAttachments { headers: { 'X-Atlassian-Token': 'no-check', 'Content-Type': 'multipart/form-data', - ...formData.getHeaders?.(), }, data: formData, maxBodyLength: Infinity, diff --git a/src/version3/models/index.ts b/src/version3/models/index.ts index 45c21ade21..68860e2b33 100644 --- a/src/version3/models/index.ts +++ b/src/version3/models/index.ts @@ -155,7 +155,6 @@ export * from './icon'; export * from './id'; export * from './idOrKey'; export * from './includedFields'; -export * from './index'; export * from './issue'; export * from './issueArchivalSync'; export * from './issueArchivalSyncRequest'; diff --git a/src/version3/parameters/addAttachment.ts b/src/version3/parameters/addAttachment.ts index aff6535be8..e372d3896a 100644 --- a/src/version3/parameters/addAttachment.ts +++ b/src/version3/parameters/addAttachment.ts @@ -1,11 +1,101 @@ +/** + * Represents an attachment to be added to an issue. + * + * @example + * ```typescript + * const attachment: Attachment = { + * filename: 'example.txt', + * file: Buffer.from('Hello, world!'), + * mimeType: 'text/plain', + * }; + * ``` + */ export interface Attachment { + /** + * The name of the attachment file. + * + * @example + * ```typescript + * const filename = 'document.pdf'; + * ``` + */ filename: string; + + /** + * The content of the attachment. Can be one of the following: + * + * - `Buffer`: For binary data. + * - `ReadableStream`: For streaming large files. + * - `string`: For text-based content. + * - `Blob`: For browser-like blob objects. + * - `File`: For file objects with metadata (e.g., in web environments). + * + * @example + * ```typescript + * const fileContent = fs.readFileSync('./document.pdf'); + * ``` + */ file: Buffer | ReadableStream | string | Blob | File; + + /** + * Optional MIME type of the attachment. Example values include: + * + * - 'application/pdf' + * - 'image/png' + * + * If not provided, the MIME type will be automatically detected based on the filename. + * + * @example + * ```typescript + * const mimeType = 'application/pdf'; + * ``` + */ + mimeType?: string; } +/** + * Parameters for adding attachments to an issue. + * + * @example + * ```typescript + * const addAttachmentParams: AddAttachment = { + * issueIdOrKey: 'PROJECT-123', + * attachment: { + * filename: 'example.txt', + * file: 'Hello, world!', + * mimeType: 'text/plain', + * }, + * }; + * ``` + */ export interface AddAttachment { - /** The ID or key of the issue that attachments are added to. */ + /** + * The ID or key of the issue to which the attachments will be added. + * + * @example + * ```typescript + * const issueIdOrKey = 'PROJECT-123'; + * ``` + */ issueIdOrKey: string; + /** + * The attachment(s) to be added. Can be a single `Attachment` object or an array of `Attachment` objects. + * + * @example + * ```typescript + * const attachments = [ + * { + * filename: 'file1.txt', + * file: Buffer.from('File 1 content'), + * mimeType: 'text/plain', + * }, + * { + * filename: 'proof image.png', + * file: fs.readFileSync('./image.png'), // Reads the image file into a Buffer + * }, + * ]; + * ``` + */ attachment: Attachment | Attachment[]; } diff --git a/src/version3/parameters/updateProject.ts b/src/version3/parameters/updateProject.ts index e37370bf45..e800522df0 100644 --- a/src/version3/parameters/updateProject.ts +++ b/src/version3/parameters/updateProject.ts @@ -2,7 +2,7 @@ import { UpdateProjectDetails } from '../models'; export interface UpdateProject extends UpdateProjectDetails { /** The project ID or project key (case-sensitive). */ - projectIdOrKey: string; + projectIdOrKey: number | string; projectTypeKey?: string; projectTemplateKey?: string; /** diff --git a/tests/integration/version2/issueAttachments.test.ts b/tests/integration/version2/issueAttachments.test.ts index 8e06907767..195d035a88 100644 --- a/tests/integration/version2/issueAttachments.test.ts +++ b/tests/integration/version2/issueAttachments.test.ts @@ -45,12 +45,29 @@ test.sequential('should add attachment', async ({ expect }) => { expect(attachments[0].mimeType).toBe('video/mp2t'); }); +test.sequential('should add attachment with custom MIME type', async ({ expect }) => { + const customMimeType = 'application/typescript'; + + const customAttachment = await client.issueAttachments.addAttachment({ + issueIdOrKey: issue.key, + attachment: { + filename: 'issueAttachments.test.ts', + file: fs.readFileSync('./tests/integration/version2/issueAttachments.test.ts'), + mimeType: customMimeType, + }, + }); + + expect(!!customAttachment).toBeTruthy(); + expect(customAttachment[0].filename).toBe('issueAttachments.test.ts'); + expect(customAttachment[0].mimeType).toBe(customMimeType); +}); + test.sequential('should getAttachmentContent', async ({ expect }) => { const content = await client.issueAttachments.getAttachmentContent({ id: attachments[0].id }); expect(Buffer.isBuffer(content)).toBeTruthy(); }); -test.sequential('should remove attachment', async ({ expect }) => { +test.sequential('should remove attachment', async () => { await client.issues.deleteIssue({ issueIdOrKey: issue.key }); }); diff --git a/tests/integration/version3/issueAttachments.test.ts b/tests/integration/version3/issueAttachments.test.ts index a1eed763a7..4a2f21043f 100644 --- a/tests/integration/version3/issueAttachments.test.ts +++ b/tests/integration/version3/issueAttachments.test.ts @@ -45,12 +45,29 @@ test.sequential('should add attachment', async ({ expect }) => { expect(attachments[0].mimeType).toBe('video/mp2t'); }); +test.sequential('should add attachment with custom MIME type', async ({ expect }) => { + const customMimeType = 'application/typescript'; + + const customAttachment = await client.issueAttachments.addAttachment({ + issueIdOrKey: issue.key, + attachment: { + filename: 'issueAttachments.test.ts', + file: fs.readFileSync('./tests/integration/version2/issueAttachments.test.ts'), + mimeType: customMimeType, + }, + }); + + expect(!!customAttachment).toBeTruthy(); + expect(customAttachment[0].filename).toBe('issueAttachments.test.ts'); + expect(customAttachment[0].mimeType).toBe(customMimeType); +}); + test.sequential('should getAttachmentContent', async ({ expect }) => { const content = await client.issueAttachments.getAttachmentContent({ id: attachments[0].id }); expect(Buffer.isBuffer(content)).toBeTruthy(); }); -test.sequential('should remove attachment', async ({ expect }) => { +test.sequential('should remove attachment', async () => { await client.issues.deleteIssue({ issueIdOrKey: issue.key }); });