diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4076858..b8a6e3c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,6 +24,7 @@ updates: - "@commitlint*" eslint: patterns: + - "@eslint-community*" - "eslint*" ignore: # Below are dependencies that have migrated to ESM diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index fdca212..7201ad4 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -53,11 +53,11 @@ jobs: - name: Publish to NPM env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - # Build docs and TS definitions, and remove dev values + # Build docs/definitions, and remove dev values # from package.json before publishing to reduce package size run: | npm i --ignore-scripts - npm run build + npm run build --if-present npm pkg delete commitlint devDependencies jest scripts npm publish --access public --ignore-scripts --provenance @@ -66,6 +66,7 @@ jobs: needs: release if: needs.release.outputs.release_created == 'true' runs-on: ubuntu-latest + environment: main permissions: contents: read id-token: write @@ -85,16 +86,16 @@ jobs: - name: Scope package run: | - pkgName=$(npm pkg get name | tr -d '"') + pkgName=$(node -p "require('./package.json').name") npm pkg set name="@fdawgs/$pkgName" - name: Publish to GitHub Packages env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Build docs and TS definitions, and remove dev values + # Build docs/definitions, and remove dev values # from package.json before publishing to reduce package size run: | npm i --ignore-scripts - npm run build + npm run build --if-present npm pkg delete commitlint devDependencies jest scripts npm publish --access public --ignore-scripts --provenance diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b9b527..797b6a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,8 +69,8 @@ jobs: - name: Run License Checker run: npm run lint:licenses - - name: Compile TypeScript Definition File - run: npm run build + - name: Run Build + run: npm run build --if-present commit-lint: name: Lint Commit Messages diff --git a/.gitignore b/.gitignore index 2b6b96e..3199d26 100644 --- a/.gitignore +++ b/.gitignore @@ -135,6 +135,9 @@ dist # Clinic.js .clinic +# Auto generated files +types/ + # lock files bun.lockb package-lock.json diff --git a/.prettierignore b/.prettierignore index 5ee876f..159db3f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -137,9 +137,8 @@ dist # Auto generated files package.json -API.md CHANGELOG.md -/types/index.d.ts +types/ # lock files bun.lockb diff --git a/API.md b/API.md deleted file mode 100644 index 32dd497..0000000 --- a/API.md +++ /dev/null @@ -1,59 +0,0 @@ - - -## UnRTF -**Kind**: global class - -* [UnRTF](#UnRTF) - * [new UnRTF([binPath])](#new_UnRTF_new) - * [.unrtfPath](#UnRTF+unrtfPath) : string \| undefined - * [.unrtfVersion](#UnRTF+unrtfVersion) : string \| undefined - * [.unrtfAcceptedOptions](#UnRTF+unrtfAcceptedOptions) : object - * [.convert(file, [options])](#UnRTF+convert) ⇒ Promise.<string> - - - -### new UnRTF([binPath]) - -| Param | Type | Description | -| --- | --- | --- | -| [binPath] | string | Path of UnRTF binary. If not provided, the constructor will attempt to find the binary in the PATH environment variable. For `win32`, a binary is bundled with the package and will be used if a local installation is not found. | - - - -### unRTF.unrtfPath : string \| undefined -**Kind**: instance property of [UnRTF](#UnRTF) - - -### unRTF.unrtfVersion : string \| undefined -**Kind**: instance property of [UnRTF](#UnRTF) - - -### unRTF.unrtfAcceptedOptions : object -**Kind**: instance property of [UnRTF](#UnRTF) - - -### unRTF.convert(file, [options]) ⇒ Promise.<string> -Converts an RTF file to HTML/LaTeX/RTF/TXT. -Defaults to HTML output if no `output*` options are provided. -UnRTF will use the directory of the original file to store embedded pictures. - -**Kind**: instance method of [UnRTF](#UnRTF) -**Returns**: Promise.<string> - A promise that resolves with a stdout string, or rejects with an `Error` object. -**Author**: Frazer Smith - -| Param | Type | Description | -| --- | --- | --- | -| file | string | Filepath of the RTF file to read. | -| [options] | object | Object containing options to pass to binary. | -| [options.noPictures] | boolean | Disable the automatic storing of embedded pictures to the directory of the original file. | -| [options.noRemap] | boolean | Disable charset conversion (only works for 8-bit charsets) (UnRTF v0.20.5 or later only). | -| [options.outputHtml] | boolean | Generate HTML output. | -| [options.outputLatex] | boolean | Generate LaTeX output. | -| [options.outputPs] | boolean | Generate PostScript (PS) output (UnRTF v0.19.4 or earlier only). | -| [options.outputRtf] | boolean | Generate RTF output. (UnRTF v0.21.3 or later only). | -| [options.outputText] | boolean | Generate ASCII text output. | -| [options.outputVt] | boolean | Generate text output with VT100 escape codes. | -| [options.outputWpml] | boolean | Generate WPML output (UnRTF v0.19.4 or earlier only). | -| [options.printVersionInfo] | boolean | Print copyright and version info. | -| [options.quiet] | boolean | Do not print any leading comments in output (UnRTF v0.21.3 or later only). | - diff --git a/README.md b/README.md index 9f0fd49..252f6b2 100644 --- a/README.md +++ b/README.md @@ -45,15 +45,9 @@ For macOS, the binary can be installed with [Homebrew](https://brew.sh/): brew install unrtf ``` -## API +## Example usage -```js -const { UnRTF } = require("node-unrtf"); -``` - -[**API Documentation can be found here**](https://github.com/Fdawgs/node-unrtf/blob/main/API.md) - -## Examples +Please refer to the [JSDoc comments in the source code](./src/index.js) or the [generated type definitions](https://www.npmjs.com/package/node-unrtf?activeTab=code) for information on the available options. ### Async Await diff --git a/package.json b/package.json index a7213b0..144bc52 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "node": ">=18.0.0" }, "scripts": { - "build": "tsc && jsdoc2md src/index.js > API.md", + "build": "tsc", "jest": "jest", "jest:coverage": "jest --coverage", "lint": "eslint . --cache --ext js,jsx --ignore-path .gitignore", @@ -74,27 +74,26 @@ "testTimeout": 10000 }, "devDependencies": { - "@commitlint/cli": "^19.0.3", - "@commitlint/config-conventional": "^19.0.3", + "@commitlint/cli": "^19.1.0", + "@commitlint/config-conventional": "^19.1.0", "@eslint-community/eslint-plugin-eslint-comments": "^4.1.0", "@types/jest": "^29.5.12", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-config-airbnb-base": "^15.0.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^27.9.0", - "eslint-plugin-jsdoc": "^48.1.0", + "eslint-plugin-jsdoc": "^48.2.1", "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-regexp": "^2.2.0", + "eslint-plugin-regexp": "^2.3.0", "eslint-plugin-security": "^2.1.1", "husky": "^9.0.11", "is-html": "^2.0.0", "jest": "^29.7.0", - "jsdoc-to-markdown": "^8.0.1", "license-checker": "^25.0.1", "prettier": "^3.2.5", "spdx-copyleft": "^1.0.0", - "typescript": "^5.3.3" + "typescript": "^5.4.2" }, "dependencies": { "semver": "^7.6.0", diff --git a/src/index.js b/src/index.js index d2629d7..68e223f 100644 --- a/src/index.js +++ b/src/index.js @@ -15,14 +15,16 @@ const unrtfPathRegex = /(.+)unrtf/u; // UnRTF version output is inconsistent between versions but always starts with the semantic version number const unrtfVersionRegex = /^(\d{1,2}\.\d{1,2}\.\d{1,2})/u; +/** @typedef {{[key: string]: {arg: string, type: string, minVersion: string, maxVersion?: string}}} UnRTFAcceptedOptions */ + /** * @author Frazer Smith * @description Checks each option provided is valid, of the correct type, and can be used by specified * version of binary. * @ignore - * @param {object} acceptedOptions - Object containing accepted options. - * @param {object} options - Object containing options to pass to binary. - * @param {string} [version] - Semantic version of binary. + * @param {UnRTFAcceptedOptions} acceptedOptions - Object containing accepted options. + * @param {{[key: string]: any}} options - Object containing options to pass to binary. + * @param {string} version - Semantic version of binary. * @returns {string[]} Array of CLI arguments. * @throws If invalid arguments provided. */ @@ -48,23 +50,15 @@ function parseOptions(acceptedOptions, options, version) { ); } - if ( - acceptedOptions[key].minVersion && - version && - lt(version, acceptedOptions[key].minVersion) - ) { + /* istanbul ignore next: unable to test due to https://github.com/jestjs/jest/pull/14297 */ + if (lt(version, acceptedOptions[key].minVersion)) { invalidArgs.push( `Invalid option provided for the current version of the binary used. '${key}' was introduced in v${acceptedOptions[key].minVersion}, but received v${version}` ); } - /* istanbul ignore next: requires incredibly old version of UnRTF to test */ - if ( - acceptedOptions[key].maxVersion && - version && - // @ts-ignore: type checking is done above - gt(version, acceptedOptions[key].maxVersion) - ) { + /* istanbul ignore next: unable to test due to https://github.com/jestjs/jest/pull/14297 */ + if (gt(version, acceptedOptions[key].maxVersion || version)) { invalidArgs.push( `Invalid option provided for the current version of the binary used. '${key}' is only present up to v${acceptedOptions[key].maxVersion}, but received v${version}` ); @@ -80,6 +74,10 @@ function parseOptions(acceptedOptions, options, version) { } class UnRTF { + #unrtfPath; + + #unrtfVersion; + /** * @param {string} [binPath] - Path of UnRTF binary. * If not provided, the constructor will attempt to find the binary @@ -89,10 +87,12 @@ class UnRTF { * if a local installation is not found. */ constructor(binPath) { + this.#unrtfPath = ""; + /* istanbul ignore else: requires specific OS */ if (binPath) { /** @type {string|undefined} */ - this.unrtfPath = binPath; + this.#unrtfPath = binPath; } else { const { platform } = process; @@ -102,10 +102,10 @@ class UnRTF { const unrtfPath = unrtfPathRegex.exec(which)?.[1]; if (unrtfPath) { - this.unrtfPath = unrtfPath; + this.#unrtfPath = unrtfPath; } if (platform === "win32" && !unrtfPath) { - this.unrtfPath = joinSafe( + this.#unrtfPath = joinSafe( __dirname, "lib", "win32", @@ -115,24 +115,29 @@ class UnRTF { } } - if (!this.unrtfPath) { + /* istanbul ignore next: unable to test due to https://github.com/jestjs/jest/pull/14297 */ + if (!this.#unrtfPath) { throw new Error( `Unable to find ${process.platform} UnRTF binaries, please pass the installation directory as a parameter to the UnRTF instance.` ); } - this.unrtfPath = normalizeTrim(this.unrtfPath); + this.#unrtfPath = normalizeTrim(this.#unrtfPath); /** * Get version of UnRTF binary for use in `convert` function. * UnRTF outputs the version into stderr. */ - const version = spawnSync(joinSafe(this.unrtfPath, "unrtf"), [ + const version = spawnSync(joinSafe(this.#unrtfPath, "unrtf"), [ "--version", ]).stderr.toString(); - /** @type {string|undefined} */ - this.unrtfVersion = unrtfVersionRegex.exec(version)?.[1]; + this.#unrtfVersion = unrtfVersionRegex.exec(version)?.[1] || ""; - /** @type {object} */ + /* istanbul ignore next: unable to test due to https://github.com/jestjs/jest/pull/14297 */ + if (!this.#unrtfVersion) { + throw new Error("Unable to determine UnRTF version."); + } + + /** @type {UnRTFAcceptedOptions} */ this.unrtfAcceptedOptions = { noPictures: { arg: "--nopict", @@ -178,6 +183,22 @@ class UnRTF { }; } + /** + * @description Returns the path of the UnRTF binary. + * @returns {string} Path of UnRTF binary. + */ + get path() { + return this.#unrtfPath; + } + + /** + * @description Returns the version of the UnRTF binary. + * @returns {string} Version of UnRTF binary. + */ + get version() { + return this.#unrtfVersion; + } + /** * @author Frazer Smith * @description Converts an RTF file to HTML/LaTeX/RTF/TXT. @@ -222,12 +243,12 @@ class UnRTF { const args = parseOptions( this.unrtfAcceptedOptions, options, - this.unrtfVersion + this.#unrtfVersion ); args.push(normalizeTrim(file)); return new Promise((resolve, reject) => { - const child = spawn(joinSafe(this.unrtfPath, "unrtf"), args); + const child = spawn(joinSafe(this.#unrtfPath, "unrtf"), args); let stdOut = ""; let stdErr = ""; @@ -247,6 +268,7 @@ class UnRTF { } else if (stdErr === "") { reject( new Error( + // @ts-ignore: Second operand used if code is not in errorMessages errorMessages[code] || `unrtf ${args.join( " " @@ -261,5 +283,5 @@ class UnRTF { } } -module.exports.UnRTF = UnRTF; -module.exports.default = UnRTF; +module.exports.default = UnRTF; // ESM default export +module.exports.UnRTF = UnRTF; // TypeScript and named export diff --git a/src/index.test.js b/src/index.test.js index 11c2b14..750d49c 100644 --- a/src/index.test.js +++ b/src/index.test.js @@ -57,7 +57,8 @@ describe("Constructor", () => { it("Creates a new UnRTF instance without the binary path set", () => { const unRtf = new UnRTF(); - expect(unRtf.unrtfPath).toBe(testBinaryPath); + expect(unRtf.path).toBe(testBinaryPath); + expect(unRtf.version).toEqual(expect.any(String)); }); /** @@ -92,6 +93,36 @@ describe("Constructor", () => { ); } }); + + /** + * @todo Fix this test, mocking of "node:" scheme not supported yet. + * @see {@link https://github.com/jestjs/jest/pull/14297 | Jest PR #14297} + */ + // eslint-disable-next-line jest/no-disabled-tests -- Blocked by Jest PR #14297 + it.skip("Throws an Error if the version of the binary cannot be determined", () => { + // Ensure the mock is used by the UnRTF constructor + jest.resetModules(); + jest.mock("node:child_process", () => ({ + spawnSync: jest.fn(() => ({ + stdout: { + toString: () => "/usr/bin/unrtf", + }, + stderr: { + toString: () => "", + }, + })), + })); + require("node:child_process"); + const { UnRTF: UnRTFMock } = require("./index"); + + expect.assertions(1); + try { + // eslint-disable-next-line no-unused-vars -- This is intentional + const unRtf = new UnRTFMock(); + } catch (err) { + expect(err.message).toBe("mock error"); + } + }); }); describe("Convert function", () => { diff --git a/tsconfig.json b/tsconfig.json index e73e3c3..be385e0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "moduleResolution": "NodeNext", "outDir": "types", "resolveJsonModule": true, + "strict": true, "target": "ES2022" }, "include": ["src/index.js"] diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index 04c71fe..0000000 --- a/types/index.d.ts +++ /dev/null @@ -1,53 +0,0 @@ -export default UnRTF; -export class UnRTF { - /** - * @param {string} [binPath] - Path of UnRTF binary. - * If not provided, the constructor will attempt to find the binary - * in the PATH environment variable. - * - * For `win32`, a binary is bundled with the package and will be used - * if a local installation is not found. - */ - constructor(binPath?: string); - /** @type {string|undefined} */ - unrtfPath: string | undefined; - /** @type {string|undefined} */ - unrtfVersion: string | undefined; - /** @type {object} */ - unrtfAcceptedOptions: object; - /** - * @author Frazer Smith - * @description Converts an RTF file to HTML/LaTeX/RTF/TXT. - * Defaults to HTML output if no `output*` options are provided. - * UnRTF will use the directory of the original file to store embedded pictures. - * @param {string} file - Filepath of the RTF file to read. - * @param {object} [options] - Object containing options to pass to binary. - * @param {boolean} [options.noPictures] - Disable the automatic storing of embedded - * pictures to the directory of the original file. - * @param {boolean} [options.noRemap] - Disable charset conversion (only works for 8-bit charsets) - * (UnRTF v0.20.5 or later only). - * @param {boolean} [options.outputHtml] - Generate HTML output. - * @param {boolean} [options.outputLatex] - Generate LaTeX output. - * @param {boolean} [options.outputPs] - Generate PostScript (PS) output (UnRTF v0.19.4 or earlier only). - * @param {boolean} [options.outputRtf] - Generate RTF output. (UnRTF v0.21.3 or later only). - * @param {boolean} [options.outputText] - Generate ASCII text output. - * @param {boolean} [options.outputVt] - Generate text output with VT100 escape codes. - * @param {boolean} [options.outputWpml] - Generate WPML output (UnRTF v0.19.4 or earlier only). - * @param {boolean} [options.printVersionInfo] - Print copyright and version info. - * @param {boolean} [options.quiet] - Do not print any leading comments in output (UnRTF v0.21.3 or later only). - * @returns {Promise} A promise that resolves with a stdout string, or rejects with an `Error` object. - */ - convert(file: string, options?: { - noPictures?: boolean; - noRemap?: boolean; - outputHtml?: boolean; - outputLatex?: boolean; - outputPs?: boolean; - outputRtf?: boolean; - outputText?: boolean; - outputVt?: boolean; - outputWpml?: boolean; - printVersionInfo?: boolean; - quiet?: boolean; - }): Promise; -}