Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintignore

This file was deleted.

13 changes: 0 additions & 13 deletions .eslintrc.json

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ junit.xml
/coverage

oclif.manifest.json
.claude
27 changes: 27 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright 2026 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

const aioLibConfig = require('@adobe/eslint-config-aio-lib-config')
const jest = require('eslint-plugin-jest')

module.exports = [
{ ignores: ['tmp'] },
...aioLibConfig,
jest.configs['flat/recommended'],
{
settings: {
n: {
allowModules: ['@oclif/core']
}
}
}
]
19 changes: 7 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,26 @@
"bugs": "https://github.com/adobe/aio-cli-plugin-config/issues",
"dependencies": {
"@adobe/aio-lib-core-config": "^5",
"@oclif/core": "^2.0.0",
"@inquirer/prompts": "^7.0.0",
"@oclif/core": "^4.0.0",
"hjson": "^3.2.2",
"js-yaml": "^4.1.0"
},
"devDependencies": {
"@adobe/eslint-config-aio-lib-config": "^4.0.0",
"@adobe/eslint-config-aio-lib-config": "^5.0.0",
"acorn": "^8.7.0",
"chalk": "^4.0.0",
"eslint": "^8.57.1",
"eslint-config-oclif": "^5.2.2",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jest": "^27.9.0",
"eslint": "^9.0.0",
"eslint-plugin-jest": "^29.0.0",
"neostandard": "^0.13.0",
"eslint-plugin-jsdoc": "^48.11.0",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-standard": "^5.0.0",
"execa": "^4.0.2",
"globby": "^11.0.0",
"jest": "^29",
"jest-haste-map": "^29.5.0",
"jest-junit": "^16.0.0",
"jest-resolve": "^29.5.0",
"oclif": "^3.2.0",
"oclif": "^4.0.0",
"stdout-stderr": "^0.1.13"
},
"engines": {
Expand Down
5 changes: 3 additions & 2 deletions src/commands/config/clear.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/

const { Flags, ux } = require('@oclif/core')
const { Flags } = require('@oclif/core')
const BaseCommand = require('../../base-command')
const { prompt } = require('../../prompt')

class ClearCommand extends BaseCommand {
async run () {
const { flags } = await this.parse(ClearCommand)

if (!flags.force) {
const confirm = await ux.prompt('are you sure? [yN]', { type: 'normal' })
const confirm = await prompt('are you sure? [yN]')
if (!confirm[0] || confirm[0].toLowerCase() !== 'y') {
return
}
Expand Down
5 changes: 3 additions & 2 deletions src/commands/config/set.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/

const { Flags, Args, ux } = require('@oclif/core')
const { Flags, Args } = require('@oclif/core')
const BaseCommand = require('../../base-command')
const fs = require('fs')
const yaml = require('js-yaml')
const hjson = require('hjson')
const { getPipedData } = require('@adobe/aio-lib-core-config')
const path = require('path')
const { prompt } = require('../../prompt')

class SetCommand extends BaseCommand {
async run () {
Expand All @@ -39,7 +40,7 @@ class SetCommand extends BaseCommand {
this.error(`Cannot read file: ${value}`)
}
} else if (flags.interactive) {
value = await ux.prompt('value', { type: 'normal' })
value = await prompt('value')
} else if (value == null) {
if (args.key.indexOf('=') > 0) {
const parts = args.key.split('=')
Expand Down
25 changes: 25 additions & 0 deletions src/prompt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright 2026 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

const { input } = require('@inquirer/prompts')

/**
* Prompts the user for input.
*
* @param {string} message - the prompt message to display
* @returns {Promise<string>} the user's input
*/
async function prompt (message) {
return input({ message })
}

module.exports = { prompt }
8 changes: 5 additions & 3 deletions test/commands/base-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const TheCommand = require('../../src/base-command')
const { stdout } = require('stdout-stderr')
const hjson = require('hjson')

const mockConfig = { runHook: async () => ({ successes: [], failures: [] }) }

describe('base-command', () => {
test('exports', () => {
expect(typeof TheCommand).toEqual('function')
Expand All @@ -29,7 +31,7 @@ describe('base-command', () => {
let command

beforeEach(() => {
command = new TheCommand([], { })
command = new TheCommand([], mockConfig)
})

describe('printObject', () => {
Expand All @@ -43,13 +45,13 @@ describe('base-command', () => {
})

test('yaml', async () => {
command = new TheCommand(['--yaml'], { })
command = new TheCommand(['--yaml'], mockConfig)
await command.printObject({ foo: { bar: true } })
expect(stdout.output).toEqual('foo:\n bar: true\n\n')
})

test('json', async () => {
command = new TheCommand(['--json'], { })
command = new TheCommand(['--json'], mockConfig)
await command.printObject({ foo: { bar: true } })
expect(stdout.output).toEqual('{"foo":{"bar":true}}\n')
})
Expand Down
17 changes: 6 additions & 11 deletions test/commands/config/clear.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,10 @@ governing permissions and limitations under the License.
const TheCommand = require('../../../src/commands/config/clear.js')
const { mockSet } = require('@adobe/aio-lib-core-config/src/Config')

jest.mock('@oclif/core', () => {
return {
...jest.requireActual('@oclif/core'),
ux: {
prompt: jest.fn()
}
}
})
const { ux } = require('@oclif/core')
jest.mock('../../../src/prompt', () => ({
prompt: jest.fn()
}))
const { prompt } = require('../../../src/prompt')

describe('clear', () => {
afterEach(() => {
Expand Down Expand Up @@ -51,14 +46,14 @@ describe('clear', () => {
})

test('prompt with yes', () => {
ux.prompt = jest.fn(() => 'y')
prompt.mockResolvedValue('y')
return TheCommand.run([]).then(() => {
expect(mockSet).toHaveBeenCalledWith(null, null, false)
})
})

test('prompt with no', () => {
ux.prompt = jest.fn(() => 'n')
prompt.mockResolvedValue('n')
return TheCommand.run([]).then(() => {
expect(mockSet).not.toHaveBeenCalled()
})
Expand Down
69 changes: 22 additions & 47 deletions test/commands/config/set.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,18 @@ OF ANY KIND, either express or implied. See the License for the specific languag
governing permissions and limitations under the License.
*/

const { ux } = require('@oclif/core')
const TheCommand = require('../../../src/commands/config/set.js')
const config = require('@adobe/aio-lib-core-config')
const path = require('path')
const { mockSet } = require('@adobe/aio-lib-core-config/src/Config')

jest.mock('@oclif/core', () => {
return {
...jest.requireActual('@oclif/core'),
ux: {
prompt: jest.fn()
}
}
})
jest.mock('../../../src/prompt', () => ({
prompt: jest.fn()
}))
const { prompt } = require('../../../src/prompt')

describe('set', () => {
let command

beforeEach(() => {
command = new TheCommand([])
mockSet.mockImplementation(() => { return { a: 12 } })
})

Expand All @@ -42,106 +34,89 @@ describe('set', () => {
})

test('default', async () => {
command.argv = ['a-key', 'value']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key', 'value'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', 'value', false)
})

test('local', async () => {
command.argv = ['-l', 'a-key', 'value']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['-l', 'a-key', 'value'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', 'value', true)
})

test('global', async () => {
command.argv = ['-g', 'a-key', 'value']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['-g', 'a-key', 'value'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', 'value', false)
})

test('no value', async () => {
command.argv = ['a-key']
await expect(command.run()).rejects.toEqual(new Error('Missing value'))
await expect(TheCommand.run(['a-key'])).rejects.toEqual(new Error('Missing value'))
})

test('get piped data', async () => {
config.getPipedData.mockResolvedValue('a file')

command.argv = ['-g', 'a-key']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['-g', 'a-key'])).resolves.not.toThrow()

expect(config.getPipedData).toHaveBeenCalledWith()
expect(mockSet).toHaveBeenCalledWith('a-key', 'a file', false)
})

test('parse key=value', async () => {
command.argv = ['a-key=value']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key=value'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', 'value', false)
})

test('parse json', async () => {
command.argv = ['a-key', '-j', '{a:1}']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key', '-j', '{a:1}'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', { a: 1 }, false)
})

test('throw error on bad yaml parsing', async () => {
command.argv = ['a-key', '-y', 'a:\nhy ']
await expect(command.run()).rejects.toEqual(new Error('Cannot parse yaml'))
await expect(TheCommand.run(['a-key', '-y', 'a:\nhy '])).rejects.toEqual(new Error('Cannot parse yaml'))
})

test('throw error on bad json parsing', async () => {
command.argv = ['a-key', '-j', '{a:1\n']
await expect(command.run()).rejects.toEqual(new Error('Cannot parse json'))
await expect(TheCommand.run(['a-key', '-j', '{a:1\n'])).rejects.toEqual(new Error('Cannot parse json'))
})

test('parse yaml', async () => {
command.argv = ['a-key', '-y', 'a:\n b: true']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key', '-y', 'a:\n b: true'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', { a: { b: true } }, false)
})

test('json file', async () => {
command.argv = ['a-key', '-f', './test/__fixtures__/a.json']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key', '-f', './test/__fixtures__/a.json'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', { a: 12 }, false)
})

test('yaml file', async () => {
command.argv = ['a-key', '-f', './test/__fixtures__/a.yaml']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key', '-f', './test/__fixtures__/a.yaml'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', { a: { b: 12 } }, false)
})

test('yml file', async () => {
command.argv = ['a-key', '-f', './test/__fixtures__/a.yml']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key', '-f', './test/__fixtures__/a.yml'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', { a: { b: 12 } }, false)
})

test('other file', async () => {
command.argv = ['a-key', '-f', './test/__fixtures__/a.txt']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key', '-f', './test/__fixtures__/a.txt'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', 'raw data', false)
})

test('file but no value', async () => {
command.argv = ['a-key', '-f']
await expect(command.run()).rejects.toEqual(new Error('Missing filename'))
await expect(TheCommand.run(['a-key', '-f'])).rejects.toEqual(new Error('Missing filename'))
})

test('file but not exists', async () => {
command.argv = ['a-key', '-f', '/doesnotexist']
await expect(command.run()).rejects.toEqual(new Error(`Cannot read file: ${path.resolve('/doesnotexist')}`))
await expect(TheCommand.run(['a-key', '-f', '/doesnotexist'])).rejects.toEqual(new Error(`Cannot read file: ${path.resolve('/doesnotexist')}`))
})

test('prompt for value', async () => {
config.getPipedData.mockResolvedValue(null)
ux.prompt = jest.fn(() => 'a value')
prompt.mockResolvedValue('a value')

command.argv = ['a-key', '-i']
await expect(command.run()).resolves.not.toThrow()
await expect(TheCommand.run(['a-key', '-i'])).resolves.not.toThrow()
expect(mockSet).toHaveBeenCalledWith('a-key', 'a value', false)
})
})
Loading
Loading