-
Notifications
You must be signed in to change notification settings - Fork 224
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cli): Adding disclaimer for plugin AI install (#3065)
* Adding a prerun hook to show AI plugin installation disclaimer * Moving logic to a plugins:preinstall hook * Adding tests for the feature * Updating conditional and adding tests for repo use case * Adding requested prompt
- Loading branch information
Showing
4 changed files
with
163 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -286,6 +286,7 @@ searchpath | |
secrettoken | ||
segv | ||
selfsigned | ||
sfdc | ||
shellescape | ||
showenvs | ||
sigints | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import {Hook, ux} from '@oclif/core' | ||
|
||
const hook: Hook<'plugins:preinstall'> = async function (options) { | ||
const npmPackageNames = ['@heroku/plugin-ai', '@heroku-cli/plugin-ai'] | ||
|
||
if (options.plugin.type !== 'npm' || !npmPackageNames.includes(options.plugin.name)) return | ||
|
||
ux.warn( | ||
'\n\nThis pilot feature is a Beta Service. You may opt to try such Beta Service in your sole discretion. ' + | ||
'Any use of the Beta Service is subject to the applicable Beta Services Terms provided at ' + | ||
'https://www.salesforce.com/company/legal/customer-agreements/. While use of the pilot feature itself is free, ' + | ||
'to the extent such use consumes a generally available Service, you may be charged for that consumption as set ' + | ||
'forth in the Documentation. Your continued use of this pilot feature constitutes your acceptance of the foregoing.\n\n' + | ||
'For clarity and without limitation, the various third-party machine learning and generative artificial intelligence ' + | ||
'(AI) models and applications (each a “Platform”) integrated with the Beta Service are Non-SFDC Applications, ' + | ||
'as that term is defined in the Beta Services Terms. Note that these third-party Platforms include features that use ' + | ||
'generative AI technology. Due to the nature of generative AI, the output that a Platform generates may be ' + | ||
'unpredictable, and may include inaccurate or harmful responses. Before using any generative AI output, Customer is ' + | ||
'solely responsible for reviewing the output for accuracy, safety, and compliance with applicable laws and third-party ' + | ||
'acceptable use policies. In addition, Customer’s use of each Platform may be subject to the Platform’s own terms and ' + | ||
'conditions, compliance with which Customer is solely responsible.\n', | ||
) | ||
|
||
const response = await ux.prompt('Continue? (Y/N)') | ||
if (response.toUpperCase() !== 'Y') { | ||
ux.error('Canceled', {exit: 1}) | ||
} | ||
} | ||
|
||
export default hook |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import {Config, ux} from '@oclif/core' | ||
import {CLIError} from '@oclif/core/lib/errors' | ||
import {expect} from 'chai' | ||
import {join} from 'path' | ||
import * as sinon from 'sinon' | ||
import {stderr} from 'stdout-stderr' | ||
|
||
describe('disclaimers ‘plugins:preinstall’ hook', function () { | ||
let config: Config | ||
let sandbox: sinon.SinonSandbox | ||
|
||
before(async function () { | ||
config = await Config.load({root: join(__dirname, '../../..')}) | ||
sandbox = sinon.createSandbox() | ||
}) | ||
|
||
afterEach(function () { | ||
sandbox.restore() | ||
}) | ||
|
||
context('when installing from a Github repository', function () { | ||
it('doesn’t show the disclaimer', async function () { | ||
stderr.start() | ||
config.runHook('plugins:preinstall', { | ||
plugin: { | ||
url: 'https://github.com/heroku/heroku-api-plugin', | ||
type: 'repo', | ||
}, | ||
}) | ||
stderr.stop() | ||
|
||
expect(stderr.output).not.to.include('This pilot feature is a Beta Service.') | ||
}) | ||
}) | ||
|
||
context('when installing a plugin different from ‘@heroku/plugin-ai’ or ‘@heroku-cli/plugin-ai’', function () { | ||
it('doesn’t show the disclaimer', async function () { | ||
stderr.start() | ||
config.runHook('plugins:preinstall', { | ||
plugin: { | ||
name: '@heroku-cli/plugin-events', | ||
tag: 'latest', | ||
type: 'npm', | ||
}, | ||
}) | ||
stderr.stop() | ||
|
||
expect(stderr.output).not.to.include('This pilot feature is a Beta Service.') | ||
}) | ||
}) | ||
|
||
context('when installing the ‘@heroku/plugin-ai’ plugin', function () { | ||
it('shows the disclaimer and prompts the user', async function () { | ||
const promptStub = sandbox.stub(ux, 'prompt').onFirstCall().resolves('y') | ||
|
||
stderr.start() | ||
await config.runHook('plugins:preinstall', { | ||
plugin: { | ||
name: '@heroku/plugin-ai', | ||
tag: 'latest', | ||
type: 'npm', | ||
}, | ||
}) | ||
stderr.stop() | ||
|
||
expect(stderr.output).to.include('This pilot feature is a Beta Service.') | ||
expect(promptStub.calledOnce).to.be.true | ||
}) | ||
|
||
it('cancels installation if customer doesn’t accepts the prompt', async function () { | ||
sandbox.stub(ux, 'prompt').onFirstCall().resolves('n') | ||
|
||
stderr.start() | ||
try { | ||
await config.runHook('plugins:preinstall', { | ||
plugin: { | ||
name: '@heroku/plugin-ai', | ||
tag: 'latest', | ||
type: 'npm', | ||
}, | ||
}) | ||
} catch (error: unknown) { | ||
stderr.stop() | ||
const {message, oclif} = error as CLIError | ||
expect(message).to.equal('Canceled') | ||
expect(oclif.exit).to.equal(1) | ||
} | ||
}) | ||
}) | ||
|
||
context('when installing the ‘@heroku-cli/plugin-ai’ plugin', function () { | ||
it('shows the disclaimer and prompts the user', async function () { | ||
const promptStub = sandbox.stub(ux, 'prompt').onFirstCall().resolves('y') | ||
|
||
stderr.start() | ||
await config.runHook('plugins:preinstall', { | ||
plugin: { | ||
name: '@heroku-cli/plugin-ai', | ||
tag: 'latest', | ||
type: 'npm', | ||
}, | ||
}) | ||
stderr.stop() | ||
|
||
expect(stderr.output).to.include('This pilot feature is a Beta Service.') | ||
expect(promptStub.calledOnce).to.be.true | ||
}) | ||
|
||
it('cancels installation if customer doesn’t accepts the prompt', async function () { | ||
sandbox.stub(ux, 'prompt').onFirstCall().resolves('n') | ||
|
||
stderr.start() | ||
try { | ||
await config.runHook('plugins:preinstall', { | ||
plugin: { | ||
name: '@heroku-cli/plugin-ai', | ||
tag: 'latest', | ||
type: 'npm', | ||
}, | ||
}) | ||
} catch (error: unknown) { | ||
stderr.stop() | ||
const {message, oclif} = error as CLIError | ||
expect(message).to.equal('Canceled') | ||
expect(oclif.exit).to.equal(1) | ||
} | ||
}) | ||
}) | ||
}) |