diff --git a/docs/HUB.md b/docs/HUB.md index a0bd84b2..b9f33279 100644 --- a/docs/HUB.md +++ b/docs/HUB.md @@ -161,12 +161,19 @@ dc-cli hub clean ### add -Saves a hub configuration for later use. A hub configuration consists of: +Saves a hub configuration for later use. A hub configuration consists of either: - client id - client secret - hub id +or: + +- PAT token +- hub id + +Once you enter the `dc-cli hub add` command you will be prompted with a yes/no choice question as to whether you would like to use a PAT Token or not. + These can subsequently be used with `dc-cli hub use`. ``` diff --git a/src/common/hub-manager.spec.ts b/src/common/hub-manager.spec.ts index 2482176b..338bff9a 100644 --- a/src/common/hub-manager.spec.ts +++ b/src/common/hub-manager.spec.ts @@ -2,6 +2,7 @@ import HubManager from '../common/hub-manager'; import * as sdk from 'dc-management-sdk-js'; import * as configure from '../commands/configure'; import fs from 'fs-extra'; +import * as questionHelpers from '../common/question-helpers'; // Adjust the path as needed // eslint-disable-next-line const enquirer = require('enquirer'); @@ -13,6 +14,12 @@ const DummyHub = { name: 'dummy-hub' }; +const DummyHubWithPAT = { + patToken: 'amp-pat', + hubId: 'hub-id', + name: 'dummy-hub' +}; + jest.mock('dc-management-sdk-js', () => ({ ...jest.requireActual('dc-management-sdk-js'), DynamicContent: jest.fn() @@ -25,6 +32,8 @@ jest.mock('enquirer', () => ({ Password: jest.fn() })); +jest.mock('../common/question-helpers'); + describe('hub manager', function () { const hubGetMock = jest.fn(); const autocompleteRun = jest.fn(); @@ -101,10 +110,25 @@ describe('hub manager', function () { const mockedWriteFileSync = jest.spyOn(fs, 'writeFileSync'); jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true); + + (questionHelpers.asyncQuestion as jest.Mock).mockResolvedValue(false); + await HubManager.addHub({ ...yargArgs, ...DummyHub }); expect(mockedWriteFileSync).toHaveBeenCalled(); }); + it('should save hub with PAT token', async () => { + mockEmptyConfig(2); + + const mockedWriteFileSync = jest.spyOn(fs, 'writeFileSync'); + jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true); + + (questionHelpers.asyncQuestion as jest.Mock).mockResolvedValue(true); + + await HubManager.addHub({ ...yargArgs, ...DummyHubWithPAT }); + expect(mockedWriteFileSync).toHaveBeenCalled(); + }); + it('should save hub from user input', async () => { const inputRun = jest.fn().mockResolvedValueOnce('client-id').mockResolvedValueOnce('hub-id'); const passwordRun = jest.fn().mockResolvedValueOnce('client-secret'); @@ -121,6 +145,7 @@ describe('hub manager', function () { const mockedWriteFileSync = jest.spyOn(fs, 'writeFileSync'); jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true); + (questionHelpers.asyncQuestion as jest.Mock).mockResolvedValue(false); await HubManager.addHub({ ...yargArgs }); expect(mockedWriteFileSync).toHaveBeenCalled(); @@ -128,10 +153,35 @@ describe('hub manager', function () { expect(passwordRun).toHaveBeenCalledTimes(1); }); + it('should save hub from user input using PAT token', async () => { + const inputRun = jest.fn().mockResolvedValueOnce('hub-id'); + const passwordRun = jest.fn().mockResolvedValueOnce('amp-pat'); + + (enquirer.Input as jest.Mock).mockReturnValue({ + run: inputRun + }); + + (enquirer.Password as jest.Mock).mockReturnValue({ + run: passwordRun + }); + + mockEmptyConfig(2); + + const mockedWriteFileSync = jest.spyOn(fs, 'writeFileSync'); + jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true); + (questionHelpers.asyncQuestion as jest.Mock).mockResolvedValue(true); + await HubManager.addHub({ ...yargArgs }); + expect(mockedWriteFileSync).toHaveBeenCalled(); + + expect(inputRun).toHaveBeenCalledTimes(1); + expect(passwordRun).toHaveBeenCalledTimes(1); + }); + it('should fail to save a duplicate hub', async () => { mockDefaultConfig(); jest.spyOn(fs, 'existsSync').mockReturnValueOnce(true); + (questionHelpers.asyncQuestion as jest.Mock).mockResolvedValue(false); await expect(HubManager.addHub({ ...yargArgs, ...DummyHub })).rejects.toThrow(`config already exists`); }); diff --git a/src/common/hub-manager.ts b/src/common/hub-manager.ts index e1f532b3..a4815d78 100644 --- a/src/common/hub-manager.ts +++ b/src/common/hub-manager.ts @@ -4,6 +4,7 @@ import chalk from 'chalk'; import { handler as configure, CONFIG_FILENAME } from '../commands/configure'; import { Arguments } from 'yargs'; import dynamicContentClientFactory from '../services/dynamic-content-client-factory'; +import { asyncQuestion } from './question-helpers'; // eslint-disable-next-line const { AutoComplete, Input, Password } = require('enquirer'); @@ -20,6 +21,7 @@ export type HubConfiguration = { hubId: string; name?: string; isActive?: boolean; + patToken?: string; }; export const validateHub = async (creds: HubConfiguration): Promise => { @@ -40,17 +42,18 @@ const getHubs = (): HubConfiguration[] => { } const hubs = fs.readJSONSync(CONFIG_PATH, { encoding: 'utf-8' }); - return hubs.map((hub: HubConfiguration) => ({ - ...hub, - isActive: - activeHub.clientId === hub.clientId && - activeHub.clientSecret === hub.clientSecret && - activeHub.hubId === hub.hubId - })); + return hubs.map((hub: HubConfiguration) => { + const obj = { + ...hub, + isActive: activeHub.hubId === hub.hubId + }; + + return obj; + }); }; const saveHub = (hub: HubConfiguration): void => { - const hubs = [hub, ...getHubs().filter(h => h.hubId !== hub.hubId && h.clientId !== hub.clientId)]; + const hubs = [hub, ...getHubs().filter(h => h.hubId !== hub.hubId)]; fs.writeFileSync(CONFIG_PATH, JSON.stringify(hubs, undefined, 4), { encoding: 'utf-8' }); }; @@ -78,21 +81,32 @@ const credentialsHelpText = helpTag('credentials assigned by Amplience support') const hubIdHelpText = helpTag('found in hub settings -> properties'); export const addHub = async ( - args: Arguments<{ clientId?: string; clientSecret?: string; hubId?: string }> + args: Arguments<{ clientId?: string; clientSecret?: string; hubId?: string; patToken?: string }> ): Promise => { // dc config sectionHeader(`${dcTag} configuration ${credentialsHelpText}`); - // novadev-693 allow id, secret, and hub id to be passed via command line - args.clientId = args.clientId || (await ask(`client ${chalk.magenta('id')}:`)); - args.clientSecret = args.clientSecret || (await secureAsk(`client ${chalk.magenta('secret')}:`)); + const usePAT = await asyncQuestion('Would you like to use a PAT Token? (y/n)\n'); + + if (usePAT) { + args.patToken = args.patToken || (await secureAsk(`PAT ${chalk.magenta('token')}:`)); + } else { + // novadev-693 allow id, secret, and hub id to be passed via command line + args.clientId = args.clientId || (await ask(`client ${chalk.magenta('id')}:`)); + args.clientSecret = args.clientSecret || (await secureAsk(`client ${chalk.magenta('secret')}:`)); + } + args.hubId = args.hubId || (await ask(`hub id ${hubIdHelpText}:`)); // unique key for a hub is clientId/hubId - if (getHubs().find(hub => args.clientId === hub.clientId && args.hubId === hub.hubId)) { + if (args.clientId && getHubs().find(hub => args.clientId === hub.clientId && args.hubId === hub.hubId)) { throw new Error(`config already exists for client id [ ${args.clientId} ] and hub id [ ${args.hubId} ]`); } + if (usePAT && getHubs().find(hub => args.patToken === hub.patToken && args.hubId === hub.hubId)) { + throw new Error(`config already exists for PAT Token and hub id [ ${args.hubId} ]`); + } + const validated = await validateHub(args as HubConfiguration); if (validated && validated.name) { saveHub(validated); @@ -134,7 +148,7 @@ const useHub = async (argv: Arguments<{ hub: string }>): Promise { getHubs().forEach(hub => { const hubName = hub.isActive ? chalk.green.bold(`* ${hub.name}`) : ` ${hub.name}`; - console.log(`${hub.hubId} ${hub.clientId.substring(0, 8)} ${hubName}`); + console.log(`${hub.hubId} ${hub.clientId?.substring(0, 8) || hub.patToken?.substring(0, 8)} ${hubName}`); }); };