diff --git a/package-lock.json b/package-lock.json index be1fd26..6055096 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@apidevtools/json-schema-ref-parser": "^14.0.2", "ajv": "^8.17.1", "axios": "^1.10.0", - "doc-detective-common": "^3.1.1", + "doc-detective-common": "^3.1.1-dev.2", "dotenv": "^16.5.0", "json-schema-faker": "^0.5.9", "posthog-node": "^5.1.1", @@ -726,8 +726,20 @@ } }, "node_modules/doc-detective-common": { - "resolved": "workspaces/doc-detective-common", - "link": true + "version": "3.1.1-dev.2", + "resolved": "https://registry.npmjs.org/doc-detective-common/-/doc-detective-common-3.1.1-dev.2.tgz", + "integrity": "sha512-DcmiHT39Mg3sK9Zm5JjdkbIqLQJ6JeOxfUryQgn3H9A6N+a1re9cAOti8O+nFaQCTamh1RIuz6l1O3G8slJNyg==", + "license": "AGPL-3.0-only", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^14.0.2", + "ajv": "^8.17.1", + "ajv-errors": "^3.0.0", + "ajv-formats": "^3.0.1", + "ajv-keywords": "^5.1.0", + "axios": "^1.10.0", + "uuid": "^11.1.0", + "yaml": "^2.8.0" + } }, "node_modules/dotenv": { "version": "16.5.0", @@ -2717,25 +2729,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "workspaces/doc-detective-common": { - "version": "3.1.1", - "license": "AGPL-3.0-only", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^14.0.2", - "ajv": "^8.17.1", - "ajv-errors": "^3.0.0", - "ajv-formats": "^3.0.1", - "ajv-keywords": "^5.1.0", - "axios": "^1.10.0", - "uuid": "^11.1.0", - "yaml": "^2.8.0" - }, - "devDependencies": { - "chai": "^5.2.0", - "mocha": "^11.7.0", - "sinon": "^21.0.0" - } } } } diff --git a/package.json b/package.json index 0dc95cf..5526edb 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@apidevtools/json-schema-ref-parser": "^14.0.2", "ajv": "^8.17.1", "axios": "^1.10.0", - "doc-detective-common": "^3.1.1", + "doc-detective-common": "^3.1.1-dev.2", "dotenv": "^16.5.0", "json-schema-faker": "^0.5.9", "posthog-node": "^5.1.1", diff --git a/src/config.js b/src/config.js index cc2ea65..47b8e2b 100644 --- a/src/config.js +++ b/src/config.js @@ -4,6 +4,7 @@ const { log, loadEnvs, replaceEnvs } = require("./utils"); const { loadDescription } = require("./openapi"); exports.setConfig = setConfig; +exports.resolveConcurrentRunners = resolveConcurrentRunners; /** * Deep merge two objects, with override properties taking precedence @@ -190,6 +191,22 @@ defaultFileTypes = { html: defaultFileTypes.html_1_0, }; +/** + * Resolves the concurrentRunners configuration value from various input formats + * to a concrete integer for the core execution engine. + * + * @param {Object} config - The configuration object + * @returns {number} The resolved concurrent runners value + */ +function resolveConcurrentRunners(config) { + if (config.concurrentRunners === true) { + // Cap at 4 only for the boolean convenience option + return Math.min(os.cpus().length, 4); + } + // Respect explicit numeric values and default + return config.concurrentRunners || 1; +} + /** * Sets up and validates the configuration object for Doc Detective * @async @@ -380,6 +397,10 @@ async function setConfig({ config }) { // Detect current environment. config.environment = getEnvironment(); + + // Resolve concurrent runners configuration + config.concurrentRunners = resolveConcurrentRunners(config); + // TODO: Revise loadDescriptions() so it doesn't mutate the input but instead returns an updated object await loadDescriptions(config); diff --git a/src/config.test.js b/src/config.test.js index 45734f1..3880c5b 100644 --- a/src/config.test.js +++ b/src/config.test.js @@ -402,3 +402,103 @@ function deepObjectExpect(actual, expected) { } }); } + +describe("resolveConcurrentRunners", function () { + const { resolveConcurrentRunners } = require("./config"); + const os = require("os"); + let originalCpus; + + beforeEach(function () { + // Save original os.cpus function + originalCpus = os.cpus; + }); + + afterEach(function () { + // Restore original os.cpus function + os.cpus = originalCpus; + }); + + it("should resolve boolean true on 8-core system to 4", function () { + // Mock os.cpus().length = 8 + os.cpus = sinon.stub().returns(new Array(8)); + + const result = resolveConcurrentRunners({ concurrentRunners: true }); + expect(result).to.equal(4); + }); + + it("should resolve boolean true on 2-core system to 2", function () { + // Mock os.cpus().length = 2 + os.cpus = sinon.stub().returns(new Array(2)); + + const result = resolveConcurrentRunners({ concurrentRunners: true }); + expect(result).to.equal(2); + }); + + it("should resolve boolean true on 16-core system to 4", function () { + // Mock os.cpus().length = 16 + os.cpus = sinon.stub().returns(new Array(16)); + + const result = resolveConcurrentRunners({ concurrentRunners: true }); + expect(result).to.equal(4); + }); + + it("should resolve boolean true on 1-core system to 1", function () { + // Mock os.cpus().length = 1 + os.cpus = sinon.stub().returns(new Array(1)); + + const result = resolveConcurrentRunners({ concurrentRunners: true }); + expect(result).to.equal(1); + }); + + it("should resolve explicit integer 8 to 8", function () { + const result = resolveConcurrentRunners({ concurrentRunners: 8 }); + expect(result).to.equal(8); + }); + + it("should resolve explicit integer 1 to 1", function () { + const result = resolveConcurrentRunners({ concurrentRunners: 1 }); + expect(result).to.equal(1); + }); + + it("should resolve explicit integer 16 to 16", function () { + const result = resolveConcurrentRunners({ concurrentRunners: 16 }); + expect(result).to.equal(16); + }); + + it("should resolve undefined to 1", function () { + const result = resolveConcurrentRunners({}); + expect(result).to.equal(1); + }); + + it("should resolve null to 1", function () { + const result = resolveConcurrentRunners({ concurrentRunners: null }); + expect(result).to.equal(1); + }); + + it("should resolve 0 to 1", function () { + const result = resolveConcurrentRunners({ concurrentRunners: 0 }); + expect(result).to.equal(1); + }); + + it("should resolve boolean false to 1", function () { + const result = resolveConcurrentRunners({ concurrentRunners: false }); + expect(result).to.equal(1); + }); + + it("should handle integration with setConfig function", async function () { + const inputConfig = { + input: ["test.md"], + concurrentRunners: true, + logLevel: "info", + fileTypes: ["markdown"] + }; + + // Mock CPU count to 8 cores + os.cpus = sinon.stub().returns(new Array(8)); + + const result = await setConfig({ config: inputConfig }); + + // Should resolve boolean true to 4 (capped) on 8-core system + expect(result.concurrentRunners).to.equal(4); + }); +});