diff --git a/docs/template.md b/docs/template.md index a08688e78..d36357b56 100644 --- a/docs/template.md +++ b/docs/template.md @@ -16,7 +16,7 @@ Examples outputs: A template is an independent Node.js project unrelated to the `generator` repository. AsyncAPI templates are managed, released, and published separately. You can also create templates and manage templates on your own. -The generator uses the official [Arborist](https://www.npmjs.com/package/@npmcli/arborist) NPM library. (This means templates do not have to be published to package managers to use them.) Arborist helps the generator fetch the template's source code and use it for the generation process. +The generator uses the official [Arborist](https://www.npmjs.com/package/@npmcli/arborist) NPM library. (This means templates do not have to be published to package managers to use them.) Arborist helps the generator fetch the template's source code and use it for the generation process. By default, this library pulls data from the default NPM registry, which is https://registry.npmjs.org. You can also configure the generator to fetch templates that are private or hosted in different NPM registries. You can store template projects on a local drive or as a `git` repository during the development process. diff --git a/lib/generator.js b/lib/generator.js index 4ce95d202..dc13e60e0 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -46,7 +46,8 @@ const DEFAULT_TEMPLATES_DIR = path.resolve(ROOT_DIR, 'node_modules'); const TRANSPILED_TEMPLATE_LOCATION = '__transpiled'; const TEMPLATE_CONTENT_DIRNAME = 'template'; -const GENERATOR_OPTIONS = ['debug', 'disabledHooks', 'entrypoint', 'forceWrite', 'install', 'noOverwriteGlobs', 'output', 'templateParams', 'mapBaseUrlToFolder']; +const GENERATOR_OPTIONS = ['debug', 'disabledHooks', 'entrypoint', 'forceWrite', 'install', 'noOverwriteGlobs', 'output', 'templateParams', 'mapBaseUrlToFolder','registry']; +const REGISTRY_OPTIONS = ['url', 'username', 'password', 'token']; const logMessage = require('./logMessages'); @@ -91,10 +92,20 @@ class Generator { * @param {Boolean} [options.install=false] Install the template and its dependencies, even when the template has already been installed. * @param {Boolean} [options.debug=false] Enable more specific errors in the console. At the moment it only shows specific errors about filters. Keep in mind that as a result errors about template are less descriptive. * @param {Object} [options.mapBaseUrlToFolder] Optional parameter to map schema references from a base url to a local base folder e.g. url=https://schema.example.com/crm/ folder=./test/docs/ . + * @param {Object} [options.registry] Optional parameter with private registry configuration + * @param {String} [options.registry.url] Parameter to pass npm registry url + * @param {String} [options.registry.username] Optional parameter to pass npm registry username + * @param {String} [options.registry.password] Optional parameter to pass npm registry base64 encoded password + * @param {String} [options.registry.token] Optional parameter to pass npm registry auth token */ - constructor(templateName, targetDir, { templateParams = {}, entrypoint, noOverwriteGlobs, disabledHooks, output = 'fs', forceWrite = false, install = false, debug = false, mapBaseUrlToFolder = {} } = {}) { - const invalidOptions = getInvalidOptions(GENERATOR_OPTIONS, arguments[arguments.length - 1] || []); + constructor(templateName, targetDir, { templateParams = {}, entrypoint, noOverwriteGlobs, disabledHooks, output = 'fs', forceWrite = false, install = false, debug = false, mapBaseUrlToFolder = {}, registry = {url: '', username: '', password: '', token: '' }} = {}) { + const options = arguments[arguments.length - 1]; + const invalidOptions = getInvalidOptions(GENERATOR_OPTIONS, options || []); if (invalidOptions.length) throw new Error(`These options are not supported by the generator: ${invalidOptions.join(', ')}`); + if (options && options.registry) { + const invalidRegOptions = getInvalidOptions(REGISTRY_OPTIONS, options.registry || []); + if (invalidRegOptions.length) throw new Error(`There invalid parameters were specified to configure private registry: ${invalidRegOptions.join(', ')}`); + } if (!templateName) throw new Error('No template name has been specified.'); if (!entrypoint && !targetDir) throw new Error('No target directory has been specified.'); if (!['fs', 'string'].includes(output)) throw new Error(`Invalid output type ${output}. Valid values are 'fs' and 'string'.`); @@ -123,6 +134,8 @@ class Generator { this.hooks = {}; /** @type {Object} Maps schema URL to folder. */ this.mapBaseUrlToFolder = mapBaseUrlToFolder; + /** @type {Object} Npm registry information. */ + this.registry = registry; // Load template configuration /** @type {Object} The template parameters. The structure for this object is based on each individual template. */ @@ -385,9 +398,16 @@ class Generator { if (isFileSystemPath(this.templateName)) log.debug(logMessage.NPM_INSTALL_TRIGGER); - const arb = new Arborist({ - path: ROOT_DIR - }); + const arbOptions = { + path: ROOT_DIR, + }; + if (this.registry) { + if (this.registry.url) arbOptions.registry = this.registry.url; + if (this.registry.username) arbOptions.username = this.registry.username; + if (this.registry.password) arbOptions.password = this.registry.password; + if (this.registry.token) arbOptions.token = this.registry.token; + } + const arb = new Arborist(arbOptions); try { const installResult = await arb.reify({ diff --git a/test/generator.test.js b/test/generator.test.js index 3ec4a81c7..c95f233e0 100644 --- a/test/generator.test.js +++ b/test/generator.test.js @@ -322,7 +322,7 @@ describe('Generator', () => { const utils = require('../lib/utils'); const asyncapiURL = 'http://example.com/fake-asyncapi.yml'; utils.__contentOfFetchedFile = 'fake text'; - + const generateFromStringMock = jest.fn().mockResolvedValue(); const gen = new Generator('testTemplate', __dirname); gen.generateFromString = generateFromStringMock; @@ -410,6 +410,26 @@ describe('Generator', () => { }, 0); }); + it('works with a path to registry', async () => { + log.debug = jest.fn(); + utils.__getTemplateDetails = undefined; + const gen = new Generator('nameOfTestTemplate', __dirname, {debug: true, registry: {url: 'some.registry.com', username: 'user', password: 'password', token: 'token'}}); + await gen.installTemplate(); + setTimeout(() => { // This puts the call at the end of the Node.js event loop queue. + expect(arboristMock.reify).toHaveBeenCalledTimes(1); + }, 0); + }); + + it('throws an error indicating an unexpected param was given for registry configuration', () => { + const t = () => new Generator('testTemplate', __dirname, { + registry: { + url: 'some.url.com', + privateKey: 'some.key' + } + }); + expect(t).toThrow('There invalid parameters were specified to configure private registry: privateKey'); + }); + it('works with a url and force = true', async () => { const gen = new Generator('https://my-test-template.com', __dirname); await gen.installTemplate(true);